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