View Javadoc

1   // ========================================================================
2   // Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.servlets;
15  
16  
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.io.OutputStream;
20  import java.net.InetSocketAddress;
21  import java.net.MalformedURLException;
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.UnavailableException;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  
36  import org.eclipse.jetty.client.HttpClient;
37  import org.eclipse.jetty.client.HttpExchange;
38  import org.eclipse.jetty.continuation.Continuation;
39  import org.eclipse.jetty.continuation.ContinuationSupport;
40  import org.eclipse.jetty.http.HttpHeaderValues;
41  import org.eclipse.jetty.http.HttpHeaders;
42  import org.eclipse.jetty.http.HttpSchemes;
43  import org.eclipse.jetty.http.HttpURI;
44  import org.eclipse.jetty.io.Buffer;
45  import org.eclipse.jetty.io.EofException;
46  import org.eclipse.jetty.util.IO;
47  import org.eclipse.jetty.util.TypeUtil;
48  import org.eclipse.jetty.util.log.Log;
49  import org.eclipse.jetty.util.log.Logger;
50  
51  
52  /**
53   * Asynchronous Proxy Servlet.
54   * 
55   * Forward requests to another server either as a standard web proxy (as defined by
56   * RFC2616) or as a transparent proxy.
57   * 
58   * This servlet needs the jetty-util and jetty-client classes to be available to
59   * the web application.
60   * 
61   * This servlet has a a logger called "org.eclipse.jetty.servlets.ProxyServlet".
62   */
63  public class ProxyServlet implements Servlet
64  {
65      private static Logger __log = Log.getLogger("org.eclipse.jetty.servlets.ProxyServlet");
66      
67      HttpClient _client;
68  
69      protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
70      {
71          _DontProxyHeaders.add("proxy-connection");
72          _DontProxyHeaders.add("connection");
73          _DontProxyHeaders.add("keep-alive");
74          _DontProxyHeaders.add("transfer-encoding");
75          _DontProxyHeaders.add("te");
76          _DontProxyHeaders.add("trailer");
77          _DontProxyHeaders.add("proxy-authorization");
78          _DontProxyHeaders.add("proxy-authenticate");
79          _DontProxyHeaders.add("upgrade");
80      }
81  
82      private ServletConfig config;
83      private ServletContext context;
84  
85      /* (non-Javadoc)
86       * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
87       */
88      public void init(ServletConfig config) throws ServletException
89      {
90          this.config=config;
91          this.context=config.getServletContext();
92  
93          _client=new HttpClient();
94          //_client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
95          _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
96          try
97          {
98              _client.start();
99          }
100         catch (Exception e)
101         {
102             throw new ServletException(e);
103         }
104     }
105 
106     /* (non-Javadoc)
107      * @see javax.servlet.Servlet#getServletConfig()
108      */
109     public ServletConfig getServletConfig()
110     {
111         return config;
112     }
113 
114     /* (non-Javadoc)
115      * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
116      */
117     public void service(ServletRequest req, ServletResponse res) throws ServletException,
118             IOException
119     {
120         final int debug=__log.isDebugEnabled()?req.hashCode():0;
121         
122         final HttpServletRequest request = (HttpServletRequest)req;
123         final HttpServletResponse response = (HttpServletResponse)res;
124         if ("CONNECT".equalsIgnoreCase(request.getMethod()))
125         {
126             handleConnect(request,response);
127         }
128         else
129         {
130             final InputStream in=request.getInputStream();
131             final OutputStream out=response.getOutputStream();
132 
133             final Continuation continuation = ContinuationSupport.getContinuation(request);
134             
135             if (!continuation.isInitial())
136                 response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT); // Need better test that isInitial
137             else
138             {
139                 String uri=request.getRequestURI();
140                 if (request.getQueryString()!=null)
141                     uri+="?"+request.getQueryString();
142 
143 		HttpURI url=proxyHttpURI(request.getScheme(),
144 		                         request.getServerName(),
145 		                         request.getServerPort(),
146 		                         uri);
147 		
148 		if (debug!=0)
149 		    __log.debug(debug+" proxy "+uri+"-->"+url);
150 		    
151 		if (url==null)
152 		{
153 		    response.sendError(HttpServletResponse.SC_FORBIDDEN);
154 		    return;
155 		}
156 
157                 HttpExchange exchange = new HttpExchange()
158                 {
159                     protected void onRequestCommitted() throws IOException
160                     {
161                     }
162 
163                     protected void onRequestComplete() throws IOException
164                     {
165                     }
166 
167                     protected void onResponseComplete() throws IOException
168                     {
169                         if (debug!=0)
170                             __log.debug(debug+" complete");
171                         continuation.complete();
172                     }
173 
174                     protected void onResponseContent(Buffer content) throws IOException
175                     {
176                         if (debug!=0)
177                             __log.debug(debug+" content"+content.length());
178                         content.writeTo(out);
179                     }
180 
181                     protected void onResponseHeaderComplete() throws IOException
182                     {
183                     }
184 
185                     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
186                     {
187                         if (debug!=0)
188                             __log.debug(debug+" "+version+" "+status+" "+reason);
189                         
190                         if (reason!=null && reason.length()>0)
191                             response.setStatus(status,reason.toString());
192                         else
193                             response.setStatus(status);
194                     }
195 
196                     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
197                     {
198                         String s = name.toString().toLowerCase();
199                         if (!_DontProxyHeaders.contains(s) ||
200                            (HttpHeaders.CONNECTION_BUFFER.equals(name) &&
201                             HttpHeaderValues.CLOSE_BUFFER.equals(value)))
202                         {
203                             if (debug!=0)
204                                 __log.debug(debug+" "+name+": "+value);
205                             
206                             response.addHeader(name.toString(),value.toString());
207                         }
208                         else if (debug!=0)
209                                 __log.debug(debug+" "+name+"! "+value);
210                     }
211 
212                     protected void onConnectionFailed(Throwable ex)
213                     {
214                         onException(ex);
215                     }
216 
217                     protected void onException(Throwable ex)
218                     {
219                         if (ex instanceof EofException)
220                         {
221                             Log.ignore(ex);
222                             return;
223                         }
224                         Log.warn(ex.toString());
225                         Log.debug(ex);
226                         if (!response.isCommitted())
227                             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
228                         continuation.complete();
229                     }
230 
231                     protected void onExpire()
232                     {
233                         if (!response.isCommitted())
234                             response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
235                         continuation.complete();
236                     }
237 
238                 };
239 
240                 exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
241                 exchange.setMethod(request.getMethod());
242 		exchange.setURL(url.toString());
243                 exchange.setVersion(request.getProtocol());
244                 
245                 if (debug!=0)
246                     __log.debug(debug+" "+request.getMethod()+" "+url+" "+request.getProtocol());
247 
248                 // check connection header
249                 String connectionHdr = request.getHeader("Connection");
250                 if (connectionHdr!=null)
251                 {
252                     connectionHdr=connectionHdr.toLowerCase();
253                     if (connectionHdr.indexOf("keep-alive")<0  &&
254                             connectionHdr.indexOf("close")<0)
255                         connectionHdr=null;
256                 }
257 
258                 // copy headers
259                 boolean xForwardedFor=false;
260                 boolean hasContent=false;
261                 long contentLength=-1;
262                 Enumeration<?> enm = request.getHeaderNames();
263                 while (enm.hasMoreElements())
264                 {
265                     // TODO could be better than this!
266                     String hdr=(String)enm.nextElement();
267                     String lhdr=hdr.toLowerCase();
268 
269                     if (_DontProxyHeaders.contains(lhdr))
270                         continue;
271                     if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0)
272                         continue;
273 
274                     if ("content-type".equals(lhdr))
275                         hasContent=true;
276                     else if ("content-length".equals(lhdr))
277                     {
278                         contentLength=request.getContentLength();
279                         exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(contentLength));
280                         if (contentLength>0)
281                             hasContent=true;
282                     }
283                     else if ("x-forwarded-for".equals(lhdr))
284                         xForwardedFor=true;
285 
286                     Enumeration<?> vals = request.getHeaders(hdr);
287                     while (vals.hasMoreElements())
288                     {
289                         String val = (String)vals.nextElement();
290                         if (val!=null)
291                         {
292                             if (debug!=0)
293                                 __log.debug(debug+" "+hdr+": "+val);
294 
295                             exchange.setRequestHeader(hdr,val);
296                         }
297                     }
298                 }
299 
300                 // Proxy headers
301                 exchange.setRequestHeader("Via","1.1 (jetty)");
302                 if (!xForwardedFor)
303                     exchange.addRequestHeader("X-Forwarded-For",
304                             request.getRemoteAddr());
305 
306                 if (hasContent)
307                     exchange.setRequestContentSource(in);
308 
309                 continuation.suspend(response); 
310                 _client.send(exchange);
311 
312             }
313         }
314     }
315 
316 
317     /* ------------------------------------------------------------ */
318     public void handleConnect(HttpServletRequest request,
319                               HttpServletResponse response)
320         throws IOException
321     {
322         String uri = request.getRequestURI();
323 
324         String port = "";
325         String host = "";
326 
327         int c = uri.indexOf(':');
328         if (c>=0)
329         {
330             port = uri.substring(c+1);
331             host = uri.substring(0,c);
332             if (host.indexOf('/')>0)
333                 host = host.substring(host.indexOf('/')+1);
334         }
335 
336         // TODO - make this async!
337 
338 
339         InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
340 
341         //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
342         //{
343         //    sendForbid(request,response,uri);
344         //}
345         //else
346         {
347             InputStream in=request.getInputStream();
348             OutputStream out=response.getOutputStream();
349 
350             Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
351 
352             response.setStatus(200);
353             response.setHeader("Connection","close");
354             response.flushBuffer();
355             // TODO prevent real close!
356 
357             IO.copyThread(socket.getInputStream(),out);
358             IO.copy(in,socket.getOutputStream());
359         }
360     }
361 
362     /* ------------------------------------------------------------ */
363     protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri)
364         throws MalformedURLException
365     {
366         return new HttpURI(scheme+"://"+serverName+":"+serverPort+uri);
367     }
368 
369 
370     /* (non-Javadoc)
371      * @see javax.servlet.Servlet#getServletInfo()
372      */
373     public String getServletInfo()
374     {
375         return "Proxy Servlet";
376     }
377 
378     /* (non-Javadoc)
379      * @see javax.servlet.Servlet#destroy()
380      */
381     public void destroy()
382     {
383 
384     }
385     
386     /**
387      * Transparent Proxy.
388      * 
389      * This convenience extension to AsyncProxyServlet configures the servlet
390      * as a transparent proxy.   The servlet is configured with init parameter:<ul>
391      * <li> ProxyTo - a URI like http://host:80/context to which the request is proxied.
392      * <li> Prefix  - a URI prefix that is striped from the start of the forwarded URI.
393      * </ul>
394      * For example, if a request was received at /foo/bar and the ProxyTo was  http://host:80/context
395      * and the Prefix was /foo, then the request would be proxied to http://host:80/context/bar
396      *
397      */
398     public static class Transparent extends ProxyServlet
399     {
400         String _prefix;
401         String _proxyTo;
402         
403         public Transparent()
404         {    
405         }
406         
407         public Transparent(String prefix,String server, int port)
408         {
409             _prefix=prefix;
410             _proxyTo="http://"+server+":"+port;
411         }
412 
413         public void init(ServletConfig config) throws ServletException
414         {
415             if (config.getInitParameter("ProxyTo")!=null)
416                 _proxyTo=config.getInitParameter("ProxyTo");
417             if (config.getInitParameter("Prefix")!=null)
418                 _prefix=config.getInitParameter("Prefix");
419             if (_proxyTo==null)
420                 throw new UnavailableException("No ProxyTo");
421             super.init(config);
422             config.getServletContext().log("Transparent AsyncProxyServlet @ "+(_prefix==null?"-":_prefix)+ " to "+_proxyTo);
423         }
424         
425         protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
426         {
427             if (_prefix!=null && !uri.startsWith(_prefix))
428                 return null;
429 
430             if (_prefix!=null)
431                 return new HttpURI(_proxyTo+uri.substring(_prefix.length()));
432             return new HttpURI(_proxyTo+uri);
433         }
434     }
435 
436 }