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