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.io.InputStream;
23 import java.io.OutputStream;
24 import java.net.InetSocketAddress;
25 import java.net.MalformedURLException;
26 import java.net.Socket;
27 import java.net.URI;
28 import java.net.URISyntaxException;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.StringTokenizer;
36
37 import javax.servlet.Servlet;
38 import javax.servlet.ServletConfig;
39 import javax.servlet.ServletContext;
40 import javax.servlet.ServletException;
41 import javax.servlet.ServletRequest;
42 import javax.servlet.ServletResponse;
43 import javax.servlet.UnavailableException;
44 import javax.servlet.http.HttpServletRequest;
45 import javax.servlet.http.HttpServletResponse;
46
47 import org.eclipse.jetty.client.HttpClient;
48 import org.eclipse.jetty.client.HttpExchange;
49 import org.eclipse.jetty.continuation.Continuation;
50 import org.eclipse.jetty.continuation.ContinuationSupport;
51 import org.eclipse.jetty.http.HttpHeaderValues;
52 import org.eclipse.jetty.http.HttpHeaders;
53 import org.eclipse.jetty.http.HttpSchemes;
54 import org.eclipse.jetty.http.HttpURI;
55 import org.eclipse.jetty.http.PathMap;
56 import org.eclipse.jetty.io.Buffer;
57 import org.eclipse.jetty.io.EofException;
58 import org.eclipse.jetty.util.HostMap;
59 import org.eclipse.jetty.util.IO;
60 import org.eclipse.jetty.util.log.Log;
61 import org.eclipse.jetty.util.log.Logger;
62 import org.eclipse.jetty.util.thread.QueuedThreadPool;
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public class ProxyServlet implements Servlet
92 {
93 protected Logger _log;
94 protected HttpClient _client;
95 protected String _hostHeader;
96
97 protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
98 {
99 _DontProxyHeaders.add("proxy-connection");
100 _DontProxyHeaders.add("connection");
101 _DontProxyHeaders.add("keep-alive");
102 _DontProxyHeaders.add("transfer-encoding");
103 _DontProxyHeaders.add("te");
104 _DontProxyHeaders.add("trailer");
105 _DontProxyHeaders.add("proxy-authorization");
106 _DontProxyHeaders.add("proxy-authenticate");
107 _DontProxyHeaders.add("upgrade");
108 }
109
110 protected ServletConfig _config;
111 protected ServletContext _context;
112 protected HostMap<PathMap> _white = new HostMap<PathMap>();
113 protected HostMap<PathMap> _black = new HostMap<PathMap>();
114
115
116
117
118
119
120
121 public void init(ServletConfig config) throws ServletException
122 {
123 _config = config;
124 _context = config.getServletContext();
125
126 _hostHeader = config.getInitParameter("HostHeader");
127
128 try
129 {
130 _log = createLogger(config);
131
132 _client = createHttpClient(config);
133
134 if (_context != null)
135 {
136 _context.setAttribute(config.getServletName() + ".ThreadPool",_client.getThreadPool());
137 _context.setAttribute(config.getServletName() + ".HttpClient",_client);
138 }
139
140 String white = config.getInitParameter("whiteList");
141 if (white != null)
142 {
143 parseList(white,_white);
144 }
145 String black = config.getInitParameter("blackList");
146 if (black != null)
147 {
148 parseList(black,_black);
149 }
150 }
151 catch (Exception e)
152 {
153 throw new ServletException(e);
154 }
155 }
156
157 public void destroy()
158 {
159 try
160 {
161 _client.stop();
162 }
163 catch (Exception x)
164 {
165 _log.debug(x);
166 }
167 }
168
169
170
171
172
173
174
175
176
177 protected Logger createLogger(ServletConfig config)
178 {
179 return Log.getLogger("org.eclipse.jetty.servlets." + config.getServletName());
180 }
181
182
183
184
185
186
187 protected HttpClient createHttpClientInstance()
188 {
189 return new HttpClient();
190 }
191
192
193
194
195
196
197
198
199
200
201
202 protected HttpClient createHttpClient(ServletConfig config) throws Exception
203 {
204 HttpClient client = createHttpClientInstance();
205 client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
206
207 String t = config.getInitParameter("maxThreads");
208
209 if (t != null)
210 {
211 client.setThreadPool(new QueuedThreadPool(Integer.parseInt(t)));
212 }
213 else
214 {
215 client.setThreadPool(new QueuedThreadPool());
216 }
217
218 ((QueuedThreadPool)client.getThreadPool()).setName(config.getServletName());
219
220 t = config.getInitParameter("maxConnections");
221
222 if (t != null)
223 {
224 client.setMaxConnectionsPerAddress(Integer.parseInt(t));
225 }
226
227 t = config.getInitParameter("timeout");
228
229 if ( t != null )
230 {
231 client.setTimeout(Long.parseLong(t));
232 }
233
234 t = config.getInitParameter("idleTimeout");
235
236 if ( t != null )
237 {
238 client.setIdleTimeout(Long.parseLong(t));
239 }
240
241 t = config.getInitParameter("requestHeaderSize");
242
243 if ( t != null )
244 {
245 client.setRequestHeaderSize(Integer.parseInt(t));
246 }
247
248 t = config.getInitParameter("requestBufferSize");
249
250 if ( t != null )
251 {
252 client.setRequestBufferSize(Integer.parseInt(t));
253 }
254
255 t = config.getInitParameter("responseHeaderSize");
256
257 if ( t != null )
258 {
259 client.setResponseHeaderSize(Integer.parseInt(t));
260 }
261
262 t = config.getInitParameter("responseBufferSize");
263
264 if ( t != null )
265 {
266 client.setResponseBufferSize(Integer.parseInt(t));
267 }
268
269 client.start();
270
271 return client;
272 }
273
274
275
276
277
278
279
280
281
282
283 private void parseList(String list, HostMap<PathMap> hostMap)
284 {
285 if (list != null && list.length() > 0)
286 {
287 int idx;
288 String entry;
289
290 StringTokenizer entries = new StringTokenizer(list,",");
291 while (entries.hasMoreTokens())
292 {
293 entry = entries.nextToken();
294 idx = entry.indexOf('/');
295
296 String host = idx > 0?entry.substring(0,idx):entry;
297 String path = idx > 0?entry.substring(idx):"/*";
298
299 host = host.trim();
300 PathMap pathMap = hostMap.get(host);
301 if (pathMap == null)
302 {
303 pathMap = new PathMap(true);
304 hostMap.put(host,pathMap);
305 }
306 if (path != null)
307 {
308 pathMap.put(path,path);
309 }
310 }
311 }
312 }
313
314
315
316
317
318
319
320
321
322
323
324 public boolean validateDestination(String host, String path)
325 {
326 if (_white.size() > 0)
327 {
328 boolean match = false;
329
330 Object whiteObj = _white.getLazyMatches(host);
331 if (whiteObj != null)
332 {
333 List whiteList = (whiteObj instanceof List)?(List)whiteObj:Collections.singletonList(whiteObj);
334
335 for (Object entry : whiteList)
336 {
337 PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
338 if (match = (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null)))
339 break;
340 }
341 }
342
343 if (!match)
344 return false;
345 }
346
347 if (_black.size() > 0)
348 {
349 Object blackObj = _black.getLazyMatches(host);
350 if (blackObj != null)
351 {
352 List blackList = (blackObj instanceof List)?(List)blackObj:Collections.singletonList(blackObj);
353
354 for (Object entry : blackList)
355 {
356 PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
357 if (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null))
358 return false;
359 }
360 }
361 }
362
363 return true;
364 }
365
366
367
368
369
370
371
372 public ServletConfig getServletConfig()
373 {
374 return _config;
375 }
376
377
378
379
380
381
382
383 public String getHostHeader()
384 {
385 return _hostHeader;
386 }
387
388
389
390
391
392
393
394
395 public void setHostHeader(String hostHeader)
396 {
397 _hostHeader = hostHeader;
398 }
399
400
401
402
403
404
405
406 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
407 {
408 final int debug = _log.isDebugEnabled()?req.hashCode():0;
409
410 final HttpServletRequest request = (HttpServletRequest)req;
411 final HttpServletResponse response = (HttpServletResponse)res;
412
413 if ("CONNECT".equalsIgnoreCase(request.getMethod()))
414 {
415 handleConnect(request,response);
416 }
417 else
418 {
419 final InputStream in = request.getInputStream();
420 final OutputStream out = response.getOutputStream();
421
422 final Continuation continuation = ContinuationSupport.getContinuation(request);
423
424 if (!continuation.isInitial())
425 response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT);
426 else
427 {
428
429 String uri = request.getRequestURI();
430 if (request.getQueryString() != null)
431 uri += "?" + request.getQueryString();
432
433 HttpURI url = proxyHttpURI(request,uri);
434
435 if (debug != 0)
436 _log.debug(debug + " proxy " + uri + "-->" + url);
437
438 if (url == null)
439 {
440 response.sendError(HttpServletResponse.SC_FORBIDDEN);
441 return;
442 }
443
444 HttpExchange exchange = new HttpExchange()
445 {
446 @Override
447 protected void onRequestCommitted() throws IOException
448 {
449 }
450
451 @Override
452 protected void onRequestComplete() throws IOException
453 {
454 }
455
456 @Override
457 protected void onResponseComplete() throws IOException
458 {
459 if (debug != 0)
460 _log.debug(debug + " complete");
461 continuation.complete();
462 }
463
464 @Override
465 protected void onResponseContent(Buffer content) throws IOException
466 {
467 if (debug != 0)
468 _log.debug(debug + " content" + content.length());
469 content.writeTo(out);
470 }
471
472 @Override
473 protected void onResponseHeaderComplete() throws IOException
474 {
475 }
476
477 @Override
478 protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
479 {
480 if (debug != 0)
481 _log.debug(debug + " " + version + " " + status + " " + reason);
482
483 if (reason != null && reason.length() > 0)
484 response.setStatus(status,reason.toString());
485 else
486 response.setStatus(status);
487 }
488
489 @Override
490 protected void onResponseHeader(Buffer name, Buffer value) throws IOException
491 {
492 String nameString = name.toString();
493 String s = nameString.toLowerCase(Locale.ENGLISH);
494 if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value)))
495 {
496 if (debug != 0)
497 _log.debug(debug + " " + name + ": " + value);
498
499 String filteredHeaderValue = filterResponseHeaderValue(nameString,value.toString(),request);
500 if (filteredHeaderValue != null && filteredHeaderValue.trim().length() > 0)
501 {
502 if (debug != 0)
503 _log.debug(debug + " " + name + ": (filtered): " + filteredHeaderValue);
504 response.addHeader(nameString,filteredHeaderValue);
505 }
506 }
507 else if (debug != 0)
508 _log.debug(debug + " " + name + "! " + value);
509 }
510
511 @Override
512 protected void onConnectionFailed(Throwable ex)
513 {
514 handleOnConnectionFailed(ex,request,response);
515
516
517
518 if (!continuation.isInitial())
519 {
520 continuation.complete();
521 }
522 }
523
524 @Override
525 protected void onException(Throwable ex)
526 {
527 if (ex instanceof EofException)
528 {
529 _log.ignore(ex);
530
531 }
532 handleOnException(ex,request,response);
533
534
535
536 if (!continuation.isInitial())
537 {
538 continuation.complete();
539 }
540 }
541
542 @Override
543 protected void onExpire()
544 {
545 handleOnExpire(request,response);
546 continuation.complete();
547 }
548
549 };
550
551 exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
552 exchange.setMethod(request.getMethod());
553 exchange.setURL(url.toString());
554 exchange.setVersion(request.getProtocol());
555
556
557 if (debug != 0)
558 _log.debug(debug + " " + request.getMethod() + " " + url + " " + request.getProtocol());
559
560
561 String connectionHdr = request.getHeader("Connection");
562 if (connectionHdr != null)
563 {
564 connectionHdr = connectionHdr.toLowerCase();
565 if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0)
566 connectionHdr = null;
567 }
568
569
570 if (_hostHeader != null)
571 exchange.setRequestHeader("Host",_hostHeader);
572
573
574 boolean xForwardedFor = false;
575 boolean hasContent = false;
576 long contentLength = -1;
577 Enumeration<?> enm = request.getHeaderNames();
578 while (enm.hasMoreElements())
579 {
580
581 String hdr = (String)enm.nextElement();
582 String lhdr = hdr.toLowerCase();
583
584 if ("transfer-encoding".equals(lhdr))
585 {
586 if (request.getHeader("transfer-encoding").indexOf("chunk")>=0)
587 hasContent = true;
588 }
589
590 if (_DontProxyHeaders.contains(lhdr))
591 continue;
592 if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0)
593 continue;
594 if (_hostHeader != null && "host".equals(lhdr))
595 continue;
596
597 if ("content-type".equals(lhdr))
598 hasContent = true;
599 else if ("content-length".equals(lhdr))
600 {
601 contentLength = request.getContentLength();
602 exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength));
603 if (contentLength > 0)
604 hasContent = true;
605 }
606 else if ("x-forwarded-for".equals(lhdr))
607 xForwardedFor = true;
608
609 Enumeration<?> vals = request.getHeaders(hdr);
610 while (vals.hasMoreElements())
611 {
612 String val = (String)vals.nextElement();
613 if (val != null)
614 {
615 if (debug != 0)
616 _log.debug(debug + " " + hdr + ": " + val);
617
618 exchange.setRequestHeader(hdr,val);
619 }
620 }
621 }
622
623
624 exchange.setRequestHeader("Via","1.1 (jetty)");
625 if (!xForwardedFor)
626 {
627 exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr());
628 exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme());
629 exchange.addRequestHeader("X-Forwarded-Host",request.getHeader("Host"));
630 exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName());
631 }
632
633 if (hasContent)
634 {
635 exchange.setRequestContentSource(in);
636 }
637
638 customizeExchange(exchange, request);
639
640
641
642
643
644 long ctimeout = (_client.getTimeout() > exchange.getTimeout()) ? _client.getTimeout() : exchange.getTimeout();
645
646
647
648 if ( ctimeout == 0 )
649 {
650 continuation.setTimeout(0);
651 }
652 else
653 {
654 continuation.setTimeout(ctimeout + 1000);
655 }
656
657 customizeContinuation(continuation);
658
659 continuation.suspend(response);
660 _client.send(exchange);
661
662 }
663 }
664 }
665
666
667 public void handleConnect(HttpServletRequest request, HttpServletResponse response) throws IOException
668 {
669 String uri = request.getRequestURI();
670
671 String port = "";
672 String host = "";
673
674 int c = uri.indexOf(':');
675 if (c >= 0)
676 {
677 port = uri.substring(c + 1);
678 host = uri.substring(0,c);
679 if (host.indexOf('/') > 0)
680 host = host.substring(host.indexOf('/') + 1);
681 }
682
683
684
685 InetSocketAddress inetAddress = new InetSocketAddress(host,Integer.parseInt(port));
686
687
688
689
690
691
692 {
693 InputStream in = request.getInputStream();
694 OutputStream out = response.getOutputStream();
695
696 Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
697
698 response.setStatus(200);
699 response.setHeader("Connection","close");
700 response.flushBuffer();
701
702
703 IO.copyThread(socket.getInputStream(),out);
704 IO.copy(in,socket.getOutputStream());
705 }
706 }
707
708
709 protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
710 {
711 return proxyHttpURI(request.getScheme(), request.getServerName(), request.getServerPort(), uri);
712 }
713
714 protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) throws MalformedURLException
715 {
716 if (!validateDestination(serverName,uri))
717 return null;
718
719 return new HttpURI(scheme + "://" + serverName + ":" + serverPort + uri);
720 }
721
722
723
724
725
726
727 public String getServletInfo()
728 {
729 return "Proxy Servlet";
730 }
731
732
733
734
735
736
737
738
739 protected void customizeExchange(HttpExchange exchange, HttpServletRequest request)
740 {
741
742 }
743
744
745
746
747
748
749
750 protected void customizeContinuation(Continuation continuation)
751 {
752
753 }
754
755
756
757
758
759
760
761
762
763 protected void handleOnConnectionFailed(Throwable ex, HttpServletRequest request, HttpServletResponse response)
764 {
765 handleOnException(ex,request,response);
766 }
767
768
769
770
771
772
773
774
775
776 protected void handleOnException(Throwable ex, HttpServletRequest request, HttpServletResponse response)
777 {
778 if (ex instanceof IOException)
779 {
780 _log.warn(ex.toString());
781 _log.debug(ex);
782 }
783 else
784 _log.warn(ex);
785
786 if (!response.isCommitted())
787 {
788 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
789 }
790 }
791
792
793
794
795
796
797
798
799 protected void handleOnExpire(HttpServletRequest request, HttpServletResponse response)
800 {
801 if (!response.isCommitted())
802 {
803 response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
804 }
805 }
806
807
808
809
810
811
812
813
814
815
816 protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
817 {
818 return headerValue;
819 }
820
821
822
823
824
825
826
827
828
829
830
831
832
833 public static class Transparent extends ProxyServlet
834 {
835 String _prefix;
836 String _proxyTo;
837
838 public Transparent()
839 {
840 }
841
842 public Transparent(String prefix, String host, int port)
843 {
844 this(prefix,"http",host,port,null);
845 }
846
847 public Transparent(String prefix, String schema, String host, int port, String path)
848 {
849 try
850 {
851 if (prefix != null)
852 {
853 _prefix = new URI(prefix).normalize().toString();
854 }
855 _proxyTo = new URI(schema,null,host,port,path,null,null).normalize().toString();
856 }
857 catch (URISyntaxException ex)
858 {
859 _log.debug("Invalid URI syntax",ex);
860 }
861 }
862
863 @Override
864 public void init(ServletConfig config) throws ServletException
865 {
866 super.init(config);
867
868 String prefix = config.getInitParameter("Prefix");
869 _prefix = prefix == null?_prefix:prefix;
870
871
872 String contextPath = _context.getContextPath();
873 _prefix = _prefix == null?contextPath:(contextPath + _prefix);
874
875 String proxyTo = config.getInitParameter("ProxyTo");
876 _proxyTo = proxyTo == null?_proxyTo:proxyTo;
877
878 if (_proxyTo == null)
879 throw new UnavailableException("ProxyTo parameter is requred.");
880
881 if (!_prefix.startsWith("/"))
882 throw new UnavailableException("Prefix parameter must start with a '/'.");
883
884 _log.info(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
885 }
886
887 @Override
888 protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
889 {
890 try
891 {
892 if (!uri.startsWith(_prefix))
893 return null;
894
895 URI dstUri = new URI(_proxyTo + uri.substring(_prefix.length())).normalize();
896
897 if (!validateDestination(dstUri.getHost(),dstUri.getPath()))
898 return null;
899
900 return new HttpURI(dstUri.toString());
901 }
902 catch (URISyntaxException ex)
903 {
904 throw new MalformedURLException(ex.getMessage());
905 }
906 }
907 }
908 }