KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mortbay > http > handler > ProxyHandler


1 // ========================================================================
2
// $Id: ProxyHandler.java,v 1.34 2005/10/05 13:32:59 gregwilkins Exp $
3
// Copyright 1991-2005 Mort Bay Consulting Pty. Ltd.
4
// ------------------------------------------------------------------------
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
// ========================================================================
15

16 package org.mortbay.http.handler;
17
18 import java.io.IOException JavaDoc;
19 import java.io.InputStream JavaDoc;
20 import java.net.HttpURLConnection JavaDoc;
21 import java.net.InetAddress JavaDoc;
22 import java.net.MalformedURLException JavaDoc;
23 import java.net.Socket JavaDoc;
24 import java.net.URL JavaDoc;
25 import java.net.URLConnection JavaDoc;
26 import java.util.Enumeration JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.Set JavaDoc;
29
30 import org.apache.commons.logging.Log;
31 import org.mortbay.log.LogFactory;
32 import org.mortbay.http.HttpConnection;
33 import org.mortbay.http.HttpException;
34 import org.mortbay.http.HttpFields;
35 import org.mortbay.http.HttpMessage;
36 import org.mortbay.http.HttpRequest;
37 import org.mortbay.http.HttpResponse;
38 import org.mortbay.http.HttpTunnel;
39 import org.mortbay.util.IO;
40 import org.mortbay.util.InetAddrPort;
41 import org.mortbay.util.LineInput;
42 import org.mortbay.util.LogSupport;
43 import org.mortbay.util.StringMap;
44 import org.mortbay.util.URI;
45
46 /* ------------------------------------------------------------ */
47 /**
48  * Proxy request handler. A HTTP/1.1 Proxy. This implementation uses the JVMs URL implementation to
49  * make proxy requests.
50  * <P>
51  * The HttpTunnel mechanism is also used to implement the CONNECT method.
52  *
53  * @version $Id: ProxyHandler.java,v 1.34 2005/10/05 13:32:59 gregwilkins Exp $
54  * @author Greg Wilkins (gregw)
55  * @author giacof@tiscali.it (chained proxy)
56  */

57 public class ProxyHandler extends AbstractHttpHandler
58 {
59     private static Log log = LogFactory.getLog(ProxyHandler.class);
60
61     protected Set JavaDoc _proxyHostsWhiteList;
62     protected Set JavaDoc _proxyHostsBlackList;
63     protected int _tunnelTimeoutMs = 250;
64     private boolean _anonymous=false;
65     private transient boolean _chained=false;
66
67     
68     /* ------------------------------------------------------------ */
69     /**
70      * Map of leg by leg headers (not end to end). Should be a set, but more efficient string map is
71      * used instead.
72      */

73     protected StringMap _DontProxyHeaders = new StringMap();
74     {
75         Object JavaDoc o = new Object JavaDoc();
76         _DontProxyHeaders.setIgnoreCase(true);
77         _DontProxyHeaders.put(HttpFields.__ProxyConnection, o);
78         _DontProxyHeaders.put(HttpFields.__Connection, o);
79         _DontProxyHeaders.put(HttpFields.__KeepAlive, o);
80         _DontProxyHeaders.put(HttpFields.__TransferEncoding, o);
81         _DontProxyHeaders.put(HttpFields.__TE, o);
82         _DontProxyHeaders.put(HttpFields.__Trailer, o);
83         _DontProxyHeaders.put(HttpFields.__Upgrade, o);
84     }
85
86     /* ------------------------------------------------------------ */
87     /**
88      * Map of leg by leg headers (not end to end). Should be a set, but more efficient string map is
89      * used instead.
90      */

91     protected StringMap _ProxyAuthHeaders = new StringMap();
92     {
93         Object JavaDoc o = new Object JavaDoc();
94         _ProxyAuthHeaders.put(HttpFields.__ProxyAuthorization, o);
95         _ProxyAuthHeaders.put(HttpFields.__ProxyAuthenticate, o);
96     }
97
98     /* ------------------------------------------------------------ */
99     /**
100      * Map of allows schemes to proxy Should be a set, but more efficient string map is used
101      * instead.
102      */

103     protected StringMap _ProxySchemes = new StringMap();
104     {
105         Object JavaDoc o = new Object JavaDoc();
106         _ProxySchemes.setIgnoreCase(true);
107         _ProxySchemes.put(HttpMessage.__SCHEME, o);
108         _ProxySchemes.put(HttpMessage.__SSL_SCHEME, o);
109         _ProxySchemes.put("ftp", o);
110     }
111
112     /* ------------------------------------------------------------ */
113     /**
114      * Set of allowed CONNECT ports.
115      */

116     protected HashSet JavaDoc _allowedConnectPorts = new HashSet JavaDoc();
117     {
118         _allowedConnectPorts.add(new Integer JavaDoc(80));
119         _allowedConnectPorts.add(new Integer JavaDoc(8000));
120         _allowedConnectPorts.add(new Integer JavaDoc(8080));
121         _allowedConnectPorts.add(new Integer JavaDoc(8888));
122         _allowedConnectPorts.add(new Integer JavaDoc(443));
123         _allowedConnectPorts.add(new Integer JavaDoc(8443));
124     }
125     
126     
127
128     /* ------------------------------------------------------------ */
129     /*
130      */

131     public void start() throws Exception JavaDoc
132     {
133         _chained=System.getProperty("http.proxyHost")!=null;
134         super.start();
135     }
136
137     /* ------------------------------------------------------------ */
138     /**
139      * Get proxy host white list.
140      *
141      * @return Array of hostnames and IPs that are proxied, or an empty array if all hosts are
142      * proxied.
143      */

144     public String JavaDoc[] getProxyHostsWhiteList()
145     {
146         if (_proxyHostsWhiteList == null || _proxyHostsWhiteList.size() == 0)
147             return new String JavaDoc[0];
148
149         String JavaDoc[] hosts = new String JavaDoc[_proxyHostsWhiteList.size()];
150         hosts = (String JavaDoc[]) _proxyHostsWhiteList.toArray(hosts);
151         return hosts;
152     }
153
154     /* ------------------------------------------------------------ */
155     /**
156      * Set proxy host white list.
157      *
158      * @param hosts Array of hostnames and IPs that are proxied, or null if all hosts are proxied.
159      */

160     public void setProxyHostsWhiteList(String JavaDoc[] hosts)
161     {
162         if (hosts == null || hosts.length == 0)
163             _proxyHostsWhiteList = null;
164         else
165         {
166             _proxyHostsWhiteList = new HashSet JavaDoc();
167             for (int i = 0; i < hosts.length; i++)
168                 if (hosts[i] != null && hosts[i].trim().length() > 0)
169                     _proxyHostsWhiteList.add(hosts[i]);
170         }
171     }
172
173     /* ------------------------------------------------------------ */
174     /**
175      * Get proxy host black list.
176      *
177      * @return Array of hostnames and IPs that are NOT proxied.
178      */

179     public String JavaDoc[] getProxyHostsBlackList()
180     {
181         if (_proxyHostsBlackList == null || _proxyHostsBlackList.size() == 0)
182             return new String JavaDoc[0];
183
184         String JavaDoc[] hosts = new String JavaDoc[_proxyHostsBlackList.size()];
185         hosts = (String JavaDoc[]) _proxyHostsBlackList.toArray(hosts);
186         return hosts;
187     }
188
189     /* ------------------------------------------------------------ */
190     /**
191      * Set proxy host black list.
192      *
193      * @param hosts Array of hostnames and IPs that are NOT proxied.
194      */

195     public void setProxyHostsBlackList(String JavaDoc[] hosts)
196     {
197         if (hosts == null || hosts.length == 0)
198             _proxyHostsBlackList = null;
199         else
200         {
201             _proxyHostsBlackList = new HashSet JavaDoc();
202             for (int i = 0; i < hosts.length; i++)
203                 if (hosts[i] != null && hosts[i].trim().length() > 0)
204                     _proxyHostsBlackList.add(hosts[i]);
205         }
206     }
207
208     /* ------------------------------------------------------------ */
209     public int getTunnelTimeoutMs()
210     {
211         return _tunnelTimeoutMs;
212     }
213
214     /* ------------------------------------------------------------ */
215     /**
216      * Tunnel timeout. IE on win2000 has connections issues with normal timeout handling. This
217      * timeout should be set to a low value that will expire to allow IE to see the end of the
218      * tunnel connection.
219      */

220     public void setTunnelTimeoutMs(int ms)
221     {
222         _tunnelTimeoutMs = ms;
223     }
224
225     /* ------------------------------------------------------------ */
226     public void handle(String JavaDoc pathInContext, String JavaDoc pathParams, HttpRequest request, HttpResponse response) throws HttpException, IOException JavaDoc
227     {
228         URI uri = request.getURI();
229
230         // Is this a CONNECT request?
231
if (HttpRequest.__CONNECT.equalsIgnoreCase(request.getMethod()))
232         {
233             response.setField(HttpFields.__Connection, "close"); // TODO Needed for IE????
234
handleConnect(pathInContext, pathParams, request, response);
235             return;
236         }
237
238         try
239         {
240             // Do we proxy this?
241
URL JavaDoc url = isProxied(uri);
242             if (url == null)
243             {
244                 if (isForbidden(uri))
245                     sendForbid(request, response, uri);
246                 return;
247             }
248
249             if (log.isDebugEnabled())
250                 log.debug("PROXY URL=" + url);
251
252             URLConnection JavaDoc connection = url.openConnection();
253             connection.setAllowUserInteraction(false);
254
255             // Set method
256
HttpURLConnection JavaDoc http = null;
257             if (connection instanceof HttpURLConnection JavaDoc)
258             {
259                 http = (HttpURLConnection JavaDoc) connection;
260                 http.setRequestMethod(request.getMethod());
261                 http.setInstanceFollowRedirects(false);
262             }
263
264             // check connection header
265
String JavaDoc connectionHdr = request.getField(HttpFields.__Connection);
266             if (connectionHdr != null && (connectionHdr.equalsIgnoreCase(HttpFields.__KeepAlive) || connectionHdr.equalsIgnoreCase(HttpFields.__Close)))
267                 connectionHdr = null;
268
269             // copy headers
270
boolean xForwardedFor = false;
271             boolean hasContent = false;
272             Enumeration JavaDoc enm = request.getFieldNames();
273             while (enm.hasMoreElements())
274             {
275                 // TODO could be better than this!
276
String JavaDoc hdr = (String JavaDoc) enm.nextElement();
277
278                 if (_DontProxyHeaders.containsKey(hdr) || !_chained && _ProxyAuthHeaders.containsKey(hdr))
279                     continue;
280                 if (connectionHdr != null && connectionHdr.indexOf(hdr) >= 0)
281                     continue;
282
283                 if (HttpFields.__ContentType.equals(hdr))
284                     hasContent = true;
285
286                 Enumeration JavaDoc vals = request.getFieldValues(hdr);
287                 while (vals.hasMoreElements())
288                 {
289                     String JavaDoc val = (String JavaDoc) vals.nextElement();
290                     if (val != null)
291                     {
292                         connection.addRequestProperty(hdr, val);
293                         xForwardedFor |= HttpFields.__XForwardedFor.equalsIgnoreCase(hdr);
294                     }
295                 }
296             }
297
298             // Proxy headers
299
if (!_anonymous)
300                 connection.setRequestProperty("Via", "1.1 (jetty)");
301             if (!xForwardedFor)
302                 connection.addRequestProperty(HttpFields.__XForwardedFor, request.getRemoteAddr());
303
304             // a little bit of cache control
305
String JavaDoc cache_control = request.getField(HttpFields.__CacheControl);
306             if (cache_control != null && (cache_control.indexOf("no-cache") >= 0 || cache_control.indexOf("no-store") >= 0))
307                 connection.setUseCaches(false);
308
309             // customize Connection
310
customizeConnection(pathInContext, pathParams, request, connection);
311
312             try
313             {
314                 connection.setDoInput(true);
315
316                 // do input thang!
317
InputStream JavaDoc in = request.getInputStream();
318                 if (hasContent)
319                 {
320                     connection.setDoOutput(true);
321                     IO.copy(in, connection.getOutputStream());
322                 }
323
324                 // Connect
325
connection.connect();
326             }
327             catch (Exception JavaDoc e)
328             {
329                 LogSupport.ignore(log, e);
330             }
331
332             InputStream JavaDoc proxy_in = null;
333
334             // handler status codes etc.
335
int code = HttpResponse.__500_Internal_Server_Error;
336             if (http != null)
337             {
338                 proxy_in = http.getErrorStream();
339
340                 code = http.getResponseCode();
341                 response.setStatus(code);
342                 response.setReason(http.getResponseMessage());
343             }
344
345             if (proxy_in == null)
346             {
347                 try
348                 {
349                     proxy_in = connection.getInputStream();
350                 }
351                 catch (Exception JavaDoc e)
352                 {
353                     LogSupport.ignore(log, e);
354                     proxy_in = http.getErrorStream();
355                 }
356             }
357
358             // clear response defaults.
359
response.removeField(HttpFields.__Date);
360             response.removeField(HttpFields.__Server);
361
362             // set response headers
363
int h = 0;
364             String JavaDoc hdr = connection.getHeaderFieldKey(h);
365             String JavaDoc val = connection.getHeaderField(h);
366             while (hdr != null || val != null)
367             {
368                 if (hdr != null && val != null && !_DontProxyHeaders.containsKey(hdr) && (_chained || !_ProxyAuthHeaders.containsKey(hdr)))
369                     response.addField(hdr, val);
370                 h++;
371                 hdr = connection.getHeaderFieldKey(h);
372                 val = connection.getHeaderField(h);
373             }
374             if (!_anonymous)
375                 response.setField("Via", "1.1 (jetty)");
376
377             // Handled
378
request.setHandled(true);
379             if (proxy_in != null)
380                 IO.copy(proxy_in, response.getOutputStream());
381
382         }
383         catch (Exception JavaDoc e)
384         {
385             log.warn(e.toString());
386             LogSupport.ignore(log, e);
387             if (!response.isCommitted())
388                 response.sendError(HttpResponse.__400_Bad_Request);
389         }
390     }
391
392     /* ------------------------------------------------------------ */
393     public void handleConnect(String JavaDoc pathInContext, String JavaDoc pathParams, HttpRequest request, HttpResponse response) throws HttpException, IOException JavaDoc
394     {
395         URI uri = request.getURI();
396
397         try
398         {
399             if (log.isDebugEnabled())
400                 log.debug("CONNECT: " + uri);
401             InetAddrPort addrPort = new InetAddrPort(uri.toString());
402
403             if (isForbidden(HttpMessage.__SSL_SCHEME, addrPort.getHost(), addrPort.getPort(), false))
404             {
405                 sendForbid(request, response, uri);
406             }
407             else
408             {
409                 HttpConnection http_connection=request.getHttpConnection();
410                 http_connection.forceClose();
411
412                 // Get the timeout
413
int timeoutMs = 30000;
414                 Object JavaDoc maybesocket = http_connection.getConnection();
415                 if (maybesocket instanceof Socket JavaDoc)
416                 {
417                     Socket JavaDoc s = (Socket JavaDoc) maybesocket;
418                     timeoutMs = s.getSoTimeout();
419                 }
420                 
421                 
422                 // Create the tunnel
423
HttpTunnel tunnel = newHttpTunnel(request,response,addrPort.getInetAddress(), addrPort.getPort(),timeoutMs);
424                 
425                 
426                 if (tunnel!=null)
427                 {
428                     // TODO - need to setup semi-busy loop for IE.
429
if (_tunnelTimeoutMs > 0)
430                     {
431                         tunnel.getSocket().setSoTimeout(_tunnelTimeoutMs);
432                         if (maybesocket instanceof Socket JavaDoc)
433                         {
434                             Socket JavaDoc s = (Socket JavaDoc) maybesocket;
435                             s.setSoTimeout(_tunnelTimeoutMs);
436                         }
437                     }
438                     tunnel.setTimeoutMs(timeoutMs);
439                     
440                     customizeConnection(pathInContext, pathParams, request, tunnel.getSocket());
441                     request.getHttpConnection().setHttpTunnel(tunnel);
442                     response.setStatus(HttpResponse.__200_OK);
443                     response.setContentLength(0);
444                 }
445                 request.setHandled(true);
446             }
447         }
448         catch (Exception JavaDoc e)
449         {
450             LogSupport.ignore(log, e);
451             response.sendError(HttpResponse.__500_Internal_Server_Error);
452         }
453     }
454
455     /* ------------------------------------------------------------ */
456     protected HttpTunnel newHttpTunnel(HttpRequest request, HttpResponse response, InetAddress JavaDoc iaddr, int port, int timeoutMS) throws IOException JavaDoc
457     {
458         try
459         {
460             Socket JavaDoc socket=null;
461             InputStream JavaDoc in=null;
462             
463             String JavaDoc chained_proxy_host=System.getProperty("http.proxyHost");
464             if (chained_proxy_host==null)
465             {
466                 socket= new Socket JavaDoc(iaddr, port);
467                 socket.setSoTimeout(timeoutMS);
468                 socket.setTcpNoDelay(true);
469             }
470             else
471             {
472                 int chained_proxy_port = Integer.getInteger("http.proxyPort", 8888).intValue();
473                 
474                 Socket JavaDoc chain_socket= new Socket JavaDoc(chained_proxy_host, chained_proxy_port);
475                 chain_socket.setSoTimeout(timeoutMS);
476                 chain_socket.setTcpNoDelay(true);
477                 if (log.isDebugEnabled()) log.debug("chain proxy socket="+chain_socket);
478                 
479                 LineInput line_in = new LineInput(chain_socket.getInputStream());
480                 byte[] connect= request.toString().getBytes(org.mortbay.util.StringUtil.__ISO_8859_1);
481                 chain_socket.getOutputStream().write(connect);
482                 
483                 String JavaDoc chain_response_line = line_in.readLine();
484                 HttpFields chain_response = new HttpFields();
485                 chain_response.read(line_in);
486                 
487                 // decode response
488
int space0 = chain_response_line.indexOf(' ');
489                 if (space0>0 && space0+1<chain_response_line.length())
490                 {
491                     int space1 = chain_response_line.indexOf(' ',space0+1);
492                     
493                     if (space1>space0)
494                     {
495                         int code=Integer.parseInt(chain_response_line.substring(space0+1,space1));
496                         
497                         if (code>=200 && code<300)
498                         {
499                             socket=chain_socket;
500                             in=line_in;
501                         }
502                         else
503                         {
504                             Enumeration JavaDoc iter = chain_response.getFieldNames();
505                             while (iter.hasMoreElements())
506                             {
507                                 String JavaDoc name=(String JavaDoc)iter.nextElement();
508                                 if (!_DontProxyHeaders.containsKey(name))
509                                 {
510                                     Enumeration JavaDoc values = chain_response.getValues(name);
511                                     while(values.hasMoreElements())
512                                     {
513                                         String JavaDoc value=(String JavaDoc)values.nextElement();
514                                         response.setField(name, value);
515                                     }
516                                 }
517                             }
518                             response.sendError(code);
519                             if (!chain_socket.isClosed())
520                                 chain_socket.close();
521                         }
522                     }
523                 }
524             }
525             
526             if (socket==null)
527                 return null;
528             HttpTunnel tunnel=new HttpTunnel(socket,in,null);
529             return tunnel;
530         }
531         catch(IOException JavaDoc e)
532         {
533             log.debug(e);
534             response.sendError(HttpResponse.__400_Bad_Request);
535             return null;
536         }
537     }
538     
539     
540     /* ------------------------------------------------------------ */
541     /**
542      * Customize proxy Socket connection for CONNECT. Method to allow derived handlers to customize
543      * the tunnel sockets.
544      *
545      */

546     protected void customizeConnection(String JavaDoc pathInContext, String JavaDoc pathParams, HttpRequest request, Socket JavaDoc socket) throws IOException JavaDoc
547     {
548     }
549
550     /* ------------------------------------------------------------ */
551     /**
552      * Customize proxy URL connection. Method to allow derived handlers to customize the connection.
553      */

554     protected void customizeConnection(String JavaDoc pathInContext, String JavaDoc pathParams, HttpRequest request, URLConnection JavaDoc connection) throws IOException JavaDoc
555     {
556     }
557
558     /* ------------------------------------------------------------ */
559     /**
560      * Is URL Proxied. Method to allow derived handlers to select which URIs are proxied and to
561      * where.
562      *
563      * @param uri The requested URI, which should include a scheme, host and port.
564      * @return The URL to proxy to, or null if the passed URI should not be proxied. The default
565      * implementation returns the passed uri if isForbidden() returns true.
566      */

567     protected URL JavaDoc isProxied(URI uri) throws MalformedURLException JavaDoc
568     {
569         // Is this a proxy request?
570
if (isForbidden(uri))
571             return null;
572
573         // OK return URI as untransformed URL.
574
return new URL JavaDoc(uri.toString());
575     }
576
577     /* ------------------------------------------------------------ */
578     /**
579      * Is URL Forbidden.
580      *
581      * @return True if the URL is not forbidden. Calls isForbidden(scheme,host,port,true);
582      */

583     protected boolean isForbidden(URI uri)
584     {
585         String JavaDoc scheme = uri.getScheme();
586         String JavaDoc host = uri.getHost();
587         int port = uri.getPort();
588         return isForbidden(scheme, host, port, true);
589     }
590
591     /* ------------------------------------------------------------ */
592     /**
593      * Is scheme,host & port Forbidden.
594      *
595      * @param scheme A scheme that mast be in the proxySchemes StringMap.
596      * @param host A host that must pass the white and black lists
597      * @param port A port that must in the allowedConnectPorts Set
598      * @param openNonPrivPorts If true ports greater than 1024 are allowed.
599      * @return True if the request to the scheme,host and port is not forbidden.
600      */

601     protected boolean isForbidden(String JavaDoc scheme, String JavaDoc host, int port, boolean openNonPrivPorts)
602     {
603         // Check port
604
Integer JavaDoc p = new Integer JavaDoc(port);
605         if (port > 0 && !_allowedConnectPorts.contains(p))
606         {
607             if (!openNonPrivPorts || port <= 1024)
608                 return true;
609         }
610
611         // Must be a scheme that can be proxied.
612
if (scheme == null || !_ProxySchemes.containsKey(scheme))
613             return true;
614
615         // Must be in any defined white list
616
if (_proxyHostsWhiteList != null && !_proxyHostsWhiteList.contains(host))
617             return true;
618
619         // Must not be in any defined black list
620
if (_proxyHostsBlackList != null && _proxyHostsBlackList.contains(host))
621             return true;
622
623         return false;
624     }
625
626     /* ------------------------------------------------------------ */
627     /**
628      * Send Forbidden. Method called to send forbidden response. Default implementation calls
629      * sendError(403)
630      */

631     protected void sendForbid(HttpRequest request, HttpResponse response, URI uri) throws IOException JavaDoc
632     {
633         response.sendError(HttpResponse.__403_Forbidden, "Forbidden for Proxy");
634     }
635
636     /* ------------------------------------------------------------ */
637     /**
638      * @return Returns the anonymous.
639      */

640     public boolean isAnonymous()
641     {
642         return _anonymous;
643     }
644
645     /* ------------------------------------------------------------ */
646     /**
647      * @param anonymous The anonymous to set.
648      */

649     public void setAnonymous(boolean anonymous)
650     {
651         _anonymous = anonymous;
652     }
653 }
654
Popular Tags