View Javadoc

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