1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.servlets;
15
16
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.net.InetSocketAddress;
21 import java.net.MalformedURLException;
22 import java.net.Socket;
23 import java.net.URI;
24 import java.net.URISyntaxException;
25 import java.util.Collections;
26 import java.util.Enumeration;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.StringTokenizer;
31
32 import javax.servlet.Servlet;
33 import javax.servlet.ServletConfig;
34 import javax.servlet.ServletContext;
35 import javax.servlet.ServletException;
36 import javax.servlet.ServletRequest;
37 import javax.servlet.ServletResponse;
38 import javax.servlet.UnavailableException;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41
42 import org.eclipse.jetty.client.HttpClient;
43 import org.eclipse.jetty.client.HttpExchange;
44 import org.eclipse.jetty.continuation.Continuation;
45 import org.eclipse.jetty.continuation.ContinuationSupport;
46 import org.eclipse.jetty.http.HttpHeaderValues;
47 import org.eclipse.jetty.http.HttpHeaders;
48 import org.eclipse.jetty.http.HttpSchemes;
49 import org.eclipse.jetty.http.HttpURI;
50 import org.eclipse.jetty.http.PathMap;
51 import org.eclipse.jetty.io.Buffer;
52 import org.eclipse.jetty.io.EofException;
53 import org.eclipse.jetty.util.HostMap;
54 import org.eclipse.jetty.util.IO;
55 import org.eclipse.jetty.util.log.Log;
56 import org.eclipse.jetty.util.log.Logger;
57 import org.eclipse.jetty.util.thread.QueuedThreadPool;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 public class ProxyServlet implements Servlet
82 {
83 protected Logger _log;
84 protected HttpClient _client;
85 protected String _hostHeader;
86
87 protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
88 {
89 _DontProxyHeaders.add("proxy-connection");
90 _DontProxyHeaders.add("connection");
91 _DontProxyHeaders.add("keep-alive");
92 _DontProxyHeaders.add("transfer-encoding");
93 _DontProxyHeaders.add("te");
94 _DontProxyHeaders.add("trailer");
95 _DontProxyHeaders.add("proxy-authorization");
96 _DontProxyHeaders.add("proxy-authenticate");
97 _DontProxyHeaders.add("upgrade");
98 }
99
100 protected ServletConfig _config;
101 protected ServletContext _context;
102 protected HostMap<PathMap> _white = new HostMap<PathMap>();
103 protected HostMap<PathMap> _black = new HostMap<PathMap>();
104
105
106
107
108
109 public void init(ServletConfig config) throws ServletException
110 {
111 _config=config;
112 _context=config.getServletContext();
113
114 _client=new HttpClient();
115 _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
116
117 _hostHeader=config.getInitParameter("HostHeader");
118
119
120 try
121 {
122 _log= Log.getLogger("org.eclipse.jetty.servlets."+config.getServletName());
123
124 String t = config.getInitParameter("maxThreads");
125 if (t!=null)
126 _client.setThreadPool(new QueuedThreadPool(Integer.parseInt(t)));
127 else
128 _client.setThreadPool(new QueuedThreadPool());
129 ((QueuedThreadPool)_client.getThreadPool()).setName(config.getServletName());
130
131 t = config.getInitParameter("maxConnections");
132 if (t!=null)
133 _client.setMaxConnectionsPerAddress(Integer.parseInt(t));
134
135 _client.start();
136
137 if (_context!=null)
138 {
139 _context.setAttribute(config.getServletName()+".Logger",_log);
140 _context.setAttribute(config.getServletName()+".ThreadPool",_client.getThreadPool());
141 _context.setAttribute(config.getServletName()+".HttpClient",_client);
142 }
143
144 String white = config.getInitParameter("whiteList");
145 if (white != null)
146 {
147 parseList(white, _white);
148 }
149 String black = config.getInitParameter("blackList");
150 if (black != null)
151 {
152 parseList(black, _black);
153 }
154 }
155 catch (Exception e)
156 {
157 throw new ServletException(e);
158 }
159 }
160
161
162
163
164
165
166
167
168
169 private void parseList(String list, HostMap<PathMap> hostMap)
170 {
171 if (list != null && list.length() > 0)
172 {
173 int idx;
174 String entry;
175
176 StringTokenizer entries = new StringTokenizer(list, ",");
177 while(entries.hasMoreTokens())
178 {
179 entry = entries.nextToken();
180 idx = entry.indexOf('/');
181
182 String host = idx > 0 ? entry.substring(0,idx) : entry;
183 String path = idx > 0 ? entry.substring(idx) : "/*";
184
185 host = host.trim();
186 PathMap pathMap = hostMap.get(host);
187 if (pathMap == null)
188 {
189 pathMap = new PathMap(true);
190 hostMap.put(host,pathMap);
191 }
192 if (path != null)
193 {
194 pathMap.put(path,path);
195 }
196 }
197 }
198 }
199
200
201
202
203
204
205
206
207
208 public boolean validateDestination(String host, String path)
209 {
210 if (_white.size()>0)
211 {
212 boolean match = false;
213
214 Object whiteObj = _white.getLazyMatches(host);
215 if (whiteObj != null)
216 {
217 List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
218
219 for (Object entry: whiteList)
220 {
221 PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
222 if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
223 break;
224 }
225 }
226
227 if (!match)
228 return false;
229 }
230
231 if (_black.size() > 0)
232 {
233 Object blackObj = _black.getLazyMatches(host);
234 if (blackObj != null)
235 {
236 List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
237
238 for (Object entry: blackList)
239 {
240 PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
241 if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
242 return false;
243 }
244 }
245 }
246
247 return true;
248 }
249
250
251
252
253
254 public ServletConfig getServletConfig()
255 {
256 return _config;
257 }
258
259
260
261
262
263
264 public String getHostHeader()
265 {
266 return _hostHeader;
267 }
268
269
270
271
272
273 public void setHostHeader(String hostHeader)
274 {
275 _hostHeader = hostHeader;
276 }
277
278
279
280
281
282 public void service(ServletRequest req, ServletResponse res) throws ServletException,
283 IOException
284 {
285 final int debug=_log.isDebugEnabled()?req.hashCode():0;
286
287 final HttpServletRequest request = (HttpServletRequest)req;
288 final HttpServletResponse response = (HttpServletResponse)res;
289 if ("CONNECT".equalsIgnoreCase(request.getMethod()))
290 {
291 handleConnect(request,response);
292 }
293 else
294 {
295 final InputStream in=request.getInputStream();
296 final OutputStream out=response.getOutputStream();
297
298 final Continuation continuation = ContinuationSupport.getContinuation(request);
299
300 if (!continuation.isInitial())
301 response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT);
302 else
303 {
304 String uri=request.getRequestURI();
305 if (request.getQueryString()!=null)
306 uri+="?"+request.getQueryString();
307
308 HttpURI url=proxyHttpURI(request.getScheme(),
309 request.getServerName(),
310 request.getServerPort(),
311 uri);
312
313 if (debug!=0)
314 _log.debug(debug+" proxy "+uri+"-->"+url);
315
316 if (url==null)
317 {
318 response.sendError(HttpServletResponse.SC_FORBIDDEN);
319 return;
320 }
321
322 HttpExchange exchange = new HttpExchange()
323 {
324 protected void onRequestCommitted() throws IOException
325 {
326 }
327
328 protected void onRequestComplete() throws IOException
329 {
330 }
331
332 protected void onResponseComplete() throws IOException
333 {
334 if (debug!=0)
335 _log.debug(debug+" complete");
336 continuation.complete();
337 }
338
339 protected void onResponseContent(Buffer content) throws IOException
340 {
341 if (debug!=0)
342 _log.debug(debug+" content"+content.length());
343 content.writeTo(out);
344 }
345
346 protected void onResponseHeaderComplete() throws IOException
347 {
348 }
349
350 protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
351 {
352 if (debug!=0)
353 _log.debug(debug+" "+version+" "+status+" "+reason);
354
355 if (reason!=null && reason.length()>0)
356 response.setStatus(status,reason.toString());
357 else
358 response.setStatus(status);
359 }
360
361 protected void onResponseHeader(Buffer name, Buffer value) throws IOException
362 {
363 String s = name.toString().toLowerCase();
364 if (!_DontProxyHeaders.contains(s) ||
365 (HttpHeaders.CONNECTION_BUFFER.equals(name) &&
366 HttpHeaderValues.CLOSE_BUFFER.equals(value)))
367 {
368 if (debug!=0)
369 _log.debug(debug+" "+name+": "+value);
370
371 response.addHeader(name.toString(),value.toString());
372 }
373 else if (debug!=0)
374 _log.debug(debug+" "+name+"! "+value);
375 }
376
377 protected void onConnectionFailed(Throwable ex)
378 {
379 onException(ex);
380 }
381
382 protected void onException(Throwable ex)
383 {
384 if (ex instanceof EofException)
385 {
386 Log.ignore(ex);
387 return;
388 }
389 Log.warn(ex.toString());
390 Log.debug(ex);
391 if (!response.isCommitted())
392 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
393 continuation.complete();
394 }
395
396 protected void onExpire()
397 {
398 if (!response.isCommitted())
399 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
400 continuation.complete();
401 }
402
403 };
404
405 exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
406 exchange.setMethod(request.getMethod());
407 exchange.setURL(url.toString());
408 exchange.setVersion(request.getProtocol());
409
410 if (debug!=0)
411 _log.debug(debug+" "+request.getMethod()+" "+url+" "+request.getProtocol());
412
413
414 String connectionHdr = request.getHeader("Connection");
415 if (connectionHdr!=null)
416 {
417 connectionHdr=connectionHdr.toLowerCase();
418 if (connectionHdr.indexOf("keep-alive")<0 &&
419 connectionHdr.indexOf("close")<0)
420 connectionHdr=null;
421 }
422
423
424 if (_hostHeader!=null)
425 exchange.setRequestHeader("Host",_hostHeader);
426
427
428 boolean xForwardedFor=false;
429 boolean hasContent=false;
430 long contentLength=-1;
431 Enumeration<?> enm = request.getHeaderNames();
432 while (enm.hasMoreElements())
433 {
434
435 String hdr=(String)enm.nextElement();
436 String lhdr=hdr.toLowerCase();
437
438 if (_DontProxyHeaders.contains(lhdr))
439 continue;
440 if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0)
441 continue;
442 if (_hostHeader!=null && "host".equals(lhdr))
443 continue;
444
445 if ("content-type".equals(lhdr))
446 hasContent=true;
447 else if ("content-length".equals(lhdr))
448 {
449 contentLength=request.getContentLength();
450 exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength));
451 if (contentLength>0)
452 hasContent=true;
453 }
454 else if ("x-forwarded-for".equals(lhdr))
455 xForwardedFor=true;
456
457 Enumeration<?> vals = request.getHeaders(hdr);
458 while (vals.hasMoreElements())
459 {
460 String val = (String)vals.nextElement();
461 if (val!=null)
462 {
463 if (debug!=0)
464 _log.debug(debug+" "+hdr+": "+val);
465
466 exchange.setRequestHeader(hdr,val);
467 }
468 }
469 }
470
471
472 exchange.setRequestHeader("Via","1.1 (jetty)");
473 if (!xForwardedFor)
474 exchange.addRequestHeader("X-Forwarded-For",
475 request.getRemoteAddr());
476
477 if (hasContent)
478 exchange.setRequestContentSource(in);
479
480 continuation.suspend(response);
481 _client.send(exchange);
482
483 }
484 }
485 }
486
487
488
489 public void handleConnect(HttpServletRequest request,
490 HttpServletResponse response)
491 throws IOException
492 {
493 String uri = request.getRequestURI();
494
495 String port = "";
496 String host = "";
497
498 int c = uri.indexOf(':');
499 if (c>=0)
500 {
501 port = uri.substring(c+1);
502 host = uri.substring(0,c);
503 if (host.indexOf('/')>0)
504 host = host.substring(host.indexOf('/')+1);
505 }
506
507
508
509
510 InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
511
512
513
514
515
516
517 {
518 InputStream in=request.getInputStream();
519 OutputStream out=response.getOutputStream();
520
521 Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
522
523 response.setStatus(200);
524 response.setHeader("Connection","close");
525 response.flushBuffer();
526
527
528 IO.copyThread(socket.getInputStream(),out);
529 IO.copy(in,socket.getOutputStream());
530 }
531 }
532
533
534 protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri)
535 throws MalformedURLException
536 {
537 if (!validateDestination(serverName, uri))
538 return null;
539
540 return new HttpURI(scheme+"://"+serverName+":"+serverPort+uri);
541 }
542
543
544
545
546
547 public String getServletInfo()
548 {
549 return "Proxy Servlet";
550 }
551
552
553
554
555 public void destroy()
556 {
557
558 }
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573 public static class Transparent extends ProxyServlet
574 {
575 String _prefix;
576 String _proxyTo;
577
578 public Transparent()
579 {
580 }
581
582 public Transparent(String prefix, String host, int port)
583 {
584 this(prefix,"http",host,port,null);
585 }
586
587 public Transparent(String prefix, String schema, String host, int port, String path)
588 {
589 try
590 {
591 if (prefix != null)
592 {
593 _prefix = new URI(prefix).normalize().toString();
594 }
595 _proxyTo = new URI(schema,null,host,port,path,null,null).normalize().toString();
596 }
597 catch (URISyntaxException ex)
598 {
599 _log.debug("Invalid URI syntax",ex);
600 }
601 }
602
603 @Override
604 public void init(ServletConfig config) throws ServletException
605 {
606 super.init(config);
607
608 String prefix = config.getInitParameter("Prefix");
609 _prefix = prefix == null?_prefix:prefix;
610
611
612 String contextPath = _context.getContextPath();
613 _prefix = _prefix == null?contextPath:(contextPath + _prefix);
614
615 String proxyTo = config.getInitParameter("ProxyTo");
616 _proxyTo = proxyTo == null?_proxyTo:proxyTo;
617
618 if (_proxyTo == null)
619 throw new UnavailableException("ProxyTo parameter is requred.");
620
621 if (!_prefix.startsWith("/"))
622 throw new UnavailableException("Prefix parameter must start with a '/'.");
623
624 _log.info(config.getServletName()+" @ " + _prefix + " to " + _proxyTo);
625 }
626
627 @Override
628 protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
629 {
630 try
631 {
632 if (!uri.startsWith(_prefix))
633 return null;
634
635 URI dstUri = new URI(_proxyTo + uri.substring(_prefix.length())).normalize();
636
637 if (!validateDestination(dstUri.getHost(),dstUri.getPath()))
638 return null;
639
640 return new HttpURI(dstUri.toString());
641 }
642 catch (URISyntaxException ex)
643 {
644 throw new MalformedURLException(ex.getMessage());
645 }
646 }
647 }
648 }