View Javadoc

1   // ========================================================================
2   // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.proxy;
16  
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.OutputStream;
21  import java.net.InetSocketAddress;
22  import java.net.MalformedURLException;
23  import java.net.Socket;
24  import java.net.URL;
25  import java.util.Enumeration;
26  import java.util.HashSet;
27  
28  import javax.servlet.Servlet;
29  import javax.servlet.ServletConfig;
30  import javax.servlet.ServletContext;
31  import javax.servlet.ServletException;
32  import javax.servlet.ServletRequest;
33  import javax.servlet.ServletResponse;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.mortbay.io.Buffer;
38  import org.mortbay.jetty.Connector;
39  import org.mortbay.jetty.Handler;
40  import org.mortbay.jetty.HttpSchemes;
41  import org.mortbay.jetty.HttpURI;
42  import org.mortbay.jetty.Server;
43  import org.mortbay.jetty.bio.SocketConnector;
44  import org.mortbay.jetty.client.Address;
45  import org.mortbay.jetty.client.HttpClient;
46  import org.mortbay.jetty.client.HttpExchange;
47  import org.mortbay.jetty.handler.ContextHandlerCollection;
48  import org.mortbay.jetty.handler.DefaultHandler;
49  import org.mortbay.jetty.handler.HandlerCollection;
50  import org.mortbay.jetty.servlet.Context;
51  import org.mortbay.jetty.servlet.ServletHolder;
52  import org.mortbay.jetty.webapp.WebAppContext;
53  import org.mortbay.util.IO;
54  import org.mortbay.util.ajax.Continuation;
55  import org.mortbay.util.ajax.ContinuationSupport;
56  
57  
58  
59  /**
60   * EXPERIMENTAL Proxy servlet.
61   * @author gregw
62   *
63   */
64  public class AsyncProxyServlet implements Servlet
65  {
66      HttpClient _client;
67  
68      protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
69      {
70          _DontProxyHeaders.add("proxy-connection");
71          _DontProxyHeaders.add("connection");
72          _DontProxyHeaders.add("keep-alive");
73          _DontProxyHeaders.add("transfer-encoding");
74          _DontProxyHeaders.add("te");
75          _DontProxyHeaders.add("trailer");
76          _DontProxyHeaders.add("proxy-authorization");
77          _DontProxyHeaders.add("proxy-authenticate");
78          _DontProxyHeaders.add("upgrade");
79      }
80  
81      private ServletConfig config;
82      private ServletContext context;
83  
84      /* (non-Javadoc)
85       * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
86       */
87      public void init(ServletConfig config) throws ServletException
88      {
89          this.config=config;
90          this.context=config.getServletContext();
91  
92          _client=new HttpClient();
93          //_client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
94          _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
95          try
96          {
97              _client.start();
98          }
99          catch (Exception e)
100         {
101             throw new ServletException(e);
102         }
103     }
104 
105     /* (non-Javadoc)
106      * @see javax.servlet.Servlet#getServletConfig()
107      */
108     public ServletConfig getServletConfig()
109     {
110         return config;
111     }
112 
113     /* (non-Javadoc)
114      * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
115      */
116     public void service(ServletRequest req, ServletResponse res) throws ServletException,
117             IOException
118     {
119         final HttpServletRequest request = (HttpServletRequest)req;
120         final HttpServletResponse response = (HttpServletResponse)res;
121         if ("CONNECT".equalsIgnoreCase(request.getMethod()))
122         {
123             handleConnect(request,response);
124         }
125         else
126         {
127             final InputStream in=request.getInputStream();
128             final OutputStream out=response.getOutputStream();
129             final Continuation continuation = ContinuationSupport.getContinuation(request,request);
130 
131 
132             if (!continuation.isPending())
133             {
134                 final byte[] buffer = new byte[4096]; // TODO avoid this!
135                 String uri=request.getRequestURI();
136                 if (request.getQueryString()!=null)
137                     uri+="?"+request.getQueryString();
138 
139                 HttpURI url=proxyHttpURI(request.getScheme(),
140                         request.getServerName(),
141                         request.getServerPort(),
142                         uri);
143                 
144                 if (url==null)
145                 {
146                     response.sendError(HttpServletResponse.SC_FORBIDDEN);
147                     return;
148                 }
149 
150                 HttpExchange exchange = new HttpExchange()
151                 {
152 
153                     protected void onRequestCommitted() throws IOException
154                     {
155                     }
156 
157                     protected void onRequestComplete() throws IOException
158                     {
159                     }
160 
161                     protected void onResponseComplete() throws IOException
162                     {
163                         continuation.resume();
164                     }
165 
166                     protected void onResponseContent(Buffer content) throws IOException
167                     {
168                         // TODO Avoid this copy
169                         while (content.hasContent())
170                         {
171                             int len=content.get(buffer,0,buffer.length);
172                             out.write(buffer,0,len);  // May block here for a little bit!
173                         }
174                     }
175 
176                     protected void onResponseHeaderComplete() throws IOException
177                     {
178                     }
179 
180                     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
181                     {
182                         if (reason!=null && reason.length()>0)
183                             response.setStatus(status,reason.toString());
184                         else
185                             response.setStatus(status);
186 
187                     }
188 
189                     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
190                     {
191                         String s = name.toString().toLowerCase();
192                         if (!_DontProxyHeaders.contains(s))
193                             response.addHeader(name.toString(),value.toString());
194                     }
195 
196                 };
197                 
198                 exchange.setVersion(request.getProtocol());
199                 exchange.setMethod(request.getMethod());
200                 
201                 exchange.setURL(url.toString());
202                 
203                 // check connection header
204                 String connectionHdr = request.getHeader("Connection");
205                 if (connectionHdr!=null)
206                 {
207                     connectionHdr=connectionHdr.toLowerCase();
208                     if (connectionHdr.indexOf("keep-alive")<0  &&
209                             connectionHdr.indexOf("close")<0)
210                         connectionHdr=null;
211                 }
212 
213                 // copy headers
214                 boolean xForwardedFor=false;
215                 boolean hasContent=false;
216                 long contentLength=-1;
217                 Enumeration enm = request.getHeaderNames();
218                 while (enm.hasMoreElements())
219                 {
220                     // TODO could be better than this!
221                     String hdr=(String)enm.nextElement();
222                     String lhdr=hdr.toLowerCase();
223 
224                     if (_DontProxyHeaders.contains(lhdr))
225                         continue;
226                     if (connectionHdr!=null && connectionHdr.indexOf(lhdr)>=0)
227                         continue;
228 
229                     if ("content-type".equals(lhdr))
230                         hasContent=true;
231                     if ("content-length".equals(lhdr))
232                         contentLength=request.getContentLength();
233 
234                     Enumeration vals = request.getHeaders(hdr);
235                     while (vals.hasMoreElements())
236                     {
237                         String val = (String)vals.nextElement();
238                         if (val!=null)
239                         {
240                             exchange.setRequestHeader(lhdr,val);
241                             xForwardedFor|="X-Forwarded-For".equalsIgnoreCase(hdr);
242                         }
243                     }
244                 }
245 
246                 // Proxy headers
247                 exchange.setRequestHeader("Via","1.1 (jetty)");
248                 if (!xForwardedFor)
249                     exchange.addRequestHeader("X-Forwarded-For",
250                             request.getRemoteAddr());
251 
252                 if (hasContent)
253                     exchange.setRequestContentSource(in);
254 
255                 _client.send(exchange);
256 
257                 continuation.suspend(30000);
258             }
259         }
260     }
261 
262 
263     /* ------------------------------------------------------------ */
264     protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri)
265         throws MalformedURLException
266     {
267         return new HttpURI(scheme+"://"+serverName+":"+serverPort+uri);
268     }
269 
270     /* ------------------------------------------------------------ */
271     public void handleConnect(HttpServletRequest request,
272                               HttpServletResponse response)
273         throws IOException
274     {
275         String uri = request.getRequestURI();
276 
277         String port = "";
278         String host = "";
279 
280         int c = uri.indexOf(':');
281         if (c>=0)
282         {
283             port = uri.substring(c+1);
284             host = uri.substring(0,c);
285             if (host.indexOf('/')>0)
286                 host = host.substring(host.indexOf('/')+1);
287         }
288 
289         InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
290 
291         //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
292         //{
293         //    sendForbid(request,response,uri);
294         //}
295         //else
296         {
297             InputStream in=request.getInputStream();
298             OutputStream out=response.getOutputStream();
299 
300             Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
301 
302             response.setStatus(200);
303             response.setHeader("Connection","close");
304             response.flushBuffer();
305 
306 
307 
308             IO.copyThread(socket.getInputStream(),out);
309             IO.copy(in,socket.getOutputStream());
310         }
311     }
312 
313 
314 
315 
316     /* (non-Javadoc)
317      * @see javax.servlet.Servlet#getServletInfo()
318      */
319     public String getServletInfo()
320     {
321         return "Proxy Servlet";
322     }
323 
324     /* (non-Javadoc)
325      * @see javax.servlet.Servlet#destroy()
326      */
327     public void destroy()
328     {
329 
330     }
331     
332     public static class Transparent extends AsyncProxyServlet
333     {
334         String _prefix;
335         String _server;
336         int _port;
337         
338         public Transparent()
339         {    
340         }
341         
342         public Transparent(String prefix,String server, int port)
343         {
344             _prefix=prefix;
345             _server=server;
346             _port=port;
347         }
348         
349         protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
350         {
351             if (!uri.startsWith(_prefix))
352                 return null;
353             HttpURI url = super.proxyHttpURI(scheme,_server,_port,uri.substring(_prefix.length()));
354             return url;
355         }
356     }
357 
358 
359     public static void main(String[] args)
360         throws Exception
361     {
362         Server proxy = new Server();
363         //SelectChannelConnector connector = new SelectChannelConnector();
364         Connector connector = new SocketConnector();
365         connector.setPort(8888);
366         proxy.addConnector(connector);
367         Context context = new Context(proxy,"/",0);
368         context.addServlet(new ServletHolder(new AsyncProxyServlet.Transparent("","www.google.com",80)), "/");
369 
370         proxy.start();
371         proxy.join();
372     }
373 }