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 proxyResponse)
419 {
420 sendProxyResponseError(clientRequest, proxyResponse, HttpStatus.FORBIDDEN_403);
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 sendProxyResponseError(clientRequest, proxyResponse, status);
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 int status = failure instanceof TimeoutException ?
639 HttpStatus.GATEWAY_TIMEOUT_504 :
640 HttpStatus.BAD_GATEWAY_502;
641 sendProxyResponseError(clientRequest, proxyResponse, status);
642 }
643 }
644
645 protected int getRequestId(HttpServletRequest clientRequest)
646 {
647 return System.identityHashCode(clientRequest);
648 }
649
650 protected void sendProxyResponseError(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, int status)
651 {
652 proxyResponse.setStatus(status);
653 proxyResponse.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
654 if (clientRequest.isAsyncStarted())
655 clientRequest.getAsyncContext().complete();
656 }
657
658
659
660
661
662
663
664
665
666
667
668
669 protected static class TransparentDelegate
670 {
671 private final ProxyServlet proxyServlet;
672 private String _proxyTo;
673 private String _prefix;
674
675 protected TransparentDelegate(ProxyServlet proxyServlet)
676 {
677 this.proxyServlet = proxyServlet;
678 }
679
680 protected void init(ServletConfig config) throws ServletException
681 {
682 _proxyTo = config.getInitParameter("proxyTo");
683 if (_proxyTo == null)
684 throw new UnavailableException("Init parameter 'proxyTo' is required.");
685
686 String prefix = config.getInitParameter("prefix");
687 if (prefix != null)
688 {
689 if (!prefix.startsWith("/"))
690 throw new UnavailableException("Init parameter 'prefix' must start with a '/'.");
691 _prefix = prefix;
692 }
693
694
695 String contextPath = config.getServletContext().getContextPath();
696 _prefix = _prefix == null ? contextPath : (contextPath + _prefix);
697
698 if (proxyServlet._log.isDebugEnabled())
699 proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
700 }
701
702 protected String rewriteTarget(HttpServletRequest request)
703 {
704 String path = request.getRequestURI();
705 if (!path.startsWith(_prefix))
706 return null;
707
708 StringBuilder uri = new StringBuilder(_proxyTo);
709 if (_proxyTo.endsWith("/"))
710 uri.setLength(uri.length() - 1);
711 String rest = path.substring(_prefix.length());
712 if (!rest.isEmpty())
713 {
714 if (!rest.startsWith("/"))
715 uri.append("/");
716 uri.append(rest);
717 }
718
719 String query = request.getQueryString();
720 if (query != null)
721 {
722
723 String separator = "://";
724 if (uri.indexOf("/", uri.indexOf(separator) + separator.length()) < 0)
725 uri.append("/");
726 uri.append("?").append(query);
727 }
728 URI rewrittenURI = URI.create(uri.toString()).normalize();
729
730 if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
731 return null;
732
733 return rewrittenURI.toString();
734 }
735 }
736 }