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