1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.servlets;
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.TimeUnit;
32 import java.util.concurrent.TimeoutException;
33 import javax.servlet.AsyncContext;
34 import javax.servlet.ServletConfig;
35 import javax.servlet.ServletException;
36 import javax.servlet.UnavailableException;
37 import javax.servlet.http.HttpServlet;
38 import javax.servlet.http.HttpServletRequest;
39 import javax.servlet.http.HttpServletResponse;
40
41 import org.eclipse.jetty.client.HttpClient;
42 import org.eclipse.jetty.client.api.Request;
43 import org.eclipse.jetty.client.api.Response;
44 import org.eclipse.jetty.client.api.Result;
45 import org.eclipse.jetty.client.util.InputStreamContentProvider;
46 import org.eclipse.jetty.client.util.TimedResponseListener;
47 import org.eclipse.jetty.http.HttpFields;
48 import org.eclipse.jetty.http.HttpMethod;
49 import org.eclipse.jetty.http.HttpVersion;
50 import org.eclipse.jetty.server.handler.ConnectHandler;
51 import org.eclipse.jetty.server.handler.ContextHandler;
52 import org.eclipse.jetty.util.log.Log;
53 import org.eclipse.jetty.util.log.Logger;
54 import org.eclipse.jetty.util.thread.QueuedThreadPool;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public class ProxyServlet extends HttpServlet
80 {
81 protected static final String ASYNC_CONTEXT = ProxyServlet.class.getName() + ".asyncContext";
82 private static final Set<String> HOP_HEADERS = new HashSet<>();
83 static
84 {
85 HOP_HEADERS.add("proxy-connection");
86 HOP_HEADERS.add("connection");
87 HOP_HEADERS.add("keep-alive");
88 HOP_HEADERS.add("transfer-encoding");
89 HOP_HEADERS.add("te");
90 HOP_HEADERS.add("trailer");
91 HOP_HEADERS.add("proxy-authorization");
92 HOP_HEADERS.add("proxy-authenticate");
93 HOP_HEADERS.add("upgrade");
94 }
95
96 private final Set<String> _whiteList = new HashSet<>();
97 private final Set<String> _blackList = new HashSet<>();
98
99 protected Logger _log;
100 private String _hostHeader;
101 private String _viaHost;
102 private HttpClient _client;
103 private long _timeout;
104
105 @Override
106 public void init() throws ServletException
107 {
108 _log = createLogger();
109
110 ServletConfig config = getServletConfig();
111
112 _hostHeader = config.getInitParameter("hostHeader");
113
114 _viaHost = config.getInitParameter("viaHost");
115 if (_viaHost == null)
116 _viaHost = viaHost();
117
118 try
119 {
120 _client = createHttpClient();
121
122
123 getServletContext().setAttribute(config.getServletName() + ".HttpClient", _client);
124
125 String whiteList = config.getInitParameter("whiteList");
126 if (whiteList != null)
127 getWhiteListHosts().addAll(parseList(whiteList));
128
129 String blackList = config.getInitParameter("blackList");
130 if (blackList != null)
131 getBlackListHosts().addAll(parseList(blackList));
132 }
133 catch (Exception e)
134 {
135 throw new ServletException(e);
136 }
137 }
138
139 public long getTimeout()
140 {
141 return _timeout;
142 }
143
144 public void setTimeout(long timeout)
145 {
146 this._timeout = timeout;
147 }
148
149 public Set<String> getWhiteListHosts()
150 {
151 return _whiteList;
152 }
153
154 public Set<String> getBlackListHosts()
155 {
156 return _blackList;
157 }
158
159 protected static String viaHost()
160 {
161 try
162 {
163 return InetAddress.getLocalHost().getHostName();
164 }
165 catch (UnknownHostException x)
166 {
167 return "localhost";
168 }
169 }
170
171
172
173
174 protected Logger createLogger()
175 {
176 String name = getServletConfig().getServletName();
177 name = name.replace('-', '.');
178 return Log.getLogger(name);
179 }
180
181 public void destroy()
182 {
183 try
184 {
185 _client.stop();
186 }
187 catch (Exception x)
188 {
189 _log.debug(x);
190 }
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204
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 protected HttpClient createHttpClient() throws ServletException
243 {
244 ServletConfig config = getServletConfig();
245
246 HttpClient client = newHttpClient();
247
248 client.setFollowRedirects(false);
249
250 String value = config.getInitParameter("maxThreads");
251 if (value == null)
252 value = "256";
253 QueuedThreadPool executor = new QueuedThreadPool(Integer.parseInt(value));
254 String servletName = config.getServletName();
255 int dot = servletName.lastIndexOf('.');
256 if (dot >= 0)
257 servletName = servletName.substring(dot + 1);
258 executor.setName(servletName);
259 client.setExecutor(executor);
260
261 value = config.getInitParameter("maxConnections");
262 if (value == null)
263 value = "32768";
264 client.setMaxConnectionsPerAddress(Integer.parseInt(value));
265
266 value = config.getInitParameter("idleTimeout");
267 if (value == null)
268 value = "30000";
269 client.setIdleTimeout(Long.parseLong(value));
270
271 value = config.getInitParameter("timeout");
272 if (value == null)
273 value = "60000";
274 _timeout = Long.parseLong(value);
275
276 value = config.getInitParameter("requestBufferSize");
277 if (value != null)
278 client.setRequestBufferSize(Integer.parseInt(value));
279
280 value = config.getInitParameter("responseBufferSize");
281 if (value != null)
282 client.setResponseBufferSize(Integer.parseInt(value));
283
284 try
285 {
286 client.start();
287
288
289 client.getContentDecoderFactories().clear();
290
291 return client;
292 }
293 catch (Exception x)
294 {
295 throw new ServletException(x);
296 }
297 }
298
299
300
301
302 protected HttpClient newHttpClient()
303 {
304 return new HttpClient();
305 }
306
307 private Set<String> parseList(String list)
308 {
309 Set<String> result = new HashSet<>();
310 String[] hosts = list.split(",");
311 for (String host : hosts)
312 {
313 host = host.trim();
314 if (host.length() == 0)
315 continue;
316 result.add(host);
317 }
318 return result;
319 }
320
321
322
323
324
325
326
327
328 public boolean validateDestination(String host, int port)
329 {
330 String hostPort = host + ":" + port;
331 if (!_whiteList.isEmpty())
332 {
333 if (!_whiteList.contains(hostPort))
334 {
335 _log.debug("Host {}:{} not whitelisted", host, port);
336 return false;
337 }
338 }
339 if (!_blackList.isEmpty())
340 {
341 if (_blackList.contains(hostPort))
342 {
343 _log.debug("Host {}:{} blacklisted", host, port);
344 return false;
345 }
346 }
347 return true;
348 }
349
350 @Override
351 protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
352 {
353 final int requestId = getRequestId(request);
354
355 URI rewrittenURI = rewriteURI(request);
356
357 if (_log.isDebugEnabled())
358 {
359 StringBuffer uri = request.getRequestURL();
360 if (request.getQueryString() != null)
361 uri.append("?").append(request.getQueryString());
362 _log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenURI);
363 }
364
365 if (rewrittenURI == null)
366 {
367 response.sendError(HttpServletResponse.SC_FORBIDDEN);
368 return;
369 }
370
371 final Request proxyRequest = _client.newRequest(rewrittenURI)
372 .method(HttpMethod.fromString(request.getMethod()))
373 .version(HttpVersion.fromString(request.getProtocol()));
374
375
376 for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
377 {
378 String headerName = headerNames.nextElement();
379 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
380
381
382 if (HOP_HEADERS.contains(lowerHeaderName))
383 continue;
384
385 if (_hostHeader!=null && lowerHeaderName.equals("host"))
386 continue;
387
388 for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
389 {
390 String headerValue = headerValues.nextElement();
391 if (headerValue != null)
392 proxyRequest.header(headerName, headerValue);
393 }
394 }
395
396
397 if (_hostHeader != null)
398 proxyRequest.header("Host", _hostHeader);
399
400
401 proxyRequest.header("Via", "http/1.1 " + _viaHost);
402 proxyRequest.header("X-Forwarded-For", request.getRemoteAddr());
403 proxyRequest.header("X-Forwarded-Proto", request.getScheme());
404 proxyRequest.header("X-Forwarded-Host", request.getHeader("Host"));
405 proxyRequest.header("X-Forwarded-Server", request.getLocalName());
406
407 proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
408 {
409 @Override
410 public long getLength()
411 {
412 return request.getContentLength();
413 }
414
415 @Override
416 protected ByteBuffer onRead(byte[] buffer, int offset, int length)
417 {
418 _log.debug("{} proxying content to upstream: {} bytes", requestId, length);
419 return super.onRead(buffer, offset, length);
420 }
421 });
422
423 final AsyncContext asyncContext = request.startAsync();
424
425 asyncContext.setTimeout(0);
426 request.setAttribute(ASYNC_CONTEXT, asyncContext);
427
428 customizeProxyRequest(proxyRequest, request);
429
430 if (_log.isDebugEnabled())
431 {
432 StringBuilder builder = new StringBuilder(request.getMethod());
433 builder.append(" ").append(request.getRequestURI());
434 String query = request.getQueryString();
435 if (query != null)
436 builder.append("?").append(query);
437 builder.append(" ").append(request.getProtocol()).append("\r\n");
438 for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
439 {
440 String headerName = headerNames.nextElement();
441 builder.append(headerName).append(": ");
442 for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
443 {
444 String headerValue = headerValues.nextElement();
445 if (headerValue != null)
446 builder.append(headerValue);
447 if (headerValues.hasMoreElements())
448 builder.append(",");
449 }
450 builder.append("\r\n");
451 }
452 builder.append("\r\n");
453
454 _log.debug("{} proxying to upstream:{}{}{}{}",
455 requestId,
456 System.lineSeparator(),
457 builder,
458 proxyRequest,
459 System.lineSeparator(),
460 proxyRequest.getHeaders().toString().trim());
461 }
462
463 proxyRequest.send(new TimedResponseListener(getTimeout(), TimeUnit.MILLISECONDS, proxyRequest, new ProxyResponseListener(request, response)));
464 }
465
466 protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
467 {
468 for (HttpFields.Field field : proxyResponse.getHeaders())
469 {
470 String headerName = field.getName();
471 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
472 if (HOP_HEADERS.contains(lowerHeaderName))
473 continue;
474
475 String newHeaderValue = filterResponseHeader(request, headerName, field.getValue());
476 if (newHeaderValue == null || newHeaderValue.trim().length() == 0)
477 continue;
478
479 response.addHeader(headerName, newHeaderValue);
480 }
481 }
482
483 protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException
484 {
485 response.getOutputStream().write(buffer, offset, length);
486 _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
487 }
488
489 protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
490 {
491 AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
492 asyncContext.complete();
493 _log.debug("{} proxying successful", getRequestId(request));
494 }
495
496 protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
497 {
498 _log.debug(getRequestId(request) + " proxying failed", failure);
499 if (!response.isCommitted())
500 {
501 if (failure instanceof TimeoutException)
502 response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
503 else
504 response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
505 }
506 AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
507 asyncContext.complete();
508 }
509
510 protected int getRequestId(HttpServletRequest request)
511 {
512 return System.identityHashCode(request);
513 }
514
515 protected URI rewriteURI(HttpServletRequest request)
516 {
517 if (!validateDestination(request.getServerName(), request.getServerPort()))
518 return null;
519
520 StringBuffer uri = request.getRequestURL();
521 String query = request.getQueryString();
522 if (query != null)
523 uri.append("?").append(query);
524
525 return URI.create(uri.toString());
526 }
527
528
529
530
531
532
533
534
535 protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
536 {
537 }
538
539
540
541
542
543
544
545
546
547
548
549 protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
550 {
551 return headerValue;
552 }
553
554
555
556
557
558
559
560
561
562
563
564
565
566 public static class Transparent extends ProxyServlet
567 {
568 private String _proxyTo;
569 private String _prefix;
570
571 public Transparent()
572 {
573 }
574
575 public Transparent(String proxyTo, String prefix)
576 {
577 _proxyTo = URI.create(proxyTo).normalize().toString();
578 _prefix = URI.create(prefix).normalize().toString();
579 }
580
581 @Override
582 public void init() throws ServletException
583 {
584 super.init();
585
586 ServletConfig config = getServletConfig();
587
588 String prefix = config.getInitParameter("prefix");
589 _prefix = prefix == null ? _prefix : prefix;
590
591
592 String contextPath = getServletContext().getContextPath();
593 _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
594
595 String proxyTo = config.getInitParameter("proxyTo");
596 _proxyTo = proxyTo == null ? _proxyTo : proxyTo;
597
598 if (_proxyTo == null)
599 throw new UnavailableException("Init parameter 'proxyTo' is required.");
600
601 if (!_prefix.startsWith("/"))
602 throw new UnavailableException("Init parameter 'prefix' parameter must start with a '/'.");
603
604 _log.info(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
605 }
606
607 @Override
608 protected URI rewriteURI(HttpServletRequest request)
609 {
610 String path = request.getRequestURI();
611 if (!path.startsWith(_prefix))
612 return null;
613
614 URI rewrittenURI = URI.create(_proxyTo + path.substring(_prefix.length())).normalize();
615
616 if (!validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
617 return null;
618
619 return rewrittenURI;
620 }
621 }
622
623 private class ProxyResponseListener extends Response.Listener.Empty
624 {
625 private final HttpServletRequest request;
626 private final HttpServletResponse response;
627
628 public ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
629 {
630 this.request = request;
631 this.response = response;
632 }
633
634 @Override
635 public void onBegin(Response proxyResponse)
636 {
637 response.setStatus(proxyResponse.getStatus());
638 }
639
640 @Override
641 public void onHeaders(Response proxyResponse)
642 {
643 onResponseHeaders(request, response, proxyResponse);
644
645 if (_log.isDebugEnabled())
646 {
647 StringBuilder builder = new StringBuilder("\r\n");
648 builder.append(request.getProtocol()).append(" ").append(response.getStatus()).append(" ").append(proxyResponse.getReason()).append("\r\n");
649 for (String headerName : response.getHeaderNames())
650 {
651 builder.append(headerName).append(": ");
652 for (Iterator<String> headerValues = response.getHeaders(headerName).iterator(); headerValues.hasNext();)
653 {
654 String headerValue = headerValues.next();
655 if (headerValue != null)
656 builder.append(headerValue);
657 if (headerValues.hasNext())
658 builder.append(",");
659 }
660 builder.append("\r\n");
661 }
662 _log.debug("{} proxying to downstream:{}{}{}{}{}",
663 getRequestId(request),
664 System.lineSeparator(),
665 proxyResponse,
666 System.lineSeparator(),
667 proxyResponse.getHeaders().toString().trim(),
668 System.lineSeparator(),
669 builder);
670 }
671 }
672
673 @Override
674 public void onContent(Response proxyResponse, ByteBuffer content)
675 {
676 byte[] buffer;
677 int offset;
678 int length = content.remaining();
679 if (content.hasArray())
680 {
681 buffer = content.array();
682 offset = content.arrayOffset();
683 }
684 else
685 {
686 buffer = new byte[length];
687 content.get(buffer);
688 offset = 0;
689 }
690
691 try
692 {
693 onResponseContent(request, response, proxyResponse, buffer, offset, length);
694 }
695 catch (IOException x)
696 {
697 proxyResponse.abort(x);
698 }
699 }
700
701 @Override
702 public void onSuccess(Response proxyResponse)
703 {
704 onResponseSuccess(request, response, proxyResponse);
705 }
706
707 @Override
708 public void onFailure(Response proxyResponse, Throwable failure)
709 {
710 onResponseFailure(request, response, proxyResponse, failure);
711 }
712
713 @Override
714 public void onComplete(Result result)
715 {
716 _log.debug("{} proxying complete", getRequestId(request));
717 }
718 }
719 }