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("proxy-connection");
89 HOP_HEADERS.add("connection");
90 HOP_HEADERS.add("keep-alive");
91 HOP_HEADERS.add("transfer-encoding");
92 HOP_HEADERS.add("te");
93 HOP_HEADERS.add("trailer");
94 HOP_HEADERS.add("proxy-authorization");
95 HOP_HEADERS.add("proxy-authenticate");
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 (!servletName.startsWith(getClass().getPackage().getName()))
192 servletName = getClass().getName() + "." + servletName;
193 return Log.getLogger(servletName);
194 }
195
196 public void destroy()
197 {
198 try
199 {
200 _client.stop();
201 }
202 catch (Exception x)
203 {
204 if (_log.isDebugEnabled())
205 _log.debug(x);
206 }
207 }
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 protected HttpClient createHttpClient() throws ServletException
260 {
261 ServletConfig config = getServletConfig();
262
263 HttpClient client = newHttpClient();
264
265
266 client.setFollowRedirects(false);
267
268
269 client.setCookieStore(new HttpCookieStore.Empty());
270
271 Executor executor;
272 String value = config.getInitParameter("maxThreads");
273 if (value == null || "-".equals(value))
274 {
275 executor = (Executor)getServletContext().getAttribute("org.eclipse.jetty.server.Executor");
276 if (executor==null)
277 throw new IllegalStateException("No server executor for proxy");
278 }
279 else
280 {
281 QueuedThreadPool qtp= new QueuedThreadPool(Integer.parseInt(value));
282 String servletName = config.getServletName();
283 int dot = servletName.lastIndexOf('.');
284 if (dot >= 0)
285 servletName = servletName.substring(dot + 1);
286 qtp.setName(servletName);
287 executor=qtp;
288 }
289
290 client.setExecutor(executor);
291
292 value = config.getInitParameter("maxConnections");
293 if (value == null)
294 value = "256";
295 client.setMaxConnectionsPerDestination(Integer.parseInt(value));
296
297 value = config.getInitParameter("idleTimeout");
298 if (value == null)
299 value = "30000";
300 client.setIdleTimeout(Long.parseLong(value));
301
302 value = config.getInitParameter("timeout");
303 if (value == null)
304 value = "60000";
305 _timeout = Long.parseLong(value);
306
307 value = config.getInitParameter("requestBufferSize");
308 if (value != null)
309 client.setRequestBufferSize(Integer.parseInt(value));
310
311 value = config.getInitParameter("responseBufferSize");
312 if (value != null)
313 client.setResponseBufferSize(Integer.parseInt(value));
314
315 try
316 {
317 client.start();
318
319
320 client.getContentDecoderFactories().clear();
321
322 return client;
323 }
324 catch (Exception x)
325 {
326 throw new ServletException(x);
327 }
328 }
329
330
331
332
333 protected HttpClient newHttpClient()
334 {
335 return new HttpClient();
336 }
337
338 private Set<String> parseList(String list)
339 {
340 Set<String> result = new HashSet<>();
341 String[] hosts = list.split(",");
342 for (String host : hosts)
343 {
344 host = host.trim();
345 if (host.length() == 0)
346 continue;
347 result.add(host);
348 }
349 return result;
350 }
351
352
353
354
355
356
357
358
359 public boolean validateDestination(String host, int port)
360 {
361 String hostPort = host + ":" + port;
362 if (!_whiteList.isEmpty())
363 {
364 if (!_whiteList.contains(hostPort))
365 {
366 if (_log.isDebugEnabled())
367 _log.debug("Host {}:{} not whitelisted", host, port);
368 return false;
369 }
370 }
371 if (!_blackList.isEmpty())
372 {
373 if (_blackList.contains(hostPort))
374 {
375 if (_log.isDebugEnabled())
376 _log.debug("Host {}:{} blacklisted", host, port);
377 return false;
378 }
379 }
380 return true;
381 }
382
383 @Override
384 protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
385 {
386 final int requestId = getRequestId(request);
387
388 URI rewrittenURI = rewriteURI(request);
389
390 if (_log.isDebugEnabled())
391 {
392 StringBuffer uri = request.getRequestURL();
393 if (request.getQueryString() != null)
394 uri.append("?").append(request.getQueryString());
395 if (_log.isDebugEnabled())
396 _log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenURI);
397 }
398
399 if (rewrittenURI == null)
400 {
401 onRewriteFailed(request, response);
402 return;
403 }
404
405 final Request proxyRequest = _client.newRequest(rewrittenURI)
406 .method(request.getMethod())
407 .version(HttpVersion.fromString(request.getProtocol()));
408
409
410 boolean hasContent = request.getContentLength() > 0 || request.getContentType() != null;
411 for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
412 {
413 String headerName = headerNames.nextElement();
414 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
415
416 if (HttpHeader.TRANSFER_ENCODING.is(headerName))
417 hasContent = true;
418
419 if (_hostHeader != null && HttpHeader.HOST.is(headerName))
420 continue;
421
422
423 if (HOP_HEADERS.contains(lowerHeaderName))
424 continue;
425
426 for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
427 {
428 String headerValue = headerValues.nextElement();
429 if (headerValue != null)
430 proxyRequest.header(headerName, headerValue);
431 }
432 }
433
434
435 if (_hostHeader != null)
436 proxyRequest.header(HttpHeader.HOST, _hostHeader);
437
438
439 addViaHeader(proxyRequest);
440 addXForwardedHeaders(proxyRequest, request);
441
442 final AsyncContext asyncContext = request.startAsync();
443
444 asyncContext.setTimeout(0);
445 proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
446
447 if (hasContent)
448 proxyRequest.content(proxyRequestContent(proxyRequest, request));
449
450 customizeProxyRequest(proxyRequest, request);
451
452 if (_log.isDebugEnabled())
453 {
454 StringBuilder builder = new StringBuilder(request.getMethod());
455 builder.append(" ").append(request.getRequestURI());
456 String query = request.getQueryString();
457 if (query != null)
458 builder.append("?").append(query);
459 builder.append(" ").append(request.getProtocol()).append("\r\n");
460 for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
461 {
462 String headerName = headerNames.nextElement();
463 builder.append(headerName).append(": ");
464 for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
465 {
466 String headerValue = headerValues.nextElement();
467 if (headerValue != null)
468 builder.append(headerValue);
469 if (headerValues.hasMoreElements())
470 builder.append(",");
471 }
472 builder.append("\r\n");
473 }
474 builder.append("\r\n");
475
476 _log.debug("{} proxying to upstream:{}{}{}{}",
477 requestId,
478 System.lineSeparator(),
479 builder,
480 proxyRequest,
481 System.lineSeparator(),
482 proxyRequest.getHeaders().toString().trim());
483 }
484
485 proxyRequest.send(newProxyResponseListener(request, response));
486 }
487
488 protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException
489 {
490 return new ProxyInputStreamContentProvider(proxyRequest, request, request.getInputStream());
491 }
492
493 protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
494 {
495 return new ProxyResponseListener(request, response);
496 }
497
498 protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure)
499 {
500 if (_log.isDebugEnabled())
501 _log.debug(getRequestId(request) + " client request failure", failure);
502 proxyRequest.abort(failure);
503 }
504
505 protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException
506 {
507 response.sendError(HttpServletResponse.SC_FORBIDDEN);
508 }
509
510 protected Request addViaHeader(Request proxyRequest)
511 {
512 return proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost());
513 }
514
515 protected void addXForwardedHeaders(Request proxyRequest, HttpServletRequest request)
516 {
517 proxyRequest.header(HttpHeader.X_FORWARDED_FOR, request.getRemoteAddr());
518 proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, request.getScheme());
519 proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString()));
520 proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName());
521 }
522
523 protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
524 {
525 for (HttpField field : proxyResponse.getHeaders())
526 {
527 String headerName = field.getName();
528 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
529 if (HOP_HEADERS.contains(lowerHeaderName))
530 continue;
531
532 String newHeaderValue = filterResponseHeader(request, headerName, field.getValue());
533 if (newHeaderValue == null || newHeaderValue.trim().length() == 0)
534 continue;
535
536 response.addHeader(headerName, newHeaderValue);
537 }
538 }
539
540 protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback)
541 {
542 try
543 {
544 if (_log.isDebugEnabled())
545 _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
546 response.getOutputStream().write(buffer, offset, length);
547 callback.succeeded();
548 }
549 catch (Throwable x)
550 {
551 callback.failed(x);
552 }
553 }
554
555 protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
556 {
557 if (_log.isDebugEnabled())
558 _log.debug("{} proxying successful", getRequestId(request));
559 AsyncContext asyncContext = request.getAsyncContext();
560 asyncContext.complete();
561 }
562
563 protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
564 {
565 if (_log.isDebugEnabled())
566 _log.debug(getRequestId(request) + " proxying failed", failure);
567 if (response.isCommitted())
568 {
569 try
570 {
571
572 response.sendError(-1);
573 AsyncContext asyncContext = request.getAsyncContext();
574 asyncContext.complete();
575 }
576 catch (IOException x)
577 {
578 if (_log.isDebugEnabled())
579 _log.debug(getRequestId(request) + " could not close the connection", failure);
580 }
581 }
582 else
583 {
584 response.resetBuffer();
585 if (failure instanceof TimeoutException)
586 response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
587 else
588 response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
589 response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
590 AsyncContext asyncContext = request.getAsyncContext();
591 asyncContext.complete();
592 }
593 }
594
595 protected int getRequestId(HttpServletRequest request)
596 {
597 return System.identityHashCode(request);
598 }
599
600 protected URI rewriteURI(HttpServletRequest request)
601 {
602 if (!validateDestination(request.getServerName(), request.getServerPort()))
603 return null;
604
605 StringBuffer uri = request.getRequestURL();
606 String query = request.getQueryString();
607 if (query != null)
608 uri.append("?").append(query);
609
610 return URI.create(uri.toString());
611 }
612
613
614
615
616
617
618
619
620 protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
621 {
622 }
623
624
625
626
627
628
629
630
631
632
633
634 protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
635 {
636 return headerValue;
637 }
638
639
640
641
642
643
644
645
646
647
648
649
650 public static class Transparent extends ProxyServlet
651 {
652 private final TransparentDelegate delegate = new TransparentDelegate(this);
653
654 @Override
655 public void init(ServletConfig config) throws ServletException
656 {
657 super.init(config);
658 delegate.init(config);
659 }
660
661 @Override
662 protected URI rewriteURI(HttpServletRequest request)
663 {
664 return delegate.rewriteURI(request);
665 }
666 }
667
668 protected static class TransparentDelegate
669 {
670 private final ProxyServlet proxyServlet;
671 private String _proxyTo;
672 private String _prefix;
673
674 protected TransparentDelegate(ProxyServlet proxyServlet)
675 {
676 this.proxyServlet = proxyServlet;
677 }
678
679 protected void init(ServletConfig config) throws ServletException
680 {
681 _proxyTo = config.getInitParameter("proxyTo");
682 if (_proxyTo == null)
683 throw new UnavailableException("Init parameter 'proxyTo' is required.");
684
685 String prefix = config.getInitParameter("prefix");
686 if (prefix != null)
687 {
688 if (!prefix.startsWith("/"))
689 throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
690 _prefix = prefix;
691 }
692
693
694 String contextPath = config.getServletContext().getContextPath();
695 _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
696
697 if (proxyServlet._log.isDebugEnabled())
698 proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
699 }
700
701 protected URI rewriteURI(HttpServletRequest request)
702 {
703 String path = request.getRequestURI();
704 if (!path.startsWith(_prefix))
705 return null;
706
707 StringBuilder uri = new StringBuilder(_proxyTo);
708 if (_proxyTo.endsWith("/"))
709 uri.setLength(uri.length() - 1);
710 String rest = path.substring(_prefix.length());
711 if (!rest.startsWith("/"))
712 uri.append("/");
713 uri.append(rest);
714 String query = request.getQueryString();
715 if (query != null)
716 uri.append("?").append(query);
717 URI rewrittenURI = URI.create(uri.toString()).normalize();
718
719 if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
720 return null;
721
722 return rewrittenURI;
723 }
724 }
725
726 protected class ProxyResponseListener extends Response.Listener.Adapter
727 {
728 private final HttpServletRequest request;
729 private final HttpServletResponse response;
730
731 protected ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
732 {
733 this.request = request;
734 this.response = response;
735 }
736
737 @Override
738 public void onBegin(Response proxyResponse)
739 {
740 response.setStatus(proxyResponse.getStatus());
741 }
742
743 @Override
744 public void onHeaders(Response proxyResponse)
745 {
746 onResponseHeaders(request, response, proxyResponse);
747
748 if (_log.isDebugEnabled())
749 {
750 StringBuilder builder = new StringBuilder("\r\n");
751 builder.append(request.getProtocol()).append(" ").append(response.getStatus()).append(" ").append(proxyResponse.getReason()).append("\r\n");
752 for (String headerName : response.getHeaderNames())
753 {
754 builder.append(headerName).append(": ");
755 for (Iterator<String> headerValues = response.getHeaders(headerName).iterator(); headerValues.hasNext();)
756 {
757 String headerValue = headerValues.next();
758 if (headerValue != null)
759 builder.append(headerValue);
760 if (headerValues.hasNext())
761 builder.append(",");
762 }
763 builder.append("\r\n");
764 }
765 _log.debug("{} proxying to downstream:{}{}{}{}{}",
766 getRequestId(request),
767 System.lineSeparator(),
768 proxyResponse,
769 System.lineSeparator(),
770 proxyResponse.getHeaders().toString().trim(),
771 System.lineSeparator(),
772 builder);
773 }
774 }
775
776 @Override
777 public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
778 {
779 byte[] buffer;
780 int offset;
781 int length = content.remaining();
782 if (content.hasArray())
783 {
784 buffer = content.array();
785 offset = content.arrayOffset();
786 }
787 else
788 {
789 buffer = new byte[length];
790 content.get(buffer);
791 offset = 0;
792 }
793
794 onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback()
795 {
796 @Override
797 public void succeeded()
798 {
799 callback.succeeded();
800 }
801
802 @Override
803 public void failed(Throwable x)
804 {
805 callback.failed(x);
806 proxyResponse.abort(x);
807 }
808 });
809 }
810
811 @Override
812 public void onComplete(Result result)
813 {
814 if (result.isSucceeded())
815 onResponseSuccess(request, response, result.getResponse());
816 else
817 onResponseFailure(request, response, result.getResponse(), result.getFailure());
818 if (_log.isDebugEnabled())
819 _log.debug("{} proxying complete", getRequestId(request));
820 }
821 }
822
823 protected class ProxyInputStreamContentProvider extends InputStreamContentProvider
824 {
825 private final Request proxyRequest;
826 private final HttpServletRequest request;
827
828 protected ProxyInputStreamContentProvider(Request proxyRequest, HttpServletRequest request, InputStream input)
829 {
830 super(input);
831 this.proxyRequest = proxyRequest;
832 this.request = request;
833 }
834
835 @Override
836 public long getLength()
837 {
838 return request.getContentLength();
839 }
840
841 @Override
842 protected ByteBuffer onRead(byte[] buffer, int offset, int length)
843 {
844 if (_log.isDebugEnabled())
845 _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
846 return onRequestContent(proxyRequest, request, buffer, offset, length);
847 }
848
849 protected ByteBuffer onRequestContent(Request proxyRequest, final HttpServletRequest request, byte[] buffer, int offset, int length)
850 {
851 return super.onRead(buffer, offset, length);
852 }
853
854 @Override
855 protected void onReadFailure(Throwable failure)
856 {
857 onClientRequestFailure(proxyRequest, request, failure);
858 }
859 }
860 }