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