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