View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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.proxy;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.net.URI;
24  import java.nio.ByteBuffer;
25  import java.util.concurrent.TimeUnit;
26  import javax.servlet.AsyncContext;
27  import javax.servlet.ServletConfig;
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletException;
30  import javax.servlet.UnavailableException;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.eclipse.jetty.client.HttpClient;
35  import org.eclipse.jetty.client.api.ContentProvider;
36  import org.eclipse.jetty.client.api.Request;
37  import org.eclipse.jetty.client.api.Response;
38  import org.eclipse.jetty.client.api.Result;
39  import org.eclipse.jetty.client.util.InputStreamContentProvider;
40  import org.eclipse.jetty.http.HttpVersion;
41  import org.eclipse.jetty.util.Callback;
42  
43  /**
44   * Asynchronous ProxyServlet.
45   * <p/>
46   * Forwards requests to another server either as a standard web reverse proxy
47   * (as defined by RFC2616) or as a transparent reverse proxy.
48   * <p/>
49   * To facilitate JMX monitoring, the {@link HttpClient} instance is set as context attribute,
50   * prefixed with the servlet's name and exposed by the mechanism provided by
51   * {@link ServletContext#setAttribute(String, Object)}.
52   * <p/>
53   * The following init parameters may be used to configure the servlet:
54   * <ul>
55   * <li>hostHeader - forces the host header to a particular value</li>
56   * <li>viaHost - the name to use in the Via header: Via: http/1.1 &lt;viaHost&gt;</li>
57   * <li>whiteList - comma-separated list of allowed proxy hosts</li>
58   * <li>blackList - comma-separated list of forbidden proxy hosts</li>
59   * </ul>
60   * <p/>
61   * In addition, see {@link #createHttpClient()} for init parameters used to configure
62   * the {@link HttpClient} instance.
63   *
64   * @see ConnectHandler
65   */
66  public class ProxyServlet extends AbstractProxyServlet
67  {
68      @Override
69      protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
70      {
71          final int requestId = getRequestId(request);
72  
73          URI rewrittenURI = rewriteURI(request);
74  
75          if (_log.isDebugEnabled())
76          {
77              StringBuffer uri = request.getRequestURL();
78              if (request.getQueryString() != null)
79                  uri.append("?").append(request.getQueryString());
80              if (_log.isDebugEnabled())
81                  _log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenURI);
82          }
83  
84          if (rewrittenURI == null)
85          {
86              onRewriteFailed(request, response);
87              return;
88          }
89  
90          final Request proxyRequest = getHttpClient().newRequest(rewrittenURI)
91                  .method(request.getMethod())
92                  .version(HttpVersion.fromString(request.getProtocol()));
93  
94          copyHeaders(request, proxyRequest);
95  
96          addProxyHeaders(request, proxyRequest);
97  
98          final AsyncContext asyncContext = request.startAsync();
99          // We do not timeout the continuation, but the proxy request
100         asyncContext.setTimeout(0);
101         proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
102 
103         if (hasContent(request))
104             proxyRequest.content(proxyRequestContent(proxyRequest, request));
105 
106         customizeProxyRequest(proxyRequest, request);
107 
108         sendProxyRequest(request, response, proxyRequest);
109     }
110 
111     /**
112      * @deprecated use {@link #copyRequestHeaders(HttpServletRequest, Request)} instead
113      */
114     @Deprecated
115     protected void copyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
116     {
117         copyRequestHeaders(clientRequest, proxyRequest);
118     }
119 
120     protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
121     {
122         return new ProxyInputStreamContentProvider(proxyRequest, request, request.getInputStream());
123     }
124 
125     protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
126     {
127         return new ProxyResponseListener(request, response);
128     }
129 
130     protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
131     {
132         if (_log.isDebugEnabled())
133             _log.debug(getRequestId(request) + " client request failure", failure);
134         proxyRequest.abort(failure);
135     }
136 
137     /**
138      * @deprecated use {@link #onProxyRewriteFailed(HttpServletRequest, HttpServletResponse)}
139      */
140     @Deprecated
141     protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
142     {
143         onProxyRewriteFailed(request, response);
144     }
145 
146     /**
147      * @deprecated use {@link #onServerResponseHeaders(HttpServletRequest, HttpServletResponse, Response)}
148      */
149     @Deprecated
150     protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
151     {
152         onServerResponseHeaders(request, response, proxyResponse);
153     }
154 
155     // TODO: remove in Jetty 9.3, only here for backward compatibility.
156     @Override
157     protected String filterServerResponseHeader(HttpServletRequest clientRequest, Response serverResponse, String headerName, String headerValue)
158     {
159         return filterResponseHeader(clientRequest, headerName, headerValue);
160     }
161 
162     protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
163     {
164         try
165         {
166             if (_log.isDebugEnabled())
167                 _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
168             response.getOutputStream().write(buffer, offset, length);
169             callback.succeeded();
170         }
171         catch (Throwable x)
172         {
173             callback.failed(x);
174         }
175     }
176 
177     /**
178      * @deprecated Use {@link #onProxyResponseSuccess(HttpServletRequest, HttpServletResponse, Response)}
179      */
180     @Deprecated
181     protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
182     {
183         onProxyResponseSuccess(request, response, proxyResponse);
184     }
185 
186     /**
187      * @deprecated Use {@link #onProxyResponseFailure(HttpServletRequest, HttpServletResponse, Response, Throwable)}
188      */
189     @Deprecated
190     protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
191     {
192         onProxyResponseFailure(request, response, proxyResponse, failure);
193     }
194 
195     /**
196      * @deprecated use {@link #rewriteTarget(HttpServletRequest)}
197      */
198     @Deprecated
199     protected URI rewriteURI(HttpServletRequest request)
200     {
201         String newTarget = rewriteTarget(request);
202         return newTarget == null ? null : URI.create(newTarget);
203     }
204 
205     /**
206      * @deprecated use {@link #sendProxyRequest(HttpServletRequest, HttpServletResponse, Request)}
207      */
208     @Deprecated
209     protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
210     {
211     }
212 
213     /**
214      * Extension point for remote server response header filtering.
215      * The default implementation returns the header value as is.
216      * If null is returned, this header won't be forwarded back to the client.
217      *
218      * @param headerName the header name
219      * @param headerValue the header value
220      * @param request the request to proxy
221      * @return filteredHeaderValue the new header value
222      */
223     protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
224     {
225         return headerValue;
226     }
227 
228     /**
229      * This convenience extension to {@link ProxyServlet} configures the servlet as a transparent proxy.
230      * This servlet is configured with the following init parameters:
231      * <ul>
232      * <li>proxyTo - a mandatory URI like http://host:80/context to which the request is proxied.</li>
233      * <li>prefix - an optional URI prefix that is stripped from the start of the forwarded URI.</li>
234      * </ul>
235      * <p/>
236      * For example, if a request is received at "/foo/bar", the 'proxyTo' parameter is "http://host:80/context"
237      * and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar".
238      */
239     public static class Transparent extends ProxyServlet
240     {
241         private final TransparentDelegate delegate = new TransparentDelegate(this);
242 
243         @Override
244         public void init(ServletConfig config) throws ServletException
245         {
246             super.init(config);
247             delegate.init(config);
248         }
249 
250         @Override
251         protected URI rewriteURI(HttpServletRequest request)
252         {
253             return delegate.rewriteURI(request);
254         }
255     }
256 
257     protected static class TransparentDelegate
258     {
259         private final ProxyServlet proxyServlet;
260         private String _proxyTo;
261         private String _prefix;
262 
263         protected TransparentDelegate(ProxyServlet proxyServlet)
264         {
265             this.proxyServlet = proxyServlet;
266         }
267 
268         protected void init(ServletConfig config) throws ServletException
269         {
270             _proxyTo = config.getInitParameter("proxyTo");
271             if (_proxyTo == null)
272                 throw new UnavailableException("Init parameter 'proxyTo' is required.");
273 
274             String prefix = config.getInitParameter("prefix");
275             if (prefix != null)
276             {
277                 if (!prefix.startsWith("/"))
278                     throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
279                 _prefix = prefix;
280             }
281 
282             // Adjust prefix value to account for context path
283             String contextPath = config.getServletContext().getContextPath();
284             _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
285 
286             if (proxyServlet._log.isDebugEnabled())
287                 proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
288         }
289 
290         protected URI rewriteURI(HttpServletRequest request)
291         {
292             String path = request.getRequestURI();
293             if (!path.startsWith(_prefix))
294                 return null;
295 
296             StringBuilder uri = new StringBuilder(_proxyTo);
297             if (_proxyTo.endsWith("/"))
298                 uri.setLength(uri.length() - 1);
299             String rest = path.substring(_prefix.length());
300             if (!rest.startsWith("/"))
301                 uri.append("/");
302             uri.append(rest);
303             String query = request.getQueryString();
304             if (query != null)
305                 uri.append("?").append(query);
306             URI rewrittenURI = URI.create(uri.toString()).normalize();
307 
308             if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
309                 return null;
310 
311             return rewrittenURI;
312         }
313     }
314 
315     protected class ProxyResponseListener extends Response.Listener.Adapter
316     {
317         private final HttpServletRequest request;
318         private final HttpServletResponse response;
319 
320         protected ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
321         {
322             this.request = request;
323             this.response = response;
324         }
325 
326         @Override
327         public void onBegin(Response proxyResponse)
328         {
329             response.setStatus(proxyResponse.getStatus());
330         }
331 
332         @Override
333         public void onHeaders(Response proxyResponse)
334         {
335             onResponseHeaders(request, response, proxyResponse);
336         }
337 
338         @Override
339         public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
340         {
341             byte[] buffer;
342             int offset;
343             int length = content.remaining();
344             if (content.hasArray())
345             {
346                 buffer = content.array();
347                 offset = content.arrayOffset();
348             }
349             else
350             {
351                 buffer = new byte[length];
352                 content.get(buffer);
353                 offset = 0;
354             }
355 
356             onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback()
357             {
358                 @Override
359                 public void succeeded()
360                 {
361                     callback.succeeded();
362                 }
363 
364                 @Override
365                 public void failed(Throwable x)
366                 {
367                     callback.failed(x);
368                     proxyResponse.abort(x);
369                 }
370             });
371         }
372 
373         @Override
374         public void onComplete(Result result)
375         {
376             if (result.isSucceeded())
377                 onResponseSuccess(request, response, result.getResponse());
378             else
379                 onResponseFailure(request, response, result.getResponse(), result.getFailure());
380             if (_log.isDebugEnabled())
381                 _log.debug("{} proxying complete", getRequestId(request));
382         }
383     }
384 
385     protected class ProxyInputStreamContentProvider extends InputStreamContentProvider
386     {
387         private final Request proxyRequest;
388         private final HttpServletRequest request;
389 
390         protected ProxyInputStreamContentProvider(Request proxyRequest, HttpServletRequest request, InputStream input)
391         {
392             super(input);
393             this.proxyRequest = proxyRequest;
394             this.request = request;
395         }
396 
397         @Override
398         public long getLength()
399         {
400             return request.getContentLength();
401         }
402 
403         @Override
404         protected ByteBuffer onRead(byte[] buffer, int offset, int length)
405         {
406             if (_log.isDebugEnabled())
407                 _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
408             return onRequestContent(proxyRequest, request, buffer, offset, length);
409         }
410 
411         protected ByteBuffer onRequestContent(Request proxyRequest, final HttpServletRequest request, byte[] buffer, int offset, int length)
412         {
413             return super.onRead(buffer, offset, length);
414         }
415 
416         @Override
417         protected void onReadFailure(Throwable failure)
418         {
419             onClientRequestFailure(proxyRequest, request, failure);
420         }
421     }
422 }