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