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.Socket;
23 import java.util.Enumeration;
24 import java.util.HashSet;
25 
26 import javax.servlet.Servlet;
27 import javax.servlet.ServletConfig;
28 import javax.servlet.ServletContext;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletRequest;
31 import javax.servlet.ServletResponse;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.servlet.http.HttpServletResponse;
34 
35 import org.mortbay.io.Buffer;
36 import org.mortbay.jetty.Connector;
37 import org.mortbay.jetty.Handler;
38 import org.mortbay.jetty.HttpSchemes;
39 import org.mortbay.jetty.Server;
40 import org.mortbay.jetty.bio.SocketConnector;
41 import org.mortbay.jetty.client.Address;
42 import org.mortbay.jetty.client.HttpClient;
43 import org.mortbay.jetty.client.HttpExchange;
44 import org.mortbay.jetty.handler.ContextHandlerCollection;
45 import org.mortbay.jetty.handler.DefaultHandler;
46 import org.mortbay.jetty.handler.HandlerCollection;
47 import org.mortbay.jetty.servlet.Context;
48 import org.mortbay.jetty.servlet.ServletHolder;
49 import org.mortbay.jetty.webapp.WebAppContext;
50 import org.mortbay.util.IO;
51 import org.mortbay.util.ajax.Continuation;
52 import org.mortbay.util.ajax.ContinuationSupport;
53 
54 
55 
56 /**
57  * EXPERIMENTAL Proxy servlet.
58  * @author gregw
59  *
60  */
61 public class AsyncProxyServlet implements Servlet
62 {
63     HttpClient _client;
64 
65     protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
66     {
67         _DontProxyHeaders.add("proxy-connection");
68         _DontProxyHeaders.add("connection");
69         _DontProxyHeaders.add("keep-alive");
70         _DontProxyHeaders.add("transfer-encoding");
71         _DontProxyHeaders.add("te");
72         _DontProxyHeaders.add("trailer");
73         _DontProxyHeaders.add("proxy-authorization");
74         _DontProxyHeaders.add("proxy-authenticate");
75         _DontProxyHeaders.add("upgrade");
76     }
77 
78     private ServletConfig config;
79     private ServletContext context;
80 
81     /* (non-Javadoc)
82      * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
83      */
init(ServletConfig config)84     public void init(ServletConfig config) throws ServletException
85     {
86         this.config=config;
87         this.context=config.getServletContext();
88 
89         _client=new HttpClient();
90         //_client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
91         _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
92         try
93         {
94             _client.start();
95         }
96         catch (Exception e)
97         {
98             throw new ServletException(e);
99         }
100     }
101 
102     /* (non-Javadoc)
103      * @see javax.servlet.Servlet#getServletConfig()
104      */
getServletConfig()105     public ServletConfig getServletConfig()
106     {
107         return config;
108     }
109 
110     /* (non-Javadoc)
111      * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
112      */
service(ServletRequest req, ServletResponse res)113     public void service(ServletRequest req, ServletResponse res) throws ServletException,
114             IOException
115     {
116         final HttpServletRequest request = (HttpServletRequest)req;
117         final HttpServletResponse response = (HttpServletResponse)res;
118         if ("CONNECT".equalsIgnoreCase(request.getMethod()))
119         {
120             handleConnect(request,response);
121         }
122         else
123         {
124             final InputStream in=request.getInputStream();
125             final OutputStream out=response.getOutputStream();
126             final Continuation continuation = ContinuationSupport.getContinuation(request,request);
127 
128 
129             if (!continuation.isPending())
130             {
131                 final byte[] buffer = new byte[4096]; // TODO avoid this!
132                 String uri=request.getRequestURI();
133                 if (request.getQueryString()!=null)
134                     uri+="?"+request.getQueryString();
135 
136 
137                 HttpExchange exchange = new HttpExchange()
138                 {
139 
140                     protected void onRequestCommitted() throws IOException
141                     {
142                         System.err.println("onRequestCommitted()");
143                     }
144 
145                     protected void onRequestComplete() throws IOException
146                     {
147                         System.err.println("onRequestComplete()");
148                     }
149 
150                     protected void onResponseComplete() throws IOException
151                     {
152                         System.err.println("onResponseComplete()");
153                         continuation.resume();
154                     }
155 
156                     protected void onResponseContent(Buffer content) throws IOException
157                     {
158                         System.err.println("onResponseContent()");
159                         // TODO Avoid this copy
160                         while (content.hasContent())
161                         {
162                             int len=content.get(buffer,0,buffer.length);
163                             out.write(buffer,0,len);  // May block here for a little bit!
164                         }
165                     }
166 
167                     protected void onResponseHeaderComplete() throws IOException
168                     {
169                         System.err.println("onResponseCompleteHeader()");
170                     }
171 
172                     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
173                     {
174                         System.err.println("onResponseStatus("+version+","+status+","+reason+")");
175                         if (reason!=null && reason.length()>0)
176                             response.setStatus(status,reason.toString());
177                         else
178                             response.setStatus(status);
179 
180                     }
181 
182                     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
183                     {
184                         System.err.println("onResponseHeader("+name+","+value+")");
185                         String s = name.toString().toLowerCase();
186                         if (!_DontProxyHeaders.contains(s))
187                             response.addHeader(name.toString(),value.toString());
188                     }
189 
190                 };
191 
192                 exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
193                 exchange.setMethod(request.getMethod());
194                 exchange.setURI(uri);
195 
196                 exchange.setVersion(request.getProtocol());
197                 Address address=new Address(request.getServerName(),request.getServerPort());
198                 exchange.setAddress(address);
199 
200                 System.err.println("PROXY TO http://"+address.getHost()+":"+address.getPort()+uri);
201 
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 
265 
266     /* ------------------------------------------------------------ */
handleConnect(HttpServletRequest request, HttpServletResponse response)267     public void handleConnect(HttpServletRequest request,
268                               HttpServletResponse response)
269         throws IOException
270     {
271         String uri = request.getRequestURI();
272 
273         context.log("CONNECT: "+uri);
274 
275         String port = "";
276         String host = "";
277 
278         int c = uri.indexOf(':');
279         if (c>=0)
280         {
281             port = uri.substring(c+1);
282             host = uri.substring(0,c);
283             if (host.indexOf('/')>0)
284                 host = host.substring(host.indexOf('/')+1);
285         }
286 
287 
288 
289 
290         InetSocketAddress inetAddress = new InetSocketAddress (host, Integer.parseInt(port));
291 
292         //if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
293         //{
294         //    sendForbid(request,response,uri);
295         //}
296         //else
297         {
298             InputStream in=request.getInputStream();
299             OutputStream out=response.getOutputStream();
300 
301             Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
302             context.log("Socket: "+socket);
303 
304             response.setStatus(200);
305             response.setHeader("Connection","close");
306             response.flushBuffer();
307 
308 
309 
310             context.log("out<-in");
311             IO.copyThread(socket.getInputStream(),out);
312             context.log("in->out");
313             IO.copy(in,socket.getOutputStream());
314         }
315     }
316 
317 
318 
319 
320     /* (non-Javadoc)
321      * @see javax.servlet.Servlet#getServletInfo()
322      */
getServletInfo()323     public String getServletInfo()
324     {
325         return "Proxy Servlet";
326     }
327 
328     /* (non-Javadoc)
329      * @see javax.servlet.Servlet#destroy()
330      */
destroy()331     public void destroy()
332     {
333 
334     }
335 
336 
main(String[] args)337     public static void main(String[] args)
338         throws Exception
339     {
340         Server server = new Server(8080);
341         HandlerCollection handlers = new HandlerCollection();
342         ContextHandlerCollection contexts = new ContextHandlerCollection();
343         handlers.setHandlers(new Handler[]{contexts,new DefaultHandler()});
344         server.setHandler(handlers);
345         WebAppContext webapp = new WebAppContext();
346         webapp.setContextPath("/test");
347         webapp.setResourceBase("../../webapps/test");
348         contexts.addHandler(webapp);
349         server.start();
350 
351         Server proxy = new Server();
352         //SelectChannelConnector connector = new SelectChannelConnector();
353         Connector connector = new SocketConnector();
354         connector.setPort(8888);
355         proxy.addConnector(connector);
356         Context context = new Context(proxy,"/",0);
357         context.addServlet(new ServletHolder(new AsyncProxyServlet()), "/");
358 
359         proxy.start();
360         proxy.join();
361     }
362 }
363