1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.proxy;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.net.InetAddress;
24 import java.net.URI;
25 import java.net.UnknownHostException;
26 import java.nio.ByteBuffer;
27 import java.util.Enumeration;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.Locale;
31 import java.util.Set;
32 import java.util.concurrent.Executor;
33 import java.util.concurrent.TimeUnit;
34 import java.util.concurrent.TimeoutException;
35 import javax.servlet.AsyncContext;
36 import javax.servlet.ServletConfig;
37 import javax.servlet.ServletContext;
38 import javax.servlet.ServletException;
39 import javax.servlet.UnavailableException;
40 import javax.servlet.http.HttpServlet;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpServletResponse;
43
44 import org.eclipse.jetty.client.HttpClient;
45 import org.eclipse.jetty.client.api.ContentProvider;
46 import org.eclipse.jetty.client.api.Request;
47 import org.eclipse.jetty.client.api.Response;
48 import org.eclipse.jetty.client.api.Result;
49 import org.eclipse.jetty.client.util.InputStreamContentProvider;
50 import org.eclipse.jetty.http.HttpField;
51 import org.eclipse.jetty.http.HttpHeader;
52 import org.eclipse.jetty.http.HttpHeaderValue;
53 import org.eclipse.jetty.http.HttpVersion;
54 import org.eclipse.jetty.util.Callback;
55 import org.eclipse.jetty.util.HttpCookieStore;
56 import org.eclipse.jetty.util.log.Log;
57 import org.eclipse.jetty.util.log.Logger;
58 import org.eclipse.jetty.util.thread.QueuedThreadPool;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public class ProxyServlet extends HttpServlet
84 {
85 private static final Set<String> HOP_HEADERS = new HashSet<>();
86 static
87 {
88 HOP_HEADERS.add("connection");
89 HOP_HEADERS.add("keep-alive");
90 HOP_HEADERS.add("proxy-authorization");
91 HOP_HEADERS.add("proxy-authenticate");
92 HOP_HEADERS.add("proxy-connection");
93 HOP_HEADERS.add("transfer-encoding");
94 HOP_HEADERS.add("te");
95 HOP_HEADERS.add("trailer");
96 HOP_HEADERS.add("upgrade");
97 }
98
99 private final Set<String> _whiteList = new HashSet<>();
100 private final Set<String> _blackList = new HashSet<>();
101
102 protected Logger _log;
103 private String _hostHeader;
104 private String _viaHost;
105 private HttpClient _client;
106 private long _timeout;
107
108 @Override
109 public void init() throws ServletException
110 {
111 _log = createLogger();
112
113 ServletConfig config = getServletConfig();
114
115 _hostHeader = config.getInitParameter("hostHeader");
116
117 _viaHost = config.getInitParameter("viaHost");
118 if (_viaHost == null)
119 _viaHost = viaHost();
120
121 try
122 {
123 _client = createHttpClient();
124
125
126 getServletContext().setAttribute(config.getServletName() + ".HttpClient", _client);
127
128 String whiteList = config.getInitParameter("whiteList");
129 if (whiteList != null)
130 getWhiteListHosts().addAll(parseList(whiteList));
131
132 String blackList = config.getInitParameter("blackList");
133 if (blackList != null)
134 getBlackListHosts().addAll(parseList(blackList));
135 }
136 catch (Exception e)
137 {
138 throw new ServletException(e);
139 }
140 }
141
142 public String getViaHost()
143 {
144 return _viaHost;
145 }
146
147 public long getTimeout()
148 {
149 return _timeout;
150 }
151
152 public void setTimeout(long timeout)
153 {
154 this._timeout = timeout;
155 }
156
157 public Set<String> getWhiteListHosts()
158 {
159 return _whiteList;
160 }
161
162 public Set<String> getBlackListHosts()
163 {
164 return _blackList;
165 }
166
167 protected static String viaHost()
168 {
169 try
170 {
171 return InetAddress.getLocalHost().getHostName();
172 }
173 catch (UnknownHostException x)
174 {
175 return "localhost";
176 }
177 }
178
179 protected HttpClient getHttpClient()
180 {
181 return _client;
182 }
183
184
185
186
187 protected Logger createLogger()
188 {
189 String servletName = getServletConfig().getServletName();
190 servletName = servletName.replace('-', '.');
191 if ((getClass().getPackage() != null) && !servletName.startsWith(getClass().getPackage().getName()))
192 {
193 servletName = getClass().getName() + "." + servletName;
194 }
195 return Log.getLogger(servletName);
196 }
197
198 public void destroy()
199 {
200 try
201 {
202 _client.stop();
203 }
204 catch (Exception x)
205 {
206 if (_log.isDebugEnabled())
207 _log.debug(x);
208 }
209 }
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 protected HttpClient createHttpClient() throws ServletException
262 {
263 ServletConfig config = getServletConfig();
264
265 HttpClient client = newHttpClient();
266
267
268 client.setFollowRedirects(false);
269
270
271 client.setCookieStore(new HttpCookieStore.Empty());
272
273 Executor executor;
274 String value = config.getInitParameter("maxThreads");
275 if (value == null || "-".equals(value))
276 {
277 executor = (Executor)getServletContext().getAttribute("org.eclipse.jetty.server.Executor");
278 if (executor==null)
279 throw new IllegalStateException("No server executor for proxy");
280 }
281 else
282 {
283 QueuedThreadPool qtp= new QueuedThreadPool(Integer.parseInt(value));
284 String servletName = config.getServletName();
285 int dot = servletName.lastIndexOf('.');
286 if (dot >= 0)
287 servletName = servletName.substring(dot + 1);
288 qtp.setName(servletName);
289 executor=qtp;
290 }
291
292 client.setExecutor(executor);
293
294 value = config.getInitParameter("maxConnections");
295 if (value == null)
296 value = "256";
297 client.setMaxConnectionsPerDestination(Integer.parseInt(value));
298
299 value = config.getInitParameter("idleTimeout");
300 if (value == null)
301 value = "30000";
302 client.setIdleTimeout(Long.parseLong(value));
303
304 value = config.getInitParameter("timeout");
305 if (value == null)
306 value = "60000";
307 _timeout = Long.parseLong(value);
308
309 value = config.getInitParameter("requestBufferSize");
310 if (value != null)
311 client.setRequestBufferSize(Integer.parseInt(value));
312
313 value = config.getInitParameter("responseBufferSize");
314 if (value != null)
315 client.setResponseBufferSize(Integer.parseInt(value));
316
317 try
318 {
319 client.start();
320
321
322 client.getContentDecoderFactories().clear();
323
324 return client;
325 }
326 catch (Exception x)
327 {
328 throw new ServletException(x);
329 }
330 }
331
332
333
334
335 protected HttpClient newHttpClient()
336 {
337 return new HttpClient();
338 }
339
340 private Set<String> parseList(String list)
341 {
342 Set<String> result = new HashSet<>();
343 String[] hosts = list.split(",");
344 for (String host : hosts)
345 {
346 host = host.trim();
347 if (host.length() == 0)
348 continue;
349 result.add(host);
350 }
351 return result;
352 }
353
354
355
356
357
358
359
360
361 public boolean validateDestination(String host, int port)
362 {
363 String hostPort = host + ":" + port;
364 if (!_whiteList.isEmpty())
365 {
366 if (!_whiteList.contains(hostPort))
367 {
368 if (_log.isDebugEnabled())
369 _log.debug("Host {}:{} not whitelisted", host, port);
370 return false;
371 }
372 }
373 if (!_blackList.isEmpty())
374 {
375 if (_blackList.contains(hostPort))
376 {
377 if (_log.isDebugEnabled())
378 _log.debug("Host {}:{} blacklisted", host, port);
379 return false;
380 }
381 }
382 return true;
383 }
384
385 @Override
386 protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
387 {
388 final int requestId = getRequestId(request);
389
390 URI rewrittenURI = rewriteURI(request);
391
392 if (_log.isDebugEnabled())
393 {
394 StringBuffer uri = request.getRequestURL();
395 if (request.getQueryString() != null)
396 uri.append("?").append(request.getQueryString());
397 if (_log.isDebugEnabled())
398 _log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenURI);
399 }
400
401 if (rewrittenURI == null)
402 {
403 onRewriteFailed(request, response);
404 return;
405 }
406
407 final Request proxyRequest = _client.newRequest(rewrittenURI)
408 .method(request.getMethod())
409 .version(HttpVersion.fromString(request.getProtocol()));
410
411
412
413
414
415 Set<String> hopHeaders = null;
416 Enumeration<String> connectionHeaders = request.getHeaders(HttpHeader.CONNECTION.asString());
417 while (connectionHeaders.hasMoreElements())
418 {
419 String value = connectionHeaders.nextElement();
420 String[] values = value.split(",");
421 for (String name : values)
422 {
423 name = name.trim().toLowerCase(Locale.ENGLISH);
424 if (hopHeaders == null)
425 hopHeaders = new HashSet<>();
426 hopHeaders.add(name);
427 }
428 }
429
430 boolean hasContent = request.getContentLength() > 0 || request.getContentType() != null;
431 for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
432 {
433 String headerName = headerNames.nextElement();
434 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
435
436 if (HttpHeader.TRANSFER_ENCODING.is(headerName))
437 hasContent = true;
438
439 if (_hostHeader != null && HttpHeader.HOST.is(headerName))
440 continue;
441
442
443 if (HOP_HEADERS.contains(lowerHeaderName))
444 continue;
445 if (hopHeaders != null && hopHeaders.contains(lowerHeaderName))
446 continue;
447
448 for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
449 {
450 String headerValue = headerValues.nextElement();
451 if (headerValue != null)
452 proxyRequest.header(headerName, headerValue);
453 }
454 }
455
456
457 if (_hostHeader != null)
458 proxyRequest.header(HttpHeader.HOST, _hostHeader);
459
460
461 addViaHeader(proxyRequest);
462 addXForwardedHeaders(proxyRequest, request);
463
464 final AsyncContext asyncContext = request.startAsync();
465
466 asyncContext.setTimeout(0);
467 proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
468
469 if (hasContent)
470 proxyRequest.content(proxyRequestContent(proxyRequest, request));
471
472 customizeProxyRequest(proxyRequest, request);
473
474 if (_log.isDebugEnabled())
475 {
476 StringBuilder builder = new StringBuilder(request.getMethod());
477 builder.append(" ").append(request.getRequestURI());
478 String query = request.getQueryString();
479 if (query != null)
480 builder.append("?").append(query);
481 builder.append(" ").append(request.getProtocol()).append("\r\n");
482 for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
483 {
484 String headerName = headerNames.nextElement();
485 builder.append(headerName).append(": ");
486 for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
487 {
488 String headerValue = headerValues.nextElement();
489 if (headerValue != null)
490 builder.append(headerValue);
491 if (headerValues.hasMoreElements())
492 builder.append(",");
493 }
494 builder.append("\r\n");
495 }
496 builder.append("\r\n");
497
498 _log.debug("{} proxying to upstream:{}{}{}{}",
499 requestId,
500 System.lineSeparator(),
501 builder,
502 proxyRequest,
503 System.lineSeparator(),
504 proxyRequest.getHeaders().toString().trim());
505 }
506
507 proxyRequest.send(newProxyResponseListener(request, response));
508 }
509
510 protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
511 {
512 return new ProxyInputStreamContentProvider(proxyRequest, request, request.getInputStream());
513 }
514
515 protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
516 {
517 return new ProxyResponseListener(request, response);
518 }
519
520 protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
521 {
522 if (_log.isDebugEnabled())
523 _log.debug(getRequestId(request) + " client request failure", failure);
524 proxyRequest.abort(failure);
525 }
526
527 protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
528 {
529 response.sendError(HttpServletResponse.SC_FORBIDDEN);
530 }
531
532 protected Request addViaHeader(Request proxyRequest)
533 {
534 return proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost());
535 }
536
537 protected void addXForwardedHeaders(Request proxyRequest, HttpServletRequest request)
538 {
539 proxyRequest.header(HttpHeader.X_FORWARDED_FOR, request.getRemoteAddr());
540 proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, request.getScheme());
541 proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString()));
542 proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName());
543 }
544
545 protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
546 {
547 for (HttpField field : proxyResponse.getHeaders())
548 {
549 String headerName = field.getName();
550 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
551 if (HOP_HEADERS.contains(lowerHeaderName))
552 continue;
553
554 String newHeaderValue = filterResponseHeader(request, headerName, field.getValue());
555 if (newHeaderValue == null || newHeaderValue.trim().length() == 0)
556 continue;
557
558 response.addHeader(headerName, newHeaderValue);
559 }
560 }
561
562 protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
563 {
564 try
565 {
566 if (_log.isDebugEnabled())
567 _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
568 response.getOutputStream().write(buffer, offset, length);
569 callback.succeeded();
570 }
571 catch (Throwable x)
572 {
573 callback.failed(x);
574 }
575 }
576
577 protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
578 {
579 if (_log.isDebugEnabled())
580 _log.debug("{} proxying successful", getRequestId(request));
581 AsyncContext asyncContext = request.getAsyncContext();
582 asyncContext.complete();
583 }
584
585 protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
586 {
587 if (_log.isDebugEnabled())
588 _log.debug(getRequestId(request) + " proxying failed", failure);
589 if (response.isCommitted())
590 {
591 try
592 {
593
594 response.sendError(-1);
595 AsyncContext asyncContext = request.getAsyncContext();
596 asyncContext.complete();
597 }
598 catch (IOException x)
599 {
600 if (_log.isDebugEnabled())
601 _log.debug(getRequestId(request) + " could not close the connection", failure);
602 }
603 }
604 else
605 {
606 response.resetBuffer();
607 if (failure instanceof TimeoutException)
608 response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
609 else
610 response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
611 response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
612 AsyncContext asyncContext = request.getAsyncContext();
613 asyncContext.complete();
614 }
615 }
616
617 protected int getRequestId(HttpServletRequest request)
618 {
619 return System.identityHashCode(request);
620 }
621
622 protected URI rewriteURI(HttpServletRequest request)
623 {
624 if (!validateDestination(request.getServerName(), request.getServerPort()))
625 return null;
626
627 StringBuffer uri = request.getRequestURL();
628 String query = request.getQueryString();
629 if (query != null)
630 uri.append("?").append(query);
631
632 return URI.create(uri.toString());
633 }
634
635
636
637
638
639
640
641
642 protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
643 {
644 }
645
646
647
648
649
650
651
652
653
654
655
656 protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
657 {
658 return headerValue;
659 }
660
661
662
663
664
665
666
667
668
669
670
671
672 public static class Transparent extends ProxyServlet
673 {
674 private final TransparentDelegate delegate = new TransparentDelegate(this);
675
676 @Override
677 public void init(ServletConfig config) throws ServletException
678 {
679 super.init(config);
680 delegate.init(config);
681 }
682
683 @Override
684 protected URI rewriteURI(HttpServletRequest request)
685 {
686 return delegate.rewriteURI(request);
687 }
688 }
689
690 protected static class TransparentDelegate
691 {
692 private final ProxyServlet proxyServlet;
693 private String _proxyTo;
694 private String _prefix;
695
696 protected TransparentDelegate(ProxyServlet proxyServlet)
697 {
698 this.proxyServlet = proxyServlet;
699 }
700
701 protected void init(ServletConfig config) throws ServletException
702 {
703 _proxyTo = config.getInitParameter("proxyTo");
704 if (_proxyTo == null)
705 throw new UnavailableException("Init parameter 'proxyTo' is required.");
706
707 String prefix = config.getInitParameter("prefix");
708 if (prefix != null)
709 {
710 if (!prefix.startsWith("/"))
711 throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
712 _prefix = prefix;
713 }
714
715
716 String contextPath = config.getServletContext().getContextPath();
717 _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
718
719 if (proxyServlet._log.isDebugEnabled())
720 proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
721 }
722
723 protected URI rewriteURI(HttpServletRequest request)
724 {
725 String path = request.getRequestURI();
726 if (!path.startsWith(_prefix))
727 return null;
728
729 StringBuilder uri = new StringBuilder(_proxyTo);
730 if (_proxyTo.endsWith("/"))
731 uri.setLength(uri.length() - 1);
732 String rest = path.substring(_prefix.length());
733 if (!rest.startsWith("/"))
734 uri.append("/");
735 uri.append(rest);
736 String query = request.getQueryString();
737 if (query != null)
738 uri.append("?").append(query);
739 URI rewrittenURI = URI.create(uri.toString()).normalize();
740
741 if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
742 return null;
743
744 return rewrittenURI;
745 }
746 }
747
748 protected class ProxyResponseListener extends Response.Listener.Adapter
749 {
750 private final HttpServletRequest request;
751 private final HttpServletResponse response;
752
753 protected ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
754 {
755 this.request = request;
756 this.response = response;
757 }
758
759 @Override
760 public void onBegin(Response proxyResponse)
761 {
762 response.setStatus(proxyResponse.getStatus());
763 }
764
765 @Override
766 public void onHeaders(Response proxyResponse)
767 {
768 onResponseHeaders(request, response, proxyResponse);
769
770 if (_log.isDebugEnabled())
771 {
772 StringBuilder builder = new StringBuilder("\r\n");
773 builder.append(request.getProtocol()).append(" ").append(response.getStatus()).append(" ").append(proxyResponse.getReason()).append("\r\n");
774 for (String headerName : response.getHeaderNames())
775 {
776 builder.append(headerName).append(": ");
777 for (Iterator<String> headerValues = response.getHeaders(headerName).iterator(); headerValues.hasNext();)
778 {
779 String headerValue = headerValues.next();
780 if (headerValue != null)
781 builder.append(headerValue);
782 if (headerValues.hasNext())
783 builder.append(",");
784 }
785 builder.append("\r\n");
786 }
787 _log.debug("{} proxying to downstream:{}{}{}{}{}",
788 getRequestId(request),
789 System.lineSeparator(),
790 proxyResponse,
791 System.lineSeparator(),
792 proxyResponse.getHeaders().toString().trim(),
793 System.lineSeparator(),
794 builder);
795 }
796 }
797
798 @Override
799 public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
800 {
801 byte[] buffer;
802 int offset;
803 int length = content.remaining();
804 if (content.hasArray())
805 {
806 buffer = content.array();
807 offset = content.arrayOffset();
808 }
809 else
810 {
811 buffer = new byte[length];
812 content.get(buffer);
813 offset = 0;
814 }
815
816 onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback()
817 {
818 @Override
819 public void succeeded()
820 {
821 callback.succeeded();
822 }
823
824 @Override
825 public void failed(Throwable x)
826 {
827 callback.failed(x);
828 proxyResponse.abort(x);
829 }
830 });
831 }
832
833 @Override
834 public void onComplete(Result result)
835 {
836 if (result.isSucceeded())
837 onResponseSuccess(request, response, result.getResponse());
838 else
839 onResponseFailure(request, response, result.getResponse(), result.getFailure());
840 if (_log.isDebugEnabled())
841 _log.debug("{} proxying complete", getRequestId(request));
842 }
843 }
844
845 protected class ProxyInputStreamContentProvider extends InputStreamContentProvider
846 {
847 private final Request proxyRequest;
848 private final HttpServletRequest request;
849
850 protected ProxyInputStreamContentProvider(Request proxyRequest, HttpServletRequest request, InputStream input)
851 {
852 super(input);
853 this.proxyRequest = proxyRequest;
854 this.request = request;
855 }
856
857 @Override
858 public long getLength()
859 {
860 return request.getContentLength();
861 }
862
863 @Override
864 protected ByteBuffer onRead(byte[] buffer, int offset, int length)
865 {
866 if (_log.isDebugEnabled())
867 _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
868 return onRequestContent(proxyRequest, request, buffer, offset, length);
869 }
870
871 protected ByteBuffer onRequestContent(Request proxyRequest, final HttpServletRequest request, byte[] buffer, int offset, int length)
872 {
873 return super.onRead(buffer, offset, length);
874 }
875
876 @Override
877 protected void onReadFailure(Throwable failure)
878 {
879 onClientRequestFailure(proxyRequest, request, failure);
880 }
881 }
882 }