View Javadoc

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