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