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