View Javadoc

1   package org.eclipse.jetty.rewrite.handler;
2   
3   //========================================================================
4   //Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
5   //------------------------------------------------------------------------
6   //All rights reserved. This program and the accompanying materials
7   //are made available under the terms of the Eclipse Public License v1.0
8   //and Apache License v2.0 which accompanies this distribution.
9   //The Eclipse Public License is available at
10  //http://www.eclipse.org/legal/epl-v10.html
11  //The Apache License v2.0 is available at
12  //http://www.opensource.org/licenses/apache2.0.php
13  //You may elect to redistribute this code under either of these licenses.
14  //========================================================================
15  
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.net.MalformedURLException;
20  import java.util.Enumeration;
21  import java.util.HashSet;
22  
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.http.HttpServletResponse;
25  
26  import org.eclipse.jetty.client.HttpClient;
27  import org.eclipse.jetty.client.HttpExchange;
28  import org.eclipse.jetty.http.HttpHeaderValues;
29  import org.eclipse.jetty.http.HttpHeaders;
30  import org.eclipse.jetty.http.HttpURI;
31  import org.eclipse.jetty.http.PathMap;
32  import org.eclipse.jetty.io.Buffer;
33  import org.eclipse.jetty.io.EofException;
34  import org.eclipse.jetty.util.log.Log;
35  import org.eclipse.jetty.util.log.Logger;
36  import org.eclipse.jetty.util.thread.QueuedThreadPool;
37  
38  /**
39   * This rule allows the user to configure a particular rewrite rule that will proxy out
40   * to a configured location.  This rule uses the jetty http client.
41   * 
42   * Rule rule = new ProxyRule();
43   * rule.setPattern("/foo/*");
44   * rule.setProxyTo("http://url.com");
45   * 
46   * see api for other configuration options which influence the configuration of the jetty 
47   * client instance
48   * 
49   */
50  public class ProxyRule extends PatternRule
51  {
52      private static final Logger _log = Log.getLogger(ProxyRule.class);
53  
54      private HttpClient _client;
55      private String _hostHeader;
56      private String _proxyTo;
57      
58      private int _connectorType = 2;
59      private String _maxThreads;
60      private String _maxConnections;
61      private String _timeout;
62      private String _idleTimeout;
63      private String _requestHeaderSize;
64      private String _requestBufferSize;
65      private String _responseHeaderSize;
66      private String _responseBufferSize;
67  
68      private HashSet<String> _DontProxyHeaders = new HashSet<String>();
69      {
70          _DontProxyHeaders.add("proxy-connection");
71          _DontProxyHeaders.add("connection");
72          _DontProxyHeaders.add("keep-alive");
73          _DontProxyHeaders.add("transfer-encoding");
74          _DontProxyHeaders.add("te");
75          _DontProxyHeaders.add("trailer");
76          _DontProxyHeaders.add("proxy-authorization");
77          _DontProxyHeaders.add("proxy-authenticate");
78          _DontProxyHeaders.add("upgrade");
79      }
80  
81      /* ------------------------------------------------------------ */
82      public ProxyRule()
83      {
84          _handling = true;
85          _terminating = true;
86      }
87  
88      /* ------------------------------------------------------------ */
89      private void initializeClient() throws Exception
90      {
91          _client = new HttpClient();
92          _client.setConnectorType(_connectorType);
93          
94          if ( _maxThreads != null )
95          {
96              _client.setThreadPool(new QueuedThreadPool(Integer.parseInt(_maxThreads)));
97          }
98          else
99          {
100             _client.setThreadPool(new QueuedThreadPool());
101         }
102         
103         if ( _maxConnections != null )
104         {
105             _client.setMaxConnectionsPerAddress(Integer.parseInt(_maxConnections));
106         }
107         
108         if ( _timeout != null )
109         {
110             _client.setTimeout(Long.parseLong(_timeout));
111         }
112         
113         if ( _idleTimeout != null )
114         {
115             _client.setIdleTimeout(Long.parseLong(_idleTimeout));
116         }
117         
118         if ( _requestBufferSize != null )
119         {
120             _client.setRequestBufferSize(Integer.parseInt(_requestBufferSize));
121         }
122         
123         if ( _requestHeaderSize != null )
124         {
125             _client.setRequestHeaderSize(Integer.parseInt(_requestHeaderSize));
126         }
127         
128         if ( _responseBufferSize != null )
129         {
130             _client.setResponseBufferSize(Integer.parseInt(_responseBufferSize));
131         }
132         
133         if ( _responseHeaderSize != null )
134         {
135             _client.setResponseHeaderSize(Integer.parseInt(_responseHeaderSize));
136         }                 
137         
138         _client.start();
139     }
140 
141     /* ------------------------------------------------------------ */
142     private HttpURI proxyHttpURI(String uri) throws MalformedURLException
143     {
144         return new HttpURI(_proxyTo + uri);
145     }
146 
147     /* ------------------------------------------------------------ */
148     @Override
149     protected String apply(String target, HttpServletRequest request, final HttpServletResponse response) throws IOException
150     {
151         synchronized (this)
152         {
153             if (_client == null)
154             {
155                 try
156                 {
157                     initializeClient();
158                 }
159                 catch (Exception e)
160                 {
161                     throw new IOException("Unable to proxy: " + e.getMessage());
162                 }
163             }
164         }
165 
166         final int debug = _log.isDebugEnabled()?request.hashCode():0;
167 
168         final InputStream in = request.getInputStream();
169         final OutputStream out = response.getOutputStream();
170 
171         HttpURI url = createUrl(request,debug);
172 
173         if (url == null)
174         {
175             response.sendError(HttpServletResponse.SC_FORBIDDEN);
176             return target;
177         }
178 
179         HttpExchange exchange = new HttpExchange()
180         {
181             @Override
182             protected void onRequestCommitted() throws IOException
183             {
184             }
185 
186             @Override
187             protected void onRequestComplete() throws IOException
188             {
189             }
190 
191             @Override
192             protected void onResponseComplete() throws IOException
193             {
194                 if (debug != 0)
195                     _log.debug(debug + " complete");
196             }
197 
198             @Override
199             protected void onResponseContent(Buffer content) throws IOException
200             {
201                 if (debug != 0)
202                     _log.debug(debug + " content" + content.length());
203                 content.writeTo(out);
204             }
205 
206             @Override
207             protected void onResponseHeaderComplete() throws IOException
208             {
209             }
210 
211             @SuppressWarnings("deprecation")
212             @Override
213             protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
214             {
215                 if (debug != 0)
216                     _log.debug(debug + " " + version + " " + status + " " + reason);
217 
218                 if (reason != null && reason.length() > 0)
219                     response.setStatus(status,reason.toString());
220                 else
221                     response.setStatus(status);
222             }
223 
224             @Override
225             protected void onResponseHeader(Buffer name, Buffer value) throws IOException
226             {
227                 String s = name.toString().toLowerCase();
228                 if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value)))
229                 {
230                     if (debug != 0)
231                         _log.debug(debug + " " + name + ": " + value);
232 
233                     response.addHeader(name.toString(),value.toString());
234                 }
235                 else if (debug != 0)
236                     _log.debug(debug + " " + name + "! " + value);
237             }
238 
239             @Override
240             protected void onConnectionFailed(Throwable ex)
241             {
242                 _log.warn(ex.toString());
243                 _log.debug(ex);
244                 if (!response.isCommitted())
245                 {
246                     response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
247                 }
248             }
249 
250             @Override
251             protected void onException(Throwable ex)
252             {
253                 if (ex instanceof EofException)
254                 {
255                     _log.ignore(ex);
256                     return;
257                 }
258                 _log.warn(ex.toString());
259                 _log.debug(ex);
260                 if (!response.isCommitted())
261                 {
262                     response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
263                 }
264             }
265 
266             @Override
267             protected void onExpire()
268             {
269                 if (!response.isCommitted())
270                 {
271                     response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
272                 }
273             }
274 
275         };
276 
277         exchange.setMethod(request.getMethod());
278         exchange.setURL(url.toString());
279         exchange.setVersion(request.getProtocol());
280 
281         if (debug != 0)
282         {
283             _log.debug("{} {} {} {}", debug ,request.getMethod(), url, request.getProtocol());
284         }
285         
286         boolean hasContent = createHeaders(request,debug,exchange);
287 
288         if (hasContent)
289         {
290             exchange.setRequestContentSource(in);
291         }
292         
293         /*
294          * we need to set the timeout on the exchange to take into account the timeout of the HttpClient and the HttpExchange
295          */
296         long ctimeout = (_client.getTimeout() > exchange.getTimeout())?_client.getTimeout():exchange.getTimeout();
297         exchange.setTimeout(ctimeout);
298 
299         _client.send(exchange);
300         
301         try
302         {
303             exchange.waitForDone();
304         }
305         catch (InterruptedException e)
306         {
307             _log.info("Exception while waiting for response on proxied request", e);
308         }
309         return target;
310     }
311 
312     /* ------------------------------------------------------------ */
313     private HttpURI createUrl(HttpServletRequest request, final int debug) throws MalformedURLException
314     {
315         String uri = request.getRequestURI();
316         
317         if (request.getQueryString() != null)
318         {
319             uri += "?" + request.getQueryString();
320         }
321         
322         uri = PathMap.pathInfo(_pattern,uri);
323         
324         if(uri==null)
325         {
326             uri = "/";
327         }
328         
329         HttpURI url = proxyHttpURI(uri);
330 
331         if (debug != 0)
332         {
333             _log.debug(debug + " proxy " + uri + "-->" + url);
334         }
335         
336         return url;
337     }
338 
339     /* ------------------------------------------------------------ */
340     private boolean createHeaders(final HttpServletRequest request, final int debug, HttpExchange exchange)
341     {
342         // check connection header
343         String connectionHdr = request.getHeader("Connection");
344         if (connectionHdr != null)
345         {
346             connectionHdr = connectionHdr.toLowerCase();
347             if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0)
348             {
349                 connectionHdr = null;
350             }
351         }
352 
353         // force host
354         if (_hostHeader != null)
355         {
356             exchange.setRequestHeader("Host",_hostHeader);
357         }
358 
359         // copy headers
360         boolean xForwardedFor = false;
361         boolean hasContent = false;
362         long contentLength = -1;
363         Enumeration<?> enm = request.getHeaderNames();
364         while (enm.hasMoreElements())
365         {
366             // TODO could be better than this!
367             String hdr = (String)enm.nextElement();
368             String lhdr = hdr.toLowerCase();
369 
370             if (_DontProxyHeaders.contains(lhdr))
371                 continue;
372             if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0)
373                 continue;
374             if (_hostHeader != null && "host".equals(lhdr))
375                 continue;
376 
377             if ("content-type".equals(lhdr))
378                 hasContent = true;
379             else if ("content-length".equals(lhdr))
380             {
381                 contentLength = request.getContentLength();
382                 exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength));
383                 if (contentLength > 0)
384                     hasContent = true;
385             }
386             else if ("x-forwarded-for".equals(lhdr))
387                 xForwardedFor = true;
388 
389             Enumeration<?> vals = request.getHeaders(hdr);
390             while (vals.hasMoreElements())
391             {
392                 String val = (String)vals.nextElement();
393                 if (val != null)
394                 {
395                     if (debug != 0)
396                         _log.debug("{} {} {}",debug,hdr,val);
397 
398                     exchange.setRequestHeader(hdr,val);
399                 }
400             }
401         }
402 
403         // Proxy headers
404         exchange.setRequestHeader("Via","1.1 (jetty)");
405         if (!xForwardedFor)
406         {
407             exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr());
408             exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme());
409             exchange.addRequestHeader("X-Forwarded-Host",request.getServerName());
410             exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName());
411         }
412         return hasContent;
413     }
414 
415     /* ------------------------------------------------------------ */
416     public void setProxyTo(String proxyTo)
417     {
418         this._proxyTo = proxyTo;
419     }
420     
421     /* ------------------------------------------------------------ */
422     public void setMaxThreads(String maxThreads)
423     {
424         this._maxThreads = maxThreads;
425     }
426     
427     /* ------------------------------------------------------------ */
428     public void setMaxConnections(String maxConnections)
429     {
430         _maxConnections = maxConnections;
431     }
432     
433     /* ------------------------------------------------------------ */
434     public void setTimeout(String timeout)
435     {
436         _timeout = timeout;
437     }
438     
439     /* ------------------------------------------------------------ */
440     public void setIdleTimeout(String idleTimeout)
441     {
442         _idleTimeout = idleTimeout;
443     }
444     
445     /* ------------------------------------------------------------ */
446     public void setRequestHeaderSize(String requestHeaderSize)
447     {
448         _requestHeaderSize = requestHeaderSize;
449     }
450     
451     /* ------------------------------------------------------------ */
452     public void setRequestBufferSize(String requestBufferSize)
453     {
454         _requestBufferSize = requestBufferSize;
455     }
456     
457     /* ------------------------------------------------------------ */
458     public void setResponseHeaderSize(String responseHeaderSize)
459     {
460         _responseHeaderSize = responseHeaderSize;
461     }
462     
463     /* ------------------------------------------------------------ */
464     public void setResponseBufferSize(String responseBufferSize)
465     {
466         _responseBufferSize = responseBufferSize;
467     }
468     
469     /* ------------------------------------------------------------ */
470     public void addDontProxyHeaders(String dontProxyHeader)
471     {
472         _DontProxyHeaders.add(dontProxyHeader);
473     }
474     
475     /* ------------------------------------------------------------ */
476     /**
477      *   CONNECTOR_SOCKET = 0;
478      *   CONNECTOR_SELECT_CHANNEL = 2; (default)
479      * 
480      * @param connectorType
481      */
482     public void setConnectorType( int connectorType )
483     {
484         _connectorType = connectorType;
485     }
486     
487 }