View Javadoc

1   // ========================================================================
2   // $Id: ProxyServlet.java 6208 2010-10-13 00:39:01Z gregw $
3   // Copyright 2004-2004 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.servlet;
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.OutputStream;
21  import java.net.HttpURLConnection;
22  import java.net.InetSocketAddress;
23  import java.net.MalformedURLException;
24  import java.net.Socket;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.util.Enumeration;
28  import java.util.HashSet;
29  
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.mortbay.util.IO;
41  
42  
43  
44  /**
45   * Proxy Servlet.
46   * <p>
47   * Forward requests to another server either as a standard web proxy (as defined by
48   * RFC2616) or as a transparent proxy.
49   *
50   */
51  public class ProxyServlet implements Servlet
52  {
53      
54      protected HashSet _DontProxyHeaders = new HashSet();
55      {
56          _DontProxyHeaders.add("proxy-connection");
57          _DontProxyHeaders.add("connection");
58          _DontProxyHeaders.add("keep-alive");
59          _DontProxyHeaders.add("transfer-encoding");
60          _DontProxyHeaders.add("te");
61          _DontProxyHeaders.add("trailer");
62          _DontProxyHeaders.add("proxy-authorization");
63          _DontProxyHeaders.add("proxy-authenticate");
64          _DontProxyHeaders.add("upgrade");
65      }
66      
67      protected ServletConfig _config;
68      protected ServletContext _context;
69      
70      /* (non-Javadoc)
71       * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
72       */
73      public void init(ServletConfig config) throws ServletException
74      {
75          this._config=config;
76          this._context=config.getServletContext();
77      }
78  
79      /* (non-Javadoc)
80       * @see javax.servlet.Servlet#getServletConfig()
81       */
82      public ServletConfig getServletConfig()
83      {
84          return _config;
85      }
86  
87      /* (non-Javadoc)
88       * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
89       */
90      public void service(ServletRequest req, ServletResponse res) throws ServletException,
91              IOException
92      {
93          HttpServletRequest request = (HttpServletRequest)req;
94          HttpServletResponse response = (HttpServletResponse)res;
95          if ("CONNECT".equalsIgnoreCase(request.getMethod()))
96          {
97              handleConnect(request,response);
98          }
99          else
100         {
101             String uri=request.getRequestURI();
102             if (request.getQueryString()!=null)
103                 uri+="?"+request.getQueryString();
104             
105             URL url=proxyHttpURL(request.getScheme(),
106                     request.getServerName(),
107                     request.getServerPort(),
108                     uri);
109             
110 
111             URLConnection connection = url.openConnection();
112             connection.setAllowUserInteraction(false);
113             
114             // Set method
115             HttpURLConnection http = null;
116             if (connection instanceof HttpURLConnection)
117             {
118                 http = (HttpURLConnection)connection;
119                 http.setRequestMethod(request.getMethod());
120                 http.setInstanceFollowRedirects(false);
121             }
122 
123             // check connection header
124             String connectionHdr = request.getHeader("Connection");
125             if (connectionHdr!=null)
126             {
127                 connectionHdr=connectionHdr.toLowerCase();
128                 if (connectionHdr.equals("keep-alive")||
129                     connectionHdr.equals("close"))
130                     connectionHdr=null;
131             }
132             
133             // copy headers
134             boolean xForwardedFor=false;
135             boolean hasContent=false;
136             Enumeration enm = request.getHeaderNames();
137             while (enm.hasMoreElements())
138             {
139                 // TODO could be better than this!
140                 String hdr=(String)enm.nextElement();
141                 String lhdr=hdr.toLowerCase();
142 
143                 if (_DontProxyHeaders.contains(lhdr))
144                     continue;
145                 if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0)
146                     continue;
147 
148                 if ("content-type".equals(lhdr))
149                     hasContent=true;
150 
151                 Enumeration vals = request.getHeaders(hdr);
152                 while (vals.hasMoreElements())
153                 {
154                     String val = (String)vals.nextElement();
155                     if (val!=null)
156                     {
157                         connection.addRequestProperty(hdr,val);
158                         xForwardedFor|="X-Forwarded-For".equalsIgnoreCase(hdr);
159                     }
160                 }
161             }
162 
163             // Proxy headers
164             connection.setRequestProperty("Via","1.1 (jetty)");
165             if (!xForwardedFor)
166             {
167                 connection.addRequestProperty("X-Forwarded-For",
168                         request.getRemoteAddr());
169                 connection.addRequestProperty("X-Forwarded-Proto",
170                         request.getScheme());
171                 connection.addRequestProperty("X-Forwarded-Host",
172                         request.getServerName());
173                 connection.addRequestProperty("X-Forwarded-Server",
174                         request.getLocalName());
175             }
176             
177             // a little bit of cache control
178             String cache_control = request.getHeader("Cache-Control");
179             if (cache_control!=null &&
180                 (cache_control.indexOf("no-cache")>=0 ||
181                  cache_control.indexOf("no-store")>=0))
182                 connection.setUseCaches(false);
183 
184             // customize Connection
185             
186             try
187             {    
188                 connection.setDoInput(true);
189                 
190                 // do input thang!
191                 InputStream in=request.getInputStream();
192                 if (hasContent)
193                 {
194                     connection.setDoOutput(true);
195                     IO.copy(in,connection.getOutputStream());
196                 }
197                 
198                 // Connect                
199                 connection.connect();    
200             }
201             catch (Exception e)
202             {
203                 _context.log("proxy",e);
204             }
205             
206             InputStream proxy_in = null;
207 
208             // handler status codes etc.
209             int code=500;
210             if (http!=null)
211             {
212                 proxy_in = http.getErrorStream();
213                 
214                 code=http.getResponseCode();
215                 response.setStatus(code, http.getResponseMessage());
216             }
217             
218             if (proxy_in==null)
219             {
220                 try {proxy_in=connection.getInputStream();}
221                 catch (Exception e)
222                 {
223                     _context.log("stream",e);
224                     proxy_in = http.getErrorStream();
225                 }
226             }
227             
228             // clear response defaults.
229             response.setHeader("Date",null);
230             response.setHeader("Server",null);
231             
232             // set response headers
233             int h=0;
234             String hdr=connection.getHeaderFieldKey(h);
235             String val=connection.getHeaderField(h);
236             while(hdr!=null || val!=null)
237             {
238                 String lhdr = hdr!=null?hdr.toLowerCase():null;
239                 if (hdr!=null && val!=null && !_DontProxyHeaders.contains(lhdr))
240                     response.addHeader(hdr,val);
241                 
242                 h++;
243                 hdr=connection.getHeaderFieldKey(h);
244                 val=connection.getHeaderField(h);
245             }
246             response.addHeader("Via","1.1 (jetty)");
247 
248             // Handle
249             if (proxy_in!=null)
250                 IO.copy(proxy_in,response.getOutputStream());
251             
252         }
253     }
254 
255     /* ------------------------------------------------------------ */
256     /** Resolve requested URL to the Proxied URL
257      * @param scheme The scheme of the received request.
258      * @param serverName The server encoded in the received request(which 
259      * may be from an absolute URL in the request line).
260      * @param serverPort The server port of the received request (which 
261      * may be from an absolute URL in the request line).
262      * @param uri The URI of the received request.
263      * @return The URL to which the request should be proxied.
264      * @throws MalformedURLException
265      */
266     protected URL proxyHttpURL(String scheme, String serverName, int serverPort, String uri)
267         throws MalformedURLException
268     {
269         return new URL(scheme,serverName,serverPort,uri);
270     }
271     
272     /* ------------------------------------------------------------ */
273     public void handleConnect(HttpServletRequest request,
274                               HttpServletResponse response)
275         throws IOException
276     {
277         String uri = request.getRequestURI();
278         
279         String port = "";
280         String host = "";
281         
282         int c = uri.indexOf(':');
283         if (c>=0)
284         {
285             port = uri.substring(c+1);
286             host = uri.substring(0,c);
287             if (host.indexOf('/')>0)
288                 host = host.substring(host.indexOf('/')+1);
289         }
290 
291         
292        
293 
294         InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
295         
296         //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
297         //{
298         //    sendForbid(request,response,uri);
299         //}
300         //else
301         {
302             InputStream in=request.getInputStream();
303             OutputStream out=response.getOutputStream();
304             
305             Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
306             
307             response.setStatus(200);
308             response.setHeader("Connection","close");
309             response.flushBuffer();
310             
311             IO.copyThread(socket.getInputStream(),out);
312             IO.copy(in,socket.getOutputStream());
313         }
314     }
315     
316     
317     
318     
319     /* (non-Javadoc)
320      * @see javax.servlet.Servlet#getServletInfo()
321      */
322     public String getServletInfo()
323     {
324         return "Proxy Servlet";
325     }
326 
327     /* (non-Javadoc)
328      * @see javax.servlet.Servlet#destroy()
329      */
330     public void destroy()
331     {
332 
333     }
334     /**
335      * Transparent Proxy.
336      * 
337      * This convenience extension to AsyncProxyServlet configures the servlet
338      * as a transparent proxy.   The servlet is configured with init parameter:<ul>
339      * <li> ProxyTo - a URI like http://host:80/context to which the request is proxied.
340      * <li> Prefix  - a URI prefix that is striped from the start of the forwarded URI.
341      * </ul>
342      * For example, if a request was received at /foo/bar and the ProxyTo was  http://host:80/context
343      * and the Prefix was /foo, then the request would be proxied to http://host:80/context/bar
344      *
345      */
346     public static class Transparent extends ProxyServlet
347     {
348         String _prefix;
349         String _proxyTo;
350         
351         public Transparent()
352         {    
353         }
354         
355         public Transparent(String prefix,String server, int port)
356         {
357             _prefix=prefix;
358             _proxyTo="http://"+server+":"+port;
359         }
360 
361         public void init(ServletConfig config) throws ServletException
362         {
363             if (config.getInitParameter("ProxyTo")!=null)
364                 _proxyTo=config.getInitParameter("ProxyTo");
365             if (config.getInitParameter("Prefix")!=null)
366                 _prefix=config.getInitParameter("Prefix");
367             if (_proxyTo==null)
368                 throw new UnavailableException("No ProxyTo");
369             super.init(config);
370             
371             _context.log("Transparent ProxyServlet @ "+(_prefix==null?"-":_prefix)+ " to "+_proxyTo);
372             
373         }
374         
375         protected URL proxyHttpURL(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
376         {
377             if (_prefix!=null && !uri.startsWith(_prefix))
378                 return null;
379             
380             if (_prefix!=null)
381                 return new URL(_proxyTo+uri.substring(_prefix.length()));
382             return new URL(_proxyTo+uri);
383         }
384     }
385 
386 }