View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.net.InetAddress;
23  import java.net.URI;
24  import java.net.UnknownHostException;
25  import java.nio.ByteBuffer;
26  import java.util.Enumeration;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.Locale;
30  import java.util.Set;
31  import java.util.concurrent.Executor;
32  import java.util.concurrent.TimeUnit;
33  import java.util.concurrent.TimeoutException;
34  import javax.servlet.AsyncContext;
35  import javax.servlet.ServletConfig;
36  import javax.servlet.ServletContext;
37  import javax.servlet.ServletException;
38  import javax.servlet.UnavailableException;
39  import javax.servlet.http.HttpServlet;
40  import javax.servlet.http.HttpServletRequest;
41  import javax.servlet.http.HttpServletResponse;
42  
43  import org.eclipse.jetty.client.HttpClient;
44  import org.eclipse.jetty.client.api.Request;
45  import org.eclipse.jetty.client.api.Response;
46  import org.eclipse.jetty.client.api.Result;
47  import org.eclipse.jetty.client.util.InputStreamContentProvider;
48  import org.eclipse.jetty.http.HttpField;
49  import org.eclipse.jetty.http.HttpHeader;
50  import org.eclipse.jetty.http.HttpMethod;
51  import org.eclipse.jetty.http.HttpVersion;
52  import org.eclipse.jetty.util.HttpCookieStore;
53  import org.eclipse.jetty.util.log.Log;
54  import org.eclipse.jetty.util.log.Logger;
55  import org.eclipse.jetty.util.thread.QueuedThreadPool;
56  
57  /**
58   * Asynchronous ProxyServlet.
59   * <p/>
60   * Forwards requests to another server either as a standard web reverse proxy
61   * (as defined by RFC2616) or as a transparent reverse proxy.
62   * <p/>
63   * To facilitate JMX monitoring, the {@link HttpClient} instance is set as context attribute,
64   * prefixed with the servlet's name and exposed by the mechanism provided by
65   * {@link ServletContext#setAttribute(String, Object)}.
66   * <p/>
67   * The following init parameters may be used to configure the servlet:
68   * <ul>
69   * <li>hostHeader - forces the host header to a particular value</li>
70   * <li>viaHost - the name to use in the Via header: Via: http/1.1 &lt;viaHost&gt;</li>
71   * <li>whiteList - comma-separated list of allowed proxy hosts</li>
72   * <li>blackList - comma-separated list of forbidden proxy hosts</li>
73   * </ul>
74   * <p/>
75   * In addition, see {@link #createHttpClient()} for init parameters used to configure
76   * the {@link HttpClient} instance.
77   *
78   * @see ConnectHandler
79   */
80  public class ProxyServlet extends HttpServlet
81  {
82      protected static final String ASYNC_CONTEXT = ProxyServlet.class.getName() + ".asyncContext";
83      private static final Set<String> HOP_HEADERS = new HashSet<>();
84      static
85      {
86          HOP_HEADERS.add("proxy-connection");
87          HOP_HEADERS.add("connection");
88          HOP_HEADERS.add("keep-alive");
89          HOP_HEADERS.add("transfer-encoding");
90          HOP_HEADERS.add("te");
91          HOP_HEADERS.add("trailer");
92          HOP_HEADERS.add("proxy-authorization");
93          HOP_HEADERS.add("proxy-authenticate");
94          HOP_HEADERS.add("upgrade");
95      }
96  
97      private final Set<String> _whiteList = new HashSet<>();
98      private final Set<String> _blackList = new HashSet<>();
99  
100     protected Logger _log;
101     private String _hostHeader;
102     private String _viaHost;
103     private HttpClient _client;
104     private long _timeout;
105 
106     @Override
107     public void init() throws ServletException
108     {
109         _log = createLogger();
110 
111         ServletConfig config = getServletConfig();
112 
113         _hostHeader = config.getInitParameter("hostHeader");
114 
115         _viaHost = config.getInitParameter("viaHost");
116         if (_viaHost == null)
117             _viaHost = viaHost();
118 
119         try
120         {
121             _client = createHttpClient();
122 
123             // Put the HttpClient in the context to leverage ContextHandler.MANAGED_ATTRIBUTES
124             getServletContext().setAttribute(config.getServletName() + ".HttpClient", _client);
125 
126             String whiteList = config.getInitParameter("whiteList");
127             if (whiteList != null)
128                 getWhiteListHosts().addAll(parseList(whiteList));
129 
130             String blackList = config.getInitParameter("blackList");
131             if (blackList != null)
132                 getBlackListHosts().addAll(parseList(blackList));
133         }
134         catch (Exception e)
135         {
136             throw new ServletException(e);
137         }
138     }
139 
140     public String getViaHost()
141     {
142         return _viaHost;
143     }
144 
145     public long getTimeout()
146     {
147         return _timeout;
148     }
149 
150     public void setTimeout(long timeout)
151     {
152         this._timeout = timeout;
153     }
154 
155     public Set<String> getWhiteListHosts()
156     {
157         return _whiteList;
158     }
159 
160     public Set<String> getBlackListHosts()
161     {
162         return _blackList;
163     }
164 
165     protected static String viaHost()
166     {
167         try
168         {
169             return InetAddress.getLocalHost().getHostName();
170         }
171         catch (UnknownHostException x)
172         {
173             return "localhost";
174         }
175     }
176 
177     /**
178      * @return a logger instance with a name derived from this servlet's name.
179      */
180     protected Logger createLogger()
181     {
182         String name = getServletConfig().getServletName();
183         name = name.replace('-', '.');
184         return Log.getLogger(name);
185     }
186 
187     public void destroy()
188     {
189         try
190         {
191             _client.stop();
192         }
193         catch (Exception x)
194         {
195             _log.debug(x);
196         }
197     }
198 
199     /**
200      * Creates a {@link HttpClient} instance, configured with init parameters of this servlet.
201      * <p/>
202      * The init parameters used to configure the {@link HttpClient} instance are:
203      * <table>
204      * <thead>
205      * <tr>
206      * <th>init-param</th>
207      * <th>default</th>
208      * <th>description</th>
209      * </tr>
210      * </thead>
211      * <tbody>
212      * <tr>
213      * <td>maxThreads</td>
214      * <td>256</td>
215      * <td>The max number of threads of HttpClient's Executor.  If not set, or set to the value of "-", then the
216      * Jetty server thread pool will be used.</td>
217      * </tr>
218      * <tr>
219      * <td>maxConnections</td>
220      * <td>32768</td>
221      * <td>The max number of connections per destination, see {@link HttpClient#setMaxConnectionsPerDestination(int)}</td>
222      * </tr>
223      * <tr>
224      * <td>idleTimeout</td>
225      * <td>30000</td>
226      * <td>The idle timeout in milliseconds, see {@link HttpClient#setIdleTimeout(long)}</td>
227      * </tr>
228      * <tr>
229      * <td>timeout</td>
230      * <td>60000</td>
231      * <td>The total timeout in milliseconds, see {@link Request#timeout(long, TimeUnit)}</td>
232      * </tr>
233      * <tr>
234      * <td>requestBufferSize</td>
235      * <td>HttpClient's default</td>
236      * <td>The request buffer size, see {@link HttpClient#setRequestBufferSize(int)}</td>
237      * </tr>
238      * <tr>
239      * <td>responseBufferSize</td>
240      * <td>HttpClient's default</td>
241      * <td>The response buffer size, see {@link HttpClient#setResponseBufferSize(int)}</td>
242      * </tr>
243      * </tbody>
244      * </table>
245      *
246      * @return a {@link HttpClient} configured from the {@link #getServletConfig() servlet configuration}
247      * @throws ServletException if the {@link HttpClient} cannot be created
248      */
249     protected HttpClient createHttpClient() throws ServletException
250     {
251         ServletConfig config = getServletConfig();
252 
253         HttpClient client = newHttpClient();
254         
255         // Redirects must be proxied as is, not followed
256         client.setFollowRedirects(false);
257 
258         // Must not store cookies, otherwise cookies of different clients will mix
259         client.setCookieStore(new HttpCookieStore.Empty());
260 
261         Executor executor;
262         String value = config.getInitParameter("maxThreads");
263         if (value == null || "-".equals(value))
264         {
265             executor = (Executor)getServletContext().getAttribute("org.eclipse.jetty.server.Executor");
266             if (executor==null)
267                 throw new IllegalStateException("No server executor for proxy");
268         }
269         else
270         {
271             QueuedThreadPool qtp= new QueuedThreadPool(Integer.parseInt(value));
272             String servletName = config.getServletName();
273             int dot = servletName.lastIndexOf('.');
274             if (dot >= 0)
275                 servletName = servletName.substring(dot + 1);
276             qtp.setName(servletName);
277             executor=qtp;
278         }
279         
280         client.setExecutor(executor);
281 
282         value = config.getInitParameter("maxConnections");
283         if (value == null)
284             value = "256";
285         client.setMaxConnectionsPerDestination(Integer.parseInt(value));
286 
287         value = config.getInitParameter("idleTimeout");
288         if (value == null)
289             value = "30000";
290         client.setIdleTimeout(Long.parseLong(value));
291 
292         value = config.getInitParameter("timeout");
293         if (value == null)
294             value = "60000";
295         _timeout = Long.parseLong(value);
296 
297         value = config.getInitParameter("requestBufferSize");
298         if (value != null)
299             client.setRequestBufferSize(Integer.parseInt(value));
300 
301         value = config.getInitParameter("responseBufferSize");
302         if (value != null)
303             client.setResponseBufferSize(Integer.parseInt(value));
304 
305         try
306         {
307             client.start();
308 
309             // Content must not be decoded, otherwise the client gets confused
310             client.getContentDecoderFactories().clear();
311 
312             return client;
313         }
314         catch (Exception x)
315         {
316             throw new ServletException(x);
317         }
318     }
319 
320     /**
321      * @return a new HttpClient instance
322      */
323     protected HttpClient newHttpClient()
324     {
325         return new HttpClient();
326     }
327 
328     private Set<String> parseList(String list)
329     {
330         Set<String> result = new HashSet<>();
331         String[] hosts = list.split(",");
332         for (String host : hosts)
333         {
334             host = host.trim();
335             if (host.length() == 0)
336                 continue;
337             result.add(host);
338         }
339         return result;
340     }
341 
342     /**
343      * Checks the given {@code host} and {@code port} against whitelist and blacklist.
344      *
345      * @param host the host to check
346      * @param port the port to check
347      * @return true if it is allowed to be proxy to the given host and port
348      */
349     public boolean validateDestination(String host, int port)
350     {
351         String hostPort = host + ":" + port;
352         if (!_whiteList.isEmpty())
353         {
354             if (!_whiteList.contains(hostPort))
355             {
356                 _log.debug("Host {}:{} not whitelisted", host, port);
357                 return false;
358             }
359         }
360         if (!_blackList.isEmpty())
361         {
362             if (_blackList.contains(hostPort))
363             {
364                 _log.debug("Host {}:{} blacklisted", host, port);
365                 return false;
366             }
367         }
368         return true;
369     }
370 
371     @Override
372     protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
373     {
374         final int requestId = getRequestId(request);
375 
376         URI rewrittenURI = rewriteURI(request);
377 
378         if (_log.isDebugEnabled())
379         {
380             StringBuffer uri = request.getRequestURL();
381             if (request.getQueryString() != null)
382                 uri.append("?").append(request.getQueryString());
383             _log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenURI);
384         }
385 
386         if (rewrittenURI == null)
387         {
388             onRewriteFailed(request, response);
389             return;
390         }
391 
392         final Request proxyRequest = _client.newRequest(rewrittenURI)
393                 .method(HttpMethod.fromString(request.getMethod()))
394                 .version(HttpVersion.fromString(request.getProtocol()));
395 
396         // Copy headers
397         boolean hasContent = false;
398         for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
399         {
400             String headerName = headerNames.nextElement();
401             String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
402 
403             // Remove hop-by-hop headers
404             if (HOP_HEADERS.contains(lowerHeaderName))
405                 continue;
406 
407             if (_hostHeader != null && HttpHeader.HOST.is(headerName))
408                 continue;
409 
410             if (request.getContentLength() > 0 || request.getContentType() != null ||
411                     HttpHeader.TRANSFER_ENCODING.is(headerName))
412                 hasContent = true;
413 
414             for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
415             {
416                 String headerValue = headerValues.nextElement();
417                 if (headerValue != null)
418                     proxyRequest.header(headerName, headerValue);
419             }
420         }
421 
422         // Force the Host header if configured
423         if (_hostHeader != null)
424             proxyRequest.header(HttpHeader.HOST, _hostHeader);
425 
426         // Add proxy headers
427         addViaHeader(proxyRequest);
428         addXForwardedHeaders(proxyRequest, request);
429 
430         if (hasContent)
431         {
432             proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
433             {
434                 @Override
435                 public long getLength()
436                 {
437                     return request.getContentLength();
438                 }
439 
440                 @Override
441                 protected ByteBuffer onRead(byte[] buffer, int offset, int length)
442                 {
443                     _log.debug("{} proxying content to upstream: {} bytes", requestId, length);
444                     return super.onRead(buffer, offset, length);
445                 }
446             });
447         }
448 
449         final AsyncContext asyncContext = request.startAsync();
450         // We do not timeout the continuation, but the proxy request
451         asyncContext.setTimeout(0);
452         request.setAttribute(ASYNC_CONTEXT, asyncContext);
453 
454         customizeProxyRequest(proxyRequest, request);
455 
456         if (_log.isDebugEnabled())
457         {
458             StringBuilder builder = new StringBuilder(request.getMethod());
459             builder.append(" ").append(request.getRequestURI());
460             String query = request.getQueryString();
461             if (query != null)
462                 builder.append("?").append(query);
463             builder.append(" ").append(request.getProtocol()).append("\r\n");
464             for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
465             {
466                 String headerName = headerNames.nextElement();
467                 builder.append(headerName).append(": ");
468                 for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
469                 {
470                     String headerValue = headerValues.nextElement();
471                     if (headerValue != null)
472                         builder.append(headerValue);
473                     if (headerValues.hasMoreElements())
474                         builder.append(",");
475                 }
476                 builder.append("\r\n");
477             }
478             builder.append("\r\n");
479 
480             _log.debug("{} proxying to upstream:{}{}{}{}",
481                     requestId,
482                     System.lineSeparator(),
483                     builder,
484                     proxyRequest,
485                     System.lineSeparator(),
486                     proxyRequest.getHeaders().toString().trim());
487         }
488 
489         proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
490         proxyRequest.send(new ProxyResponseListener(request, response));
491     }
492 
493     protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
494     {
495         response.sendError(HttpServletResponse.SC_FORBIDDEN);
496     }
497 
498     protected Request addViaHeader(Request proxyRequest)
499     {
500         return proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost());
501     }
502 
503     protected void addXForwardedHeaders(Request proxyRequest, HttpServletRequest request)
504     {
505         proxyRequest.header(HttpHeader.X_FORWARDED_FOR, request.getRemoteAddr());
506         proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, request.getScheme());
507         proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString()));
508         proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName());
509     }
510 
511     protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
512     {
513         for (HttpField field : proxyResponse.getHeaders())
514         {
515             String headerName = field.getName();
516             String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
517             if (HOP_HEADERS.contains(lowerHeaderName))
518                 continue;
519 
520             String newHeaderValue = filterResponseHeader(request, headerName, field.getValue());
521             if (newHeaderValue == null || newHeaderValue.trim().length() == 0)
522                 continue;
523 
524             response.addHeader(headerName, newHeaderValue);
525         }
526     }
527 
528     protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException
529     {
530         response.getOutputStream().write(buffer, offset, length);
531         _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
532     }
533 
534     protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
535     {
536         AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
537         asyncContext.complete();
538         _log.debug("{} proxying successful", getRequestId(request));
539     }
540 
541     protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
542     {
543         _log.debug(getRequestId(request) + " proxying failed", failure);
544         if (!response.isCommitted())
545         {
546             if (failure instanceof TimeoutException)
547                 response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
548             else
549                 response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
550         }
551         AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
552         asyncContext.complete();
553     }
554 
555     protected int getRequestId(HttpServletRequest request)
556     {
557         return System.identityHashCode(request);
558     }
559 
560     protected URI rewriteURI(HttpServletRequest request)
561     {
562         if (!validateDestination(request.getServerName(), request.getServerPort()))
563             return null;
564 
565         StringBuffer uri = request.getRequestURL();
566         String query = request.getQueryString();
567         if (query != null)
568             uri.append("?").append(query);
569 
570         return URI.create(uri.toString());
571     }
572 
573     /**
574      * Extension point for subclasses to customize the proxy request.
575      * The default implementation does nothing.
576      *
577      * @param proxyRequest the proxy request to customize
578      * @param request the request to be proxied
579      */
580     protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
581     {
582     }
583 
584     /**
585      * Extension point for remote server response header filtering.
586      * The default implementation returns the header value as is.
587      * If null is returned, this header won't be forwarded back to the client.
588      *
589      * @param headerName the header name
590      * @param headerValue the header value
591      * @param request the request to proxy
592      * @return filteredHeaderValue the new header value
593      */
594     protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
595     {
596         return headerValue;
597     }
598 
599     /**
600      * Transparent Proxy.
601      * <p/>
602      * This convenience extension to ProxyServlet configures the servlet as a transparent proxy.
603      * The servlet is configured with init parameters:
604      * <ul>
605      * <li>proxyTo - a URI like http://host:80/context to which the request is proxied.
606      * <li>prefix - a URI prefix that is striped from the start of the forwarded URI.
607      * </ul>
608      * For example, if a request is received at /foo/bar and the 'proxyTo' parameter is "http://host:80/context"
609      * and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar".
610      */
611     public static class Transparent extends ProxyServlet
612     {
613         private String _proxyTo;
614         private String _prefix;
615 
616         public Transparent()
617         {
618         }
619 
620         public Transparent(String proxyTo, String prefix)
621         {
622             _proxyTo = URI.create(proxyTo).normalize().toString();
623             _prefix = URI.create(prefix).normalize().toString();
624         }
625 
626         @Override
627         public void init() throws ServletException
628         {
629             super.init();
630 
631             ServletConfig config = getServletConfig();
632 
633             String prefix = config.getInitParameter("prefix");
634             _prefix = prefix == null ? _prefix : prefix;
635 
636             // Adjust prefix value to account for context path
637             String contextPath = getServletContext().getContextPath();
638             _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
639 
640             String proxyTo = config.getInitParameter("proxyTo");
641             _proxyTo = proxyTo == null ? _proxyTo : proxyTo;
642 
643             if (_proxyTo == null)
644                 throw new UnavailableException("Init parameter 'proxyTo' is required.");
645 
646             if (!_prefix.startsWith("/"))
647                 throw new UnavailableException("Init parameter 'prefix' parameter must start with a '/'.");
648 
649             _log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
650         }
651 
652         @Override
653         protected URI rewriteURI(HttpServletRequest request)
654         {
655             String path = request.getRequestURI();
656             if (!path.startsWith(_prefix))
657                 return null;
658 
659             StringBuilder uri = new StringBuilder(_proxyTo);
660             if (_proxyTo.endsWith("/"))
661                 uri.setLength(uri.length() - 1);
662             String rest = path.substring(_prefix.length());
663             if (!rest.startsWith("/"))
664                 uri.append("/");
665             uri.append(rest);
666             String query = request.getQueryString();
667             if (query != null)
668                 uri.append("?").append(query);
669             URI rewrittenURI = URI.create(uri.toString()).normalize();
670 
671             if (!validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
672                 return null;
673 
674             return rewrittenURI;
675         }
676     }
677 
678     private class ProxyResponseListener extends Response.Listener.Adapter
679     {
680         private final HttpServletRequest request;
681         private final HttpServletResponse response;
682 
683         public ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
684         {
685             this.request = request;
686             this.response = response;
687         }
688 
689         @Override
690         public void onBegin(Response proxyResponse)
691         {
692             response.setStatus(proxyResponse.getStatus());
693         }
694 
695         @Override
696         public void onHeaders(Response proxyResponse)
697         {
698             onResponseHeaders(request, response, proxyResponse);
699 
700             if (_log.isDebugEnabled())
701             {
702                 StringBuilder builder = new StringBuilder("\r\n");
703                 builder.append(request.getProtocol()).append(" ").append(response.getStatus()).append(" ").append(proxyResponse.getReason()).append("\r\n");
704                 for (String headerName : response.getHeaderNames())
705                 {
706                     builder.append(headerName).append(": ");
707                     for (Iterator<String> headerValues = response.getHeaders(headerName).iterator(); headerValues.hasNext();)
708                     {
709                         String headerValue = headerValues.next();
710                         if (headerValue != null)
711                             builder.append(headerValue);
712                         if (headerValues.hasNext())
713                             builder.append(",");
714                     }
715                     builder.append("\r\n");
716                 }
717                 _log.debug("{} proxying to downstream:{}{}{}{}{}",
718                         getRequestId(request),
719                         System.lineSeparator(),
720                         proxyResponse,
721                         System.lineSeparator(),
722                         proxyResponse.getHeaders().toString().trim(),
723                         System.lineSeparator(),
724                         builder);
725             }
726         }
727 
728         @Override
729         public void onContent(Response proxyResponse, ByteBuffer content)
730         {
731             byte[] buffer;
732             int offset;
733             int length = content.remaining();
734             if (content.hasArray())
735             {
736                 buffer = content.array();
737                 offset = content.arrayOffset();
738             }
739             else
740             {
741                 buffer = new byte[length];
742                 content.get(buffer);
743                 offset = 0;
744             }
745 
746             try
747             {
748                 onResponseContent(request, response, proxyResponse, buffer, offset, length);
749             }
750             catch (IOException x)
751             {
752                 proxyResponse.abort(x);
753             }
754         }
755 
756         @Override
757         public void onSuccess(Response proxyResponse)
758         {
759             onResponseSuccess(request, response, proxyResponse);
760         }
761 
762         @Override
763         public void onFailure(Response proxyResponse, Throwable failure)
764         {
765             onResponseFailure(request, response, proxyResponse, failure);
766         }
767 
768         @Override
769         public void onComplete(Result result)
770         {
771             _log.debug("{} proxying complete", getRequestId(request));
772         }
773     }
774 }