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.net.InetAddress;
23 import java.net.URI;
24 import java.net.UnknownHostException;
25 import java.util.Collections;
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.TimeoutException;
33
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.http.HttpField;
47 import org.eclipse.jetty.http.HttpHeader;
48 import org.eclipse.jetty.http.HttpHeaderValue;
49 import org.eclipse.jetty.http.HttpStatus;
50 import org.eclipse.jetty.util.HttpCookieStore;
51 import org.eclipse.jetty.util.log.Log;
52 import org.eclipse.jetty.util.log.Logger;
53 import org.eclipse.jetty.util.thread.QueuedThreadPool;
54
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
80
81 public abstract class AbstractProxyServlet extends HttpServlet
82 {
83 protected static final Set<String> HOP_HEADERS;
84 static
85 {
86 Set<String> hopHeaders = new HashSet<>();
87 hopHeaders.add("connection");
88 hopHeaders.add("keep-alive");
89 hopHeaders.add("proxy-authorization");
90 hopHeaders.add("proxy-authenticate");
91 hopHeaders.add("proxy-connection");
92 hopHeaders.add("transfer-encoding");
93 hopHeaders.add("te");
94 hopHeaders.add("trailer");
95 hopHeaders.add("upgrade");
96 HOP_HEADERS = Collections.unmodifiableSet(hopHeaders);
97 }
98
99 private final Set<String> _whiteList = new HashSet<>();
100 private final Set<String> _blackList = new HashSet<>();
101 protected Logger _log;
102 private boolean _preserveHost;
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 _preserveHost = Boolean.parseBoolean(config.getInitParameter("preserveHost"));
116
117 _hostHeader = config.getInitParameter("hostHeader");
118
119 _viaHost = config.getInitParameter("viaHost");
120 if (_viaHost == null)
121 _viaHost = viaHost();
122
123 try
124 {
125 _client = createHttpClient();
126
127
128 getServletContext().setAttribute(config.getServletName() + ".HttpClient", _client);
129
130 String whiteList = config.getInitParameter("whiteList");
131 if (whiteList != null)
132 getWhiteListHosts().addAll(parseList(whiteList));
133
134 String blackList = config.getInitParameter("blackList");
135 if (blackList != null)
136 getBlackListHosts().addAll(parseList(blackList));
137 }
138 catch (Exception e)
139 {
140 throw new ServletException(e);
141 }
142 }
143
144 @Override
145 public void destroy()
146 {
147 try
148 {
149 _client.stop();
150 }
151 catch (Exception x)
152 {
153 if (_log.isDebugEnabled())
154 _log.debug(x);
155 }
156 }
157
158 public String getHostHeader()
159 {
160 return _hostHeader;
161 }
162
163 public String getViaHost()
164 {
165 return _viaHost;
166 }
167
168 private static String viaHost()
169 {
170 try
171 {
172 return InetAddress.getLocalHost().getHostName();
173 }
174 catch (UnknownHostException x)
175 {
176 return "localhost";
177 }
178 }
179
180 public long getTimeout()
181 {
182 return _timeout;
183 }
184
185 public void setTimeout(long timeout)
186 {
187 this._timeout = timeout;
188 }
189
190 public Set<String> getWhiteListHosts()
191 {
192 return _whiteList;
193 }
194
195 public Set<String> getBlackListHosts()
196 {
197 return _blackList;
198 }
199
200
201
202
203 protected Logger createLogger()
204 {
205 String servletName = getServletConfig().getServletName();
206 servletName = servletName.replace('-', '.');
207 if ((getClass().getPackage() != null) && !servletName.startsWith(getClass().getPackage().getName()))
208 {
209 servletName = getClass().getName() + "." + servletName;
210 }
211 return Log.getLogger(servletName);
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
262
263 protected HttpClient createHttpClient() throws ServletException
264 {
265 ServletConfig config = getServletConfig();
266
267 HttpClient client = newHttpClient();
268
269
270 client.setFollowRedirects(false);
271
272
273 client.setCookieStore(new HttpCookieStore.Empty());
274
275 Executor executor;
276 String value = config.getInitParameter("maxThreads");
277 if (value == null || "-".equals(value))
278 {
279 executor = (Executor)getServletContext().getAttribute("org.eclipse.jetty.server.Executor");
280 if (executor==null)
281 throw new IllegalStateException("No server executor for proxy");
282 }
283 else
284 {
285 QueuedThreadPool qtp= new QueuedThreadPool(Integer.parseInt(value));
286 String servletName = config.getServletName();
287 int dot = servletName.lastIndexOf('.');
288 if (dot >= 0)
289 servletName = servletName.substring(dot + 1);
290 qtp.setName(servletName);
291 executor=qtp;
292 }
293
294 client.setExecutor(executor);
295
296 value = config.getInitParameter("maxConnections");
297 if (value == null)
298 value = "256";
299 client.setMaxConnectionsPerDestination(Integer.parseInt(value));
300
301 value = config.getInitParameter("idleTimeout");
302 if (value == null)
303 value = "30000";
304 client.setIdleTimeout(Long.parseLong(value));
305
306 value = config.getInitParameter("timeout");
307 if (value == null)
308 value = "60000";
309 _timeout = Long.parseLong(value);
310
311 value = config.getInitParameter("requestBufferSize");
312 if (value != null)
313 client.setRequestBufferSize(Integer.parseInt(value));
314
315 value = config.getInitParameter("responseBufferSize");
316 if (value != null)
317 client.setResponseBufferSize(Integer.parseInt(value));
318
319 try
320 {
321 client.start();
322
323
324 client.getContentDecoderFactories().clear();
325
326
327 client.getProtocolHandlers().clear();
328
329 return client;
330 }
331 catch (Exception x)
332 {
333 throw new ServletException(x);
334 }
335 }
336
337
338
339
340 protected HttpClient newHttpClient()
341 {
342 return new HttpClient();
343 }
344
345 protected HttpClient getHttpClient()
346 {
347 return _client;
348 }
349
350 private Set<String> parseList(String list)
351 {
352 Set<String> result = new HashSet<>();
353 String[] hosts = list.split(",");
354 for (String host : hosts)
355 {
356 host = host.trim();
357 if (host.length() == 0)
358 continue;
359 result.add(host);
360 }
361 return result;
362 }
363
364
365
366
367
368
369
370
371 public boolean validateDestination(String host, int port)
372 {
373 String hostPort = host + ":" + port;
374 if (!_whiteList.isEmpty())
375 {
376 if (!_whiteList.contains(hostPort))
377 {
378 if (_log.isDebugEnabled())
379 _log.debug("Host {}:{} not whitelisted", host, port);
380 return false;
381 }
382 }
383 if (!_blackList.isEmpty())
384 {
385 if (_blackList.contains(hostPort))
386 {
387 if (_log.isDebugEnabled())
388 _log.debug("Host {}:{} blacklisted", host, port);
389 return false;
390 }
391 }
392 return true;
393 }
394
395 protected String rewriteTarget(HttpServletRequest clientRequest)
396 {
397 if (!validateDestination(clientRequest.getServerName(), clientRequest.getServerPort()))
398 return null;
399
400 StringBuffer target = clientRequest.getRequestURL();
401 String query = clientRequest.getQueryString();
402 if (query != null)
403 target.append("?").append(query);
404 return target.toString();
405 }
406
407
408
409
410
411
412
413
414
415
416
417 protected void onProxyRewriteFailed(HttpServletRequest clientRequest, HttpServletResponse clientResponse)
418 {
419 clientResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
420 }
421
422 protected boolean hasContent(HttpServletRequest clientRequest)
423 {
424 return clientRequest.getContentLength() > 0 ||
425 clientRequest.getContentType() != null ||
426 clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
427 }
428
429 protected void copyRequestHeaders(HttpServletRequest clientRequest, Request proxyRequest)
430 {
431
432 proxyRequest.getHeaders().clear();
433
434 Set<String> headersToRemove = findConnectionHeaders(clientRequest);
435
436 for (Enumeration<String> headerNames = clientRequest.getHeaderNames(); headerNames.hasMoreElements();)
437 {
438 String headerName = headerNames.nextElement();
439 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
440
441 if (HttpHeader.HOST.is(headerName) && !_preserveHost)
442 continue;
443
444
445 if (HOP_HEADERS.contains(lowerHeaderName))
446 continue;
447 if (headersToRemove != null && headersToRemove.contains(lowerHeaderName))
448 continue;
449
450 for (Enumeration<String> headerValues = clientRequest.getHeaders(headerName); headerValues.hasMoreElements();)
451 {
452 String headerValue = headerValues.nextElement();
453 if (headerValue != null)
454 proxyRequest.header(headerName, headerValue);
455 }
456 }
457
458
459 if (_hostHeader != null)
460 proxyRequest.header(HttpHeader.HOST, _hostHeader);
461 }
462
463 protected Set<String> findConnectionHeaders(HttpServletRequest clientRequest)
464 {
465
466
467 Set<String> hopHeaders = null;
468 Enumeration<String> connectionHeaders = clientRequest.getHeaders(HttpHeader.CONNECTION.asString());
469 while (connectionHeaders.hasMoreElements())
470 {
471 String value = connectionHeaders.nextElement();
472 String[] values = value.split(",");
473 for (String name : values)
474 {
475 name = name.trim().toLowerCase(Locale.ENGLISH);
476 if (hopHeaders == null)
477 hopHeaders = new HashSet<>();
478 hopHeaders.add(name);
479 }
480 }
481 return hopHeaders;
482 }
483
484 protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
485 {
486 addViaHeader(proxyRequest);
487 addXForwardedHeaders(clientRequest, proxyRequest);
488 }
489
490 protected void addViaHeader(Request proxyRequest)
491 {
492 proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost());
493 }
494
495 protected void addXForwardedHeaders(HttpServletRequest clientRequest, Request proxyRequest)
496 {
497 proxyRequest.header(HttpHeader.X_FORWARDED_FOR, clientRequest.getRemoteAddr());
498 proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, clientRequest.getScheme());
499 proxyRequest.header(HttpHeader.X_FORWARDED_HOST, clientRequest.getHeader(HttpHeader.HOST.asString()));
500 proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, clientRequest.getLocalName());
501 }
502
503 protected void sendProxyRequest(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest)
504 {
505 if (_log.isDebugEnabled())
506 {
507 StringBuilder builder = new StringBuilder(clientRequest.getMethod());
508 builder.append(" ").append(clientRequest.getRequestURI());
509 String query = clientRequest.getQueryString();
510 if (query != null)
511 builder.append("?").append(query);
512 builder.append(" ").append(clientRequest.getProtocol()).append(System.lineSeparator());
513 for (Enumeration<String> headerNames = clientRequest.getHeaderNames(); headerNames.hasMoreElements();)
514 {
515 String headerName = headerNames.nextElement();
516 builder.append(headerName).append(": ");
517 for (Enumeration<String> headerValues = clientRequest.getHeaders(headerName); headerValues.hasMoreElements();)
518 {
519 String headerValue = headerValues.nextElement();
520 if (headerValue != null)
521 builder.append(headerValue);
522 if (headerValues.hasMoreElements())
523 builder.append(",");
524 }
525 builder.append(System.lineSeparator());
526 }
527 builder.append(System.lineSeparator());
528
529 _log.debug("{} proxying to upstream:{}{}{}{}",
530 getRequestId(clientRequest),
531 System.lineSeparator(),
532 builder,
533 proxyRequest,
534 System.lineSeparator(),
535 proxyRequest.getHeaders().toString().trim());
536 }
537
538 proxyRequest.send(newProxyResponseListener(clientRequest, proxyResponse));
539 }
540
541 protected abstract Response.CompleteListener newProxyResponseListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse);
542
543 protected void onClientRequestFailure(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, Throwable failure)
544 {
545 boolean aborted = proxyRequest.abort(failure);
546 if (!aborted)
547 {
548 int status = failure instanceof TimeoutException ?
549 HttpStatus.REQUEST_TIMEOUT_408 :
550 HttpStatus.INTERNAL_SERVER_ERROR_500;
551 proxyResponse.setStatus(status);
552 clientRequest.getAsyncContext().complete();
553 }
554 }
555
556 protected void onServerResponseHeaders(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
557 {
558 for (HttpField field : serverResponse.getHeaders())
559 {
560 String headerName = field.getName();
561 String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
562 if (HOP_HEADERS.contains(lowerHeaderName))
563 continue;
564
565 String newHeaderValue = filterServerResponseHeader(clientRequest, serverResponse, headerName, field.getValue());
566 if (newHeaderValue == null || newHeaderValue.trim().length() == 0)
567 continue;
568
569 proxyResponse.addHeader(headerName, newHeaderValue);
570 }
571
572 if (_log.isDebugEnabled())
573 {
574 StringBuilder builder = new StringBuilder(System.lineSeparator());
575 builder.append(clientRequest.getProtocol()).append(" ").append(proxyResponse.getStatus())
576 .append(" ").append(serverResponse.getReason()).append(System.lineSeparator());
577 for (String headerName : proxyResponse.getHeaderNames())
578 {
579 builder.append(headerName).append(": ");
580 for (Iterator<String> headerValues = proxyResponse.getHeaders(headerName).iterator(); headerValues.hasNext(); )
581 {
582 String headerValue = headerValues.next();
583 if (headerValue != null)
584 builder.append(headerValue);
585 if (headerValues.hasNext())
586 builder.append(",");
587 }
588 builder.append(System.lineSeparator());
589 }
590 _log.debug("{} proxying to downstream:{}{}{}{}{}",
591 getRequestId(clientRequest),
592 System.lineSeparator(),
593 serverResponse,
594 System.lineSeparator(),
595 serverResponse.getHeaders().toString().trim(),
596 System.lineSeparator(),
597 builder);
598 }
599 }
600
601 protected String filterServerResponseHeader(HttpServletRequest clientRequest, Response serverResponse, String headerName, String headerValue)
602 {
603 return headerValue;
604 }
605
606 protected void onProxyResponseSuccess(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse)
607 {
608 if (_log.isDebugEnabled())
609 _log.debug("{} proxying successful", getRequestId(clientRequest));
610
611 AsyncContext asyncContext = clientRequest.getAsyncContext();
612 asyncContext.complete();
613 }
614
615 protected void onProxyResponseFailure(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse, Throwable failure)
616 {
617 if (_log.isDebugEnabled())
618 _log.debug(getRequestId(clientRequest) + " proxying failed", failure);
619
620 if (proxyResponse.isCommitted())
621 {
622 try
623 {
624
625 proxyResponse.sendError(-1);
626 AsyncContext asyncContext = clientRequest.getAsyncContext();
627 asyncContext.complete();
628 }
629 catch (IOException x)
630 {
631 if (_log.isDebugEnabled())
632 _log.debug(getRequestId(clientRequest) + " could not close the connection", failure);
633 }
634 }
635 else
636 {
637 proxyResponse.resetBuffer();
638 if (failure instanceof TimeoutException)
639 proxyResponse.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
640 else
641 proxyResponse.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
642 proxyResponse.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
643 AsyncContext asyncContext = clientRequest.getAsyncContext();
644 asyncContext.complete();
645 }
646 }
647
648 protected int getRequestId(HttpServletRequest clientRequest)
649 {
650 return System.identityHashCode(clientRequest);
651 }
652
653
654
655
656
657
658
659
660
661
662
663
664 protected static class TransparentDelegate
665 {
666 private final ProxyServlet proxyServlet;
667 private String _proxyTo;
668 private String _prefix;
669
670 protected TransparentDelegate(ProxyServlet proxyServlet)
671 {
672 this.proxyServlet = proxyServlet;
673 }
674
675 protected void init(ServletConfig config) throws ServletException
676 {
677 _proxyTo = config.getInitParameter("proxyTo");
678 if (_proxyTo == null)
679 throw new UnavailableException("Init parameter 'proxyTo' is required.");
680
681 String prefix = config.getInitParameter("prefix");
682 if (prefix != null)
683 {
684 if (!prefix.startsWith("/"))
685 throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
686 _prefix = prefix;
687 }
688
689
690 String contextPath = config.getServletContext().getContextPath();
691 _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
692
693 if (proxyServlet._log.isDebugEnabled())
694 proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
695 }
696
697 protected String rewriteTarget(HttpServletRequest request)
698 {
699 String path = request.getRequestURI();
700 if (!path.startsWith(_prefix))
701 return null;
702
703 StringBuilder uri = new StringBuilder(_proxyTo);
704 if (_proxyTo.endsWith("/"))
705 uri.setLength(uri.length() - 1);
706 String rest = path.substring(_prefix.length());
707 if (!rest.isEmpty())
708 {
709 if (!rest.startsWith("/"))
710 uri.append("/");
711 uri.append(rest);
712 }
713
714 String query = request.getQueryString();
715 if (query != null)
716 {
717
718 String separator = "://";
719 if (uri.indexOf("/", uri.indexOf(separator) + separator.length()) < 0)
720 uri.append("/");
721 uri.append("?").append(query);
722 }
723 URI rewrittenURI = URI.create(uri.toString()).normalize();
724
725 if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
726 return null;
727
728 return rewrittenURI.toString();
729 }
730 }
731 }