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