// ======================================================================== // $Id: HttpTunnel.java,v 1.11 2005/10/05 11:14:37 gregwilkins Exp $ // Copyright 2002-2004 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ======================================================================== package net.lightbody.bmp.proxy.jetty.http; import net.lightbody.bmp.proxy.jetty.log.LogFactory; import net.lightbody.bmp.proxy.jetty.util.IO; import net.lightbody.bmp.proxy.jetty.util.LogSupport; import org.apache.commons.logging.Log; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.Socket; /* ------------------------------------------------------------ */ /** HTTP Tunnel. * A HTTP Tunnel can be used to take over a HTTP connection in order to * tunnel another protocol over it. The prime example is the CONNECT method * handled by the ProxyHandler to setup a SSL tunnel between the client and * the real server. * * @see HttpConnection * @version $Revision: 1.11 $ * @author Greg Wilkins (gregw) */ public class HttpTunnel { private static Log log= LogFactory.getLog(HttpTunnel.class); private Thread _thread; private int _timeoutMs; private Socket _socket; private InputStream _sIn; private OutputStream _sOut; private InputStream _in; private OutputStream _out; /* ------------------------------------------------------------ */ /** Constructor. */ protected HttpTunnel() { } /* ------------------------------------------------------------ */ /** Constructor. * @param socket The tunnel socket. * @param timeoutMs The maximum time to wait for a read on the tunnel. Note that * sotimer exceptions are ignored by the tunnel. * @param in Alternative input stream or null if using normal socket stream * @param out Alternative output stream or null if using normal socket stream * @param timeoutMs * @throws IOException */ public HttpTunnel(Socket socket, InputStream in, OutputStream out) throws IOException { _socket= socket; _sIn=in; _sOut=out; if (_sIn==null) _sIn=_socket.getInputStream(); if (_sOut==null) _sOut=socket.getOutputStream(); _timeoutMs=30000; } /* ------------------------------------------------------------ */ /** handle method. * This method is called by the HttpConnection.handleNext() method if * this HttpTunnel has been set on that connection. * The default implementation of this method copies between the HTTP * socket and the socket passed in the constructor. * @param in * @param out */ public void handle(InputStream in, OutputStream out) { Copy copy= new Copy(); _in= in; _out= out; try { _thread= Thread.currentThread(); copy.start(); copydata(_sIn, _out); } catch (Exception e) { LogSupport.ignore(log, e); } finally { try { _in.close(); if (_socket!=null) { _socket.shutdownOutput(); _socket.close(); } else { _sIn.close(); _sOut.close(); } } catch (Exception e) { LogSupport.ignore(log, e); } copy.interrupt(); } } /* ------------------------------------------------------------ */ private void copydata(InputStream in, OutputStream out) throws java.io.IOException { long timestamp= 0; long byteCount = 0; while (true) { try { byteCount = copyBytes(in, out,-1); timestamp= 0; if (byteCount == -1) { return; } } catch (InterruptedIOException e) { LogSupport.ignore(log, e); if (timestamp == 0) timestamp= System.currentTimeMillis(); else if (_timeoutMs > 0 && (System.currentTimeMillis() - timestamp) > _timeoutMs) throw e; } } } /* ------------------------------------------------------------------- */ /** Copy Stream in to Stream for byteCount bytes or until EOF or exception. * @return Copied bytes count or -1 if no bytes were read *and* EOF was reached */ public static int copyBytes(InputStream in, OutputStream out, long byteCount) throws IOException { byte buffer[] = new byte[IO.bufferSize]; int len=IO.bufferSize; int totalCount=0; if (byteCount>=0) { totalCount=(int)byteCount; while (byteCount>0) { try { if (byteCount<IO.bufferSize) len=in.read(buffer,0,(int)byteCount); else len=in.read(buffer,0,IO.bufferSize); if (len==-1 && totalCount==byteCount) totalCount=(int)byteCount - 1; } catch (InterruptedIOException e) { if (totalCount==byteCount) throw e; LogSupport.ignore(log, e); len=0; } if (len<=0) break; byteCount -= len; out.write(buffer,0,len); } totalCount -= byteCount; } else { while (len>0) { try { len=in.read(buffer,0,IO.bufferSize); if (len==-1 && totalCount==0) totalCount=-1; } catch (InterruptedIOException e) { if (totalCount==0) throw e; LogSupport.ignore(log, e); len=0; } if (len>0) { out.write(buffer,0,len); totalCount += len; } } } return totalCount; } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /** Copy thread. * Helper thread to copy from the HTTP input to the sockets output */ private class Copy extends Thread { public void run() { try { copydata(_in, _sOut); } catch (Exception e) { LogSupport.ignore(log, e); } finally { try { _out.close(); if (_socket!=null) { _socket.shutdownInput(); _socket.close(); } else { _sOut.close(); _sIn.close(); } } catch (Exception e) { LogSupport.ignore(log, e); } _thread.interrupt(); } } } /* ------------------------------------------------------------ */ /** * @return Returns the socket. */ public Socket getSocket() { return _socket; } /* ------------------------------------------------------------ */ /** * @return Returns the timeoutMs. */ public int getTimeoutMs() { return _timeoutMs; } /* ------------------------------------------------------------ */ /** * @param timeoutMs The timeoutMs to set. */ public void setTimeoutMs(int timeoutMs) { _timeoutMs = timeoutMs; } }