1   // ========================================================================
2   // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.proxy;
16  
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.OutputStream;
21  import java.net.InetSocketAddress;
22  import java.net.Socket;
23  import java.util.Enumeration;
24  import java.util.HashSet;
25  
26  import javax.servlet.Servlet;
27  import javax.servlet.ServletConfig;
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletException;
30  import javax.servlet.ServletRequest;
31  import javax.servlet.ServletResponse;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  
35  import org.mortbay.io.Buffer;
36  import org.mortbay.jetty.EofException;
37  import org.mortbay.jetty.HttpHeaders;
38  import org.mortbay.jetty.HttpSchemes;
39  import org.mortbay.jetty.client.Address;
40  import org.mortbay.jetty.client.HttpClient;
41  import org.mortbay.jetty.client.HttpExchange;
42  import org.mortbay.log.Log;
43  import org.mortbay.util.IO;
44  import org.mortbay.util.TypeUtil;
45  
46  
47  
48  /**
49   * EXPERIMENTAL Proxy servlet.
50   * @author gregw
51   *
52   */
53  public class AsyncProxyServlet implements Servlet
54  {
55      HttpClient _client;
56  
57      protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
58      {
59          _DontProxyHeaders.add("proxy-connection");
60          _DontProxyHeaders.add("connection");
61          _DontProxyHeaders.add("keep-alive");
62          _DontProxyHeaders.add("transfer-encoding");
63          _DontProxyHeaders.add("te");
64          _DontProxyHeaders.add("trailer");
65          _DontProxyHeaders.add("proxy-authorization");
66          _DontProxyHeaders.add("proxy-authenticate");
67          _DontProxyHeaders.add("upgrade");
68      }
69  
70      private ServletConfig config;
71      private ServletContext context;
72  
73      /* (non-Javadoc)
74       * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
75       */
76      public void init(ServletConfig config) throws ServletException
77      {
78          this.config=config;
79          this.context=config.getServletContext();
80  
81          _client=new HttpClient();
82          //_client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
83          _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
84          try
85          {
86              _client.start();
87          }
88          catch (Exception e)
89          {
90              throw new ServletException(e);
91          }
92      }
93  
94      /* (non-Javadoc)
95       * @see javax.servlet.Servlet#getServletConfig()
96       */
97      public ServletConfig getServletConfig()
98      {
99          return config;
100     }
101 
102     /* (non-Javadoc)
103      * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
104      */
105     public void service(ServletRequest req, ServletResponse res) throws ServletException,
106             IOException
107     {
108         final HttpServletRequest request = (HttpServletRequest)req;
109         final HttpServletResponse response = (HttpServletResponse)res;
110         if ("CONNECT".equalsIgnoreCase(request.getMethod()))
111         {
112             handleConnect(request,response);
113         }
114         else
115         {
116             final InputStream in=request.getInputStream();
117             final OutputStream out=response.getOutputStream();
118 
119             if (request.isInitial())
120             {
121                 String uri=request.getRequestURI();
122                 if (request.getQueryString()!=null)
123                     uri+="?"+request.getQueryString();
124 
125                 HttpExchange exchange = new HttpExchange()
126                 {
127                     protected void onRequestCommitted() throws IOException
128                     {
129                     }
130 
131                     protected void onRequestComplete() throws IOException
132                     {
133                     }
134 
135                     protected void onResponseComplete() throws IOException
136                     {
137                         request.complete();
138                     }
139 
140                     protected void onResponseContent(Buffer content) throws IOException
141                     {
142                         content.writeTo(out);
143                     }
144 
145                     protected void onResponseHeaderComplete() throws IOException
146                     {
147                     }
148 
149                     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
150                     {
151                         if (reason!=null && reason.length()>0)
152                             response.setStatus(status,reason.toString());
153                         else
154                             response.setStatus(status);
155                     }
156 
157                     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
158                     {
159                         String s = name.toString().toLowerCase();
160                         if (!_DontProxyHeaders.contains(s))
161                             response.addHeader(name.toString(),value.toString());
162                     }
163 
164                     protected void onConnectionFailed(Throwable ex)
165                     {
166                         onException(ex);
167                     }
168 
169                     protected void onException(Throwable ex)
170                     {
171                         if (ex instanceof EofException)
172                         {
173                             Log.ignore(ex);
174                             return;
175                         }
176 
177                         try
178                         {
179                             Log.warn(ex.toString());
180                             Log.debug(ex);
181                             if (!response.isCommitted())
182                                 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
183                             request.complete();
184                         }
185                         catch(EofException e)
186                         {
187                             Log.ignore(e);
188                         }
189                         catch(IOException e)
190                         {
191                             Log.warn(e.toString());
192                             Log.debug(e);
193                         }
194                     }
195 
196                     protected void onExpire()
197                     {
198                         try
199                         {
200                             if (!response.isCommitted())
201                                 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
202                             request.complete();
203                         }
204                         catch(EofException e)
205                         {
206                             Log.ignore(e);
207                         }
208                         catch(IOException e)
209                         {
210                             Log.warn(e.toString());
211                             Log.debug(e);
212                         }
213                     }
214 
215                 };
216 
217                 exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
218                 exchange.setMethod(request.getMethod());
219                 exchange.setURI(uri);
220 
221                 exchange.setVersion(request.getProtocol());
222                 Address address=new Address(request.getServerName(),request.getServerPort());
223                 exchange.setAddress(address);
224 
225                 if (Log.isDebugEnabled())
226                     Log.debug("PROXY TO http://"+address.getHost()+":"+address.getPort()+uri);
227 
228 
229                 // check connection header
230                 String connectionHdr = request.getHeader("Connection");
231                 if (connectionHdr!=null)
232                 {
233                     connectionHdr=connectionHdr.toLowerCase();
234                     if (connectionHdr.indexOf("keep-alive")<0  &&
235                             connectionHdr.indexOf("close")<0)
236                         connectionHdr=null;
237                 }
238 
239                 // copy headers
240                 boolean xForwardedFor=false;
241                 boolean hasContent=false;
242                 long contentLength=-1;
243                 Enumeration<?> enm = request.getHeaderNames();
244                 while (enm.hasMoreElements())
245                 {
246                     // TODO could be better than this!
247                     String hdr=(String)enm.nextElement();
248                     String lhdr=hdr.toLowerCase();
249 
250                     if (_DontProxyHeaders.contains(lhdr))
251                         continue;
252                     if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0)
253                         continue;
254 
255                     if ("content-type".equals(lhdr))
256                         hasContent=true;
257                     if ("content-length".equals(lhdr))
258                     {
259                         contentLength=request.getContentLength();
260                         exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(contentLength));
261                         if (contentLength>0)
262                             hasContent=true;
263                     }
264 
265                     Enumeration<?> vals = request.getHeaders(hdr);
266                     while (vals.hasMoreElements())
267                     {
268                         String val = (String)vals.nextElement();
269                         if (val!=null)
270                         {
271                             exchange.setRequestHeader(lhdr,val);
272                             xForwardedFor|="X-Forwarded-For".equalsIgnoreCase(hdr);
273                         }
274                     }
275                 }
276 
277                 // Proxy headers
278                 exchange.setRequestHeader("Via","1.1 (jetty)");
279                 if (!xForwardedFor)
280                     exchange.addRequestHeader("X-Forwarded-For",
281                             request.getRemoteAddr());
282 
283                 if (hasContent)
284                     exchange.setRequestContentSource(in);
285 
286                 request.suspend();
287                 _client.send(exchange);
288 
289             }
290         }
291     }
292 
293 
294     /* ------------------------------------------------------------ */
295     public void handleConnect(HttpServletRequest request,
296                               HttpServletResponse response)
297         throws IOException
298     {
299         String uri = request.getRequestURI();
300 
301         context.log("CONNECT: "+uri);
302 
303         String port = "";
304         String host = "";
305 
306         int c = uri.indexOf(':');
307         if (c>=0)
308         {
309             port = uri.substring(c+1);
310             host = uri.substring(0,c);
311             if (host.indexOf('/')>0)
312                 host = host.substring(host.indexOf('/')+1);
313         }
314 
315         // TODO - make this async!
316 
317 
318         InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
319 
320         //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
321         //{
322         //    sendForbid(request,response,uri);
323         //}
324         //else
325         {
326             InputStream in=request.getInputStream();
327             OutputStream out=response.getOutputStream();
328 
329             Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
330             context.log("Socket: "+socket);
331 
332             response.setStatus(200);
333             response.setHeader("Connection","close");
334             response.flushBuffer();
335             // TODO prevent real close!
336 
337             context.log("out<-in");
338             IO.copyThread(socket.getInputStream(),out);
339             context.log("in->out");
340             IO.copy(in,socket.getOutputStream());
341         }
342     }
343 
344 
345     /* (non-Javadoc)
346      * @see javax.servlet.Servlet#getServletInfo()
347      */
348     public String getServletInfo()
349     {
350         return "Proxy Servlet";
351     }
352 
353     /* (non-Javadoc)
354      * @see javax.servlet.Servlet#destroy()
355      */
356     public void destroy()
357     {
358 
359     }
360 
361 
362 }