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