package com.netifera.platform.net.http.tools; import java.io.IOException; import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; import org.apache.http.ConnectionReuseStrategy; import org.apache.http.Header; import org.apache.http.HttpConnection; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpInetConnection; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseFactory; import org.apache.http.HttpStatus; import org.apache.http.HttpVersion; import org.apache.http.ProtocolVersion; import org.apache.http.impl.DefaultConnectionReuseStrategy; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.nio.DefaultClientIOEventDispatch; import org.apache.http.impl.nio.DefaultServerIOEventDispatch; import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor; import org.apache.http.nio.ContentDecoder; import org.apache.http.nio.ContentEncoder; import org.apache.http.nio.IOControl; import org.apache.http.nio.NHttpClientConnection; import org.apache.http.nio.NHttpClientHandler; import org.apache.http.nio.NHttpConnection; import org.apache.http.nio.NHttpServerConnection; import org.apache.http.nio.NHttpServiceHandler; import org.apache.http.nio.reactor.ConnectingIOReactor; import org.apache.http.nio.reactor.IOEventDispatch; import org.apache.http.nio.reactor.ListeningIOReactor; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpParams; import org.apache.http.protocol.BasicHttpProcessor; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpProcessor; import org.apache.http.protocol.RequestConnControl; import org.apache.http.protocol.RequestContent; import org.apache.http.protocol.RequestExpectContinue; import org.apache.http.protocol.RequestTargetHost; import org.apache.http.protocol.ResponseConnControl; import org.apache.http.protocol.ResponseContent; import org.apache.http.protocol.ResponseDate; import org.apache.http.protocol.ResponseServer; import com.netifera.platform.api.probe.IProbe; import com.netifera.platform.api.tools.ITool; import com.netifera.platform.api.tools.IToolContext; import com.netifera.platform.api.tools.ToolException; import com.netifera.platform.net.http.internal.tools.Activator; import com.netifera.platform.tools.RequiredOptionMissingException; import com.netifera.platform.util.addresses.inet.InternetAddress; import com.netifera.platform.util.locators.TCPSocketLocator; public class HTTPProxy implements ITool { // public final static String TOOL_NAME = "HTTP Proxy"; // public final static String TOOL_DESCRIPTION = "Proxy that collects visited URLs and rewrites HTML responses to show hidden fields."; private IToolContext context; private int listeningPort; private long realm; public void toolRun(IToolContext context) throws ToolException { this.context = context; // XXX hardcode local probe as realm IProbe probe = Activator.getInstance().getProbeManager().getLocalProbe(); realm = probe.getEntity().getId(); setupToolOptions(); try { context.info("HTTP proxy listening on port "+listeningPort); listen(listeningPort); //} catch (InterruptedException e) { // Thread.currentThread().interrupt(); // //context.warning(e.getMessage()); } catch (IOException e) { context.exception(e.getMessage(), e); } } private void setupToolOptions() throws ToolException { Integer port = (Integer) context.getConfiguration().get("port"); if (port == null) throw new RequiredOptionMissingException("port"); listeningPort = port.intValue(); } private void interceptResponse(NHttpClientConnection conn, HttpRequest request, HttpResponse response) { URI url; try { url = new URI(request.getRequestLine().getUri()); } catch (URISyntaxException e) { context.debug("bad URI: " + request.getRequestLine().getUri()); return; } InetAddress address = ((HttpInetConnection)conn).getRemoteAddress(); int port = ((HttpInetConnection)conn).getRemotePort(); TCPSocketLocator locator = new TCPSocketLocator(InternetAddress.fromInetAddress(address), port); if (response.containsHeader("Server")) Activator.getInstance().getWebEntityFactory().createWebServer(realm, context.getSpaceId(), locator, response.getFirstHeader("Server").getValue()); else Activator.getInstance().getWebEntityFactory().createWebServer(realm, context.getSpaceId(), locator, null); HttpEntity entity = response.getEntity(); int status = response.getStatusLine().getStatusCode(); if (status < 200 || status >= 400) { context.debug("inconsistant: " + request.getRequestLine()+" -> "+response.getStatusLine().toString()); } else { context.debug(request.getRequestLine()+" -> "+response.getStatusLine().toString()); if (status == 200) { String contentType = entity.getContentType().getValue(); Activator.getInstance().getWebEntityFactory().createWebPage(realm, context.getSpaceId(), locator, url, contentType); // is favicon? get it and add it to the model /* if (url.getPath().equals("/favicon.ico") && contentType.matches("image/x-icon|application/octet-stream")) { entity.consumeContent(); byte[] favicon = new byte[(int)entity.getContentLength()]; entity.getContent().read(favicon); messenger.WebSiteFaviconDiscovered(service, url, favicon); } */ // System.err.println(contentType); } else if (status >= 300) { String location = response.getFirstHeader("Location").getValue(); context.info("Redirect "+url+" to "+location); } } } public void listen(int port) throws IOException { HttpParams params = new BasicHttpParams(); params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 30000) .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024).setBooleanParameter( CoreConnectionPNames.STALE_CONNECTION_CHECK, false) .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true) .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "HttpComponents/1.1")/*.setParameter( CoreProtocolPNames.USER_AGENT, "HttpComponents/1.1")*/; final ConnectingIOReactor connectingIOReactor = new DefaultConnectingIOReactor( 1, params); final ListeningIOReactor listeningIOReactor = new DefaultListeningIOReactor( 1, params); BasicHttpProcessor originServerProc = new BasicHttpProcessor(); originServerProc.addInterceptor(new RequestContent()); originServerProc.addInterceptor(new RequestTargetHost()); originServerProc.addInterceptor(new RequestConnControl()); // originServerProc.addInterceptor(new RequestUserAgent()); originServerProc.addInterceptor(new RequestExpectContinue()); BasicHttpProcessor clientProxyProcessor = new BasicHttpProcessor(); clientProxyProcessor.addInterceptor(new ResponseDate()); clientProxyProcessor.addInterceptor(new ResponseServer()); clientProxyProcessor.addInterceptor(new ResponseContent()); clientProxyProcessor.addInterceptor(new ResponseConnControl()); NHttpClientHandler connectingHandler = new ConnectingHandler( originServerProc, new DefaultConnectionReuseStrategy(), params); NHttpServiceHandler listeningHandler = new ListeningHandler( connectingIOReactor, clientProxyProcessor, new DefaultHttpResponseFactory(), new DefaultConnectionReuseStrategy(), params); final IOEventDispatch connectingEventDispatch = new DefaultClientIOEventDispatch( connectingHandler, params); final IOEventDispatch listeningEventDispatch = new DefaultServerIOEventDispatch( listeningHandler, params); Thread t = new Thread(new Runnable() { public void run() { try { connectingIOReactor.execute(connectingEventDispatch); } catch (InterruptedIOException ex) { context.warning("Interrupted"); } catch (IOException e) { context.exception("I/O error: " + e.getMessage(), e); } } }); t.start(); listeningIOReactor.listen(new InetSocketAddress(port)); listeningIOReactor.execute(listeningEventDispatch); } class ListeningHandler implements NHttpServiceHandler { // private final HttpHost targetHost; private final ConnectingIOReactor connectingIOReactor; private final HttpProcessor httpProcessor; private final HttpResponseFactory responseFactory; private final ConnectionReuseStrategy connStrategy; private final HttpParams params; public ListeningHandler( final ConnectingIOReactor connectingIOReactor, final HttpProcessor httpProcessor, final HttpResponseFactory responseFactory, final ConnectionReuseStrategy connStrategy, final HttpParams params) { super(); this.connectingIOReactor = connectingIOReactor; this.httpProcessor = httpProcessor; this.connStrategy = connStrategy; this.responseFactory = responseFactory; this.params = params; } public void connected(final NHttpServerConnection conn) { context.debug(conn + ": client conn open"); } public void requestReceived(final NHttpServerConnection conn) { context.debug(conn + ": client conn request received"); /* HttpContext context = conn.getContext(); ProxyTask proxyTask = (ProxyTask) context .getAttribute(ProxyTask.ATTRIB); */ ProxyTask proxyTask = new ProxyTask(); synchronized (proxyTask) { // Initialize connection state // proxyTask.setTarget(this.targetHost); proxyTask.setClientIOControl(conn); proxyTask.setClientState(ProxyTask.CONNECTED); HttpContext httpContext = conn.getContext(); httpContext.setAttribute(ProxyTask.ATTRIB, proxyTask); /*****************************/ HttpRequest request = conn.getHttpRequest(); URI url; try { url = new URI(request.getRequestLine().getUri()); } catch (URISyntaxException e) { context.debug("bad URI: " + request.getRequestLine().getUri()); return; } String host = url.getHost(); int port = url.getPort(); if (port < 0) port = 80; InetSocketAddress address = new InetSocketAddress(host, port); /* TCPServiceLocator service = new TCPServiceLocator(InternetAddress.fromInetAddress(address.getAddress()), port); context.setAttribute("service-locator", service); context.setAttribute("url", url); */ /************************************/ this.connectingIOReactor.connect(address, null, proxyTask, null); /* } synchronized (proxyTask) { // Validate connection state if (proxyTask.getClientState() != ProxyTask.IDLE && proxyTask.getClientState() != ProxyTask.CONNECTED) { throw new IllegalStateException("Illegal connection state"); } */ try { // HttpRequest request = conn.getHttpRequest(); context.debug(conn + ": [client] >> " + request.getRequestLine().toString()); Header[] headers = request.getAllHeaders(); for (int i = 0; i < headers.length; i++) { context.debug(conn + ": [client] >> " + headers[i].toString()); } ProtocolVersion ver = request.getRequestLine() .getProtocolVersion(); if (!ver.lessEquals(HttpVersion.HTTP_1_1)) { // Downgrade protocol version if greater than HTTP/1.1 ver = HttpVersion.HTTP_1_1; } // FIXME should rewrite the request, change http://x/ to / // Update connection state proxyTask.setRequest(request); proxyTask.setClientState(ProxyTask.REQUEST_RECEIVED); // See if the client expects a 100-Continue if (request instanceof HttpEntityEnclosingRequest) { if (((HttpEntityEnclosingRequest) request) .expectContinue()) { HttpResponse ack = this.responseFactory .newHttpResponse(ver, HttpStatus.SC_CONTINUE, httpContext); conn.submitResponse(ack); } } else { // No request content expected. Suspend client input conn.suspendInput(); } // If there is already a connection to the origin server // make sure origin output is active if (proxyTask.getOriginIOControl() != null) { proxyTask.getOriginIOControl().requestOutput(); } } catch (IOException ex) { shutdownConnection(conn); } catch (HttpException ex) { shutdownConnection(conn); } } } public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) { context.debug(conn + ": client conn input ready " + decoder); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); synchronized (proxyTask) { // Validate connection state if (proxyTask.getClientState() != ProxyTask.REQUEST_RECEIVED && proxyTask.getClientState() != ProxyTask.REQUEST_BODY_STREAM) { throw new IllegalStateException("Illegal connection state"); } try { ByteBuffer dst = proxyTask.getInBuffer(); int bytesRead = decoder.read(dst); context.debug(conn + ": " + bytesRead + " bytes read"); if (!dst.hasRemaining()) { // Input buffer is full. Suspend client input // until the origin handler frees up some space in the // buffer conn.suspendInput(); } // If there is some content in the input buffer make sure // origin // output is active if (dst.position() > 0) { if (proxyTask.getOriginIOControl() != null) { proxyTask.getOriginIOControl().requestOutput(); } } if (decoder.isCompleted()) { context.debug(conn + ": client conn request body received"); // Update connection state proxyTask.setClientState(ProxyTask.REQUEST_BODY_DONE); // Suspend client input conn.suspendInput(); } else { proxyTask.setClientState(ProxyTask.REQUEST_BODY_STREAM); } } catch (IOException ex) { shutdownConnection(conn); } } } public void responseReady(final NHttpServerConnection conn) { context.debug(conn + ": client conn response ready"); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); synchronized (proxyTask) { if (proxyTask.getClientState() == ProxyTask.IDLE) { // Response not available return; } // Validate connection state if (proxyTask.getClientState() != ProxyTask.REQUEST_RECEIVED && proxyTask.getClientState() != ProxyTask.REQUEST_BODY_DONE) { throw new IllegalStateException("Illegal connection state"); } try { HttpRequest request = proxyTask.getRequest(); HttpResponse response = proxyTask.getResponse(); if (response == null) { throw new IllegalStateException("HTTP response is null"); } // Remove connection specific headers response.removeHeaders(HTTP.CONTENT_LEN); response.removeHeaders(HTTP.TRANSFER_ENCODING); response.removeHeaders(HTTP.CONN_DIRECTIVE); response.removeHeaders("Keep-Alive"); response.setParams(this.params); // Pre-process HTTP request httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn); httpContext.setAttribute(ExecutionContext.HTTP_REQUEST, request); this.httpProcessor.process(response, httpContext); conn.submitResponse(response); proxyTask.setClientState(ProxyTask.RESPONSE_SENT); context.debug(conn + ": [proxy] << " + response.getStatusLine().toString()); Header[] headers = response.getAllHeaders(); for (int i = 0; i < headers.length; i++) { context.debug(conn + ": [proxy] << " + headers[i].toString()); } //---------- here if (!canResponseHaveBody(request, response)) { conn.resetInput(); if (!this.connStrategy.keepAlive(response, httpContext)) { conn.close(); } else { // Reset connection state proxyTask.reset(); conn.requestInput(); // Ready to deal with a new request } } } catch (IOException ex) { shutdownConnection(conn); } catch (HttpException ex) { shutdownConnection(conn); } } } private boolean canResponseHaveBody(final HttpRequest request, final HttpResponse response) { if (request != null && "HEAD".equalsIgnoreCase(request.getRequestLine() .getMethod())) { return false; } int status = response.getStatusLine().getStatusCode(); return status >= HttpStatus.SC_OK && status != HttpStatus.SC_NO_CONTENT && status != HttpStatus.SC_NOT_MODIFIED && status != HttpStatus.SC_RESET_CONTENT; } public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) { context.debug(conn + ": client conn output ready " + encoder); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); synchronized (proxyTask) { // Validate connection state if (proxyTask.getClientState() != ProxyTask.RESPONSE_SENT && proxyTask.getClientState() != ProxyTask.RESPONSE_BODY_STREAM) { throw new IllegalStateException("Illegal connection state"); } HttpResponse response = proxyTask.getResponse(); if (response == null) { throw new IllegalStateException("HTTP request is null"); } try { ByteBuffer src = proxyTask.getOutBuffer(); src.flip(); if (src.hasRemaining()) { int bytesWritten = encoder.write(src); context.debug(conn + ": " + bytesWritten + " bytes written"); } src.compact(); if (src.position() == 0) { if (proxyTask.getOriginState() == ProxyTask.RESPONSE_BODY_DONE) { encoder.complete(); } else { // Input output is empty. Wait until the origin // handler // fills up the buffer conn.suspendOutput(); } } // Update connection state if (encoder.isCompleted()) { context.debug(conn + ": client conn response body sent"); proxyTask.setClientState(ProxyTask.RESPONSE_BODY_DONE); if (!this.connStrategy.keepAlive(response, httpContext)) { conn.close(); } else { // Reset connection state proxyTask.reset(); conn.requestInput(); // Ready to deal with a new request } } else { proxyTask .setOriginState(ProxyTask.RESPONSE_BODY_STREAM); // Make sure origin input is active proxyTask.getOriginIOControl().requestInput(); } } catch (IOException ex) { shutdownConnection(conn); } } } public void closed(final NHttpServerConnection conn) { context.debug(conn + ": client conn closed"); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); if (proxyTask != null) { synchronized (proxyTask) { IOControl ioControl = proxyTask.getOriginIOControl(); if (ioControl != null) { try { ioControl.shutdown(); } catch (IOException ex) { // ignore } } } } } public void exception(final NHttpServerConnection conn, final HttpException httpex) { context.debug(conn + ": " + httpex.getMessage()); HttpContext httpContext = conn.getContext(); try { HttpResponse response = this.responseFactory.newHttpResponse( HttpVersion.HTTP_1_0, HttpStatus.SC_BAD_REQUEST, httpContext); response.setParams(this.params); response.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE); // Pre-process HTTP request httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION, conn); httpContext.setAttribute(ExecutionContext.HTTP_REQUEST, null); this.httpProcessor.process(response, httpContext); conn.submitResponse(response); conn.close(); } catch (IOException ex) { shutdownConnection(conn); } catch (HttpException ex) { shutdownConnection(conn); } } public void exception(final NHttpServerConnection conn, final IOException ex) { shutdownConnection(conn); context.debug(conn + ": " + ex.getMessage()); } public void timeout(final NHttpServerConnection conn) { context.debug(conn + ": timeout"); // FIXME info? shutdownConnection(conn); } private void shutdownConnection(final NHttpConnection conn) { try { conn.shutdown(); } catch (IOException ignore) { } } } class ConnectingHandler implements NHttpClientHandler { private final HttpProcessor httpProcessor; private final ConnectionReuseStrategy connStrategy; private final HttpParams params; public ConnectingHandler(final HttpProcessor httpProcessor, final ConnectionReuseStrategy connStrategy, final HttpParams params) { super(); this.httpProcessor = httpProcessor; this.connStrategy = connStrategy; this.params = params; } public void connected(final NHttpClientConnection conn, final Object attachment) { context.debug(conn + ": origin conn open"); // The shared state object is expected to be passed as an attachment ProxyTask proxyTask = (ProxyTask) attachment; synchronized (proxyTask) { // Validate connection state if (proxyTask.getOriginState() != ProxyTask.IDLE) { throw new IllegalStateException("Illegal connection state"); } // Set origin IO control handle proxyTask.setOriginIOControl(conn); // Store the state object in the context HttpContext context = conn.getContext(); context.setAttribute(ProxyTask.ATTRIB, proxyTask); // Update connection state proxyTask.setOriginState(ProxyTask.CONNECTED); if (proxyTask.getRequest() != null) { conn.requestOutput(); } } } public void requestReady(final NHttpClientConnection conn) { context.debug(conn + ": origin conn request ready"); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); synchronized (proxyTask) { // Validate connection state if (proxyTask.getOriginState() == ProxyTask.REQUEST_SENT) { // Request sent but no response available yet return; } if (proxyTask.getOriginState() != ProxyTask.IDLE && proxyTask.getOriginState() != ProxyTask.CONNECTED) { throw new IllegalStateException("Illegal connection state"); } HttpRequest request = proxyTask.getRequest(); if (request == null) { throw new IllegalStateException("HTTP request is null"); } // Remove connection specific headers request.removeHeaders(HTTP.CONTENT_LEN); request.removeHeaders(HTTP.TRANSFER_ENCODING); request.removeHeaders(HTTP.TARGET_HOST); request.removeHeaders(HTTP.CONN_DIRECTIVE); request.removeHeaders(HTTP.USER_AGENT); request.removeHeaders("Keep-Alive"); HttpHost targetHost = proxyTask.getTarget(); try { // FIXME // should change the URL from http://google.com/ to / request.setParams(this.params); // Pre-process HTTP request httpContext.setAttribute(ExecutionContext.HTTP_CONNECTION,conn); httpContext.setAttribute(ExecutionContext.HTTP_TARGET_HOST,targetHost); this.httpProcessor.process(request, httpContext); // and send it to the origin server conn.submitRequest(request); // Update connection state proxyTask.setOriginState(ProxyTask.REQUEST_SENT); context.debug(conn + ": [proxy] >> " + request.getRequestLine().toString()); Header[] headers = request.getAllHeaders(); for (int i = 0; i < headers.length; i++) { context.debug(conn + ": [proxy] >> " + headers[i].toString()); } } catch (IOException ex) { shutdownConnection(conn); } catch (HttpException ex) { shutdownConnection(conn); } } } public void outputReady(final NHttpClientConnection conn, final ContentEncoder encoder) { context.debug(conn + ": origin conn output ready " + encoder); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); synchronized (proxyTask) { // Validate connection state if (proxyTask.getOriginState() != ProxyTask.REQUEST_SENT && proxyTask.getOriginState() != ProxyTask.REQUEST_BODY_STREAM) { throw new IllegalStateException("Illegal connection state"); } try { ByteBuffer src = proxyTask.getInBuffer(); src.flip(); if (src.hasRemaining()) { int bytesWritten = encoder.write(src); context.debug(conn + ": " + bytesWritten + " bytes written"); } src.compact(); if (src.position() == 0) { if (proxyTask.getClientState() == ProxyTask.REQUEST_BODY_DONE) { encoder.complete(); } else { // Input buffer is empty. Wait until the client // fills up // the buffer conn.suspendOutput(); } } // Update connection state if (encoder.isCompleted()) { context.debug(conn + ": origin conn request body sent"); proxyTask.setOriginState(ProxyTask.REQUEST_BODY_DONE); } else { proxyTask.setOriginState(ProxyTask.REQUEST_BODY_STREAM); // Make sure client input is active proxyTask.getClientIOControl().requestInput(); } } catch (IOException ex) { shutdownConnection(conn); } } } public void responseReceived(final NHttpClientConnection conn) { context.debug(conn + ": origin conn response received"); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); synchronized (proxyTask) { // Validate connection state if (proxyTask.getOriginState() != ProxyTask.REQUEST_SENT && proxyTask.getOriginState() != ProxyTask.REQUEST_BODY_DONE) { throw new IllegalStateException("Illegal connection state"); } HttpResponse response = conn.getHttpResponse(); HttpRequest request = proxyTask.getRequest(); context.debug(conn + ": [origin] << " + response.getStatusLine().toString()); Header[] headers = response.getAllHeaders(); for (int i = 0; i < headers.length; i++) { context.debug(conn + ": [origin] << " + headers[i].toString()); } int statusCode = response.getStatusLine().getStatusCode(); if (statusCode < HttpStatus.SC_OK) { // Ignore 1xx response return; } try { // Update connection state proxyTask.setResponse(response); proxyTask.setOriginState(ProxyTask.RESPONSE_RECEIVED); /**************** zardoz ****************/ interceptResponse(conn, request, response); /**************************************/ if (!canResponseHaveBody(request, response)) { conn.resetInput(); if (!this.connStrategy.keepAlive(response, httpContext)) { conn.close(); } } // Make sure client output is active proxyTask.getClientIOControl().requestOutput(); } catch (IOException ex) { shutdownConnection(conn); } } } private boolean canResponseHaveBody(final HttpRequest request, final HttpResponse response) { if (request != null && "HEAD".equalsIgnoreCase(request.getRequestLine() .getMethod())) { return false; } int status = response.getStatusLine().getStatusCode(); return status >= HttpStatus.SC_OK && status != HttpStatus.SC_NO_CONTENT && status != HttpStatus.SC_NOT_MODIFIED && status != HttpStatus.SC_RESET_CONTENT; } public void inputReady(final NHttpClientConnection conn, final ContentDecoder decoder) { context.debug(conn + ": origin conn input ready " + decoder); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); synchronized (proxyTask) { // Validate connection state if (proxyTask.getOriginState() != ProxyTask.RESPONSE_RECEIVED && proxyTask.getOriginState() != ProxyTask.RESPONSE_BODY_STREAM) { throw new IllegalStateException("Illegal connection state"); } HttpResponse response = proxyTask.getResponse(); try { ByteBuffer dst = proxyTask.getOutBuffer(); int bytesRead = decoder.read(dst); context.debug(conn + ": " + bytesRead + " bytes read"); if (!dst.hasRemaining()) { // Output buffer is full. Suspend origin input until // the client handler frees up some space in the buffer conn.suspendInput(); } // If there is some content in the buffer make sure client // output // is active if (dst.position() > 0) { proxyTask.getClientIOControl().requestOutput(); } if (decoder.isCompleted()) { context.debug(conn + ": origin conn response body received"); proxyTask.setOriginState(ProxyTask.RESPONSE_BODY_DONE); if (!this.connStrategy.keepAlive(response, httpContext)) { conn.close(); } } else { proxyTask .setOriginState(ProxyTask.RESPONSE_BODY_STREAM); } } catch (IOException ex) { shutdownConnection(conn); } } } public void closed(final NHttpClientConnection conn) { context.debug(conn + ": origin conn closed"); HttpContext httpContext = conn.getContext(); ProxyTask proxyTask = (ProxyTask) httpContext .getAttribute(ProxyTask.ATTRIB); if (proxyTask != null) { synchronized (proxyTask) { IOControl ioControl = proxyTask.getClientIOControl(); if (ioControl != null) { try { ioControl.shutdown(); } catch (IOException ignore) { } } } } } public void exception(final NHttpClientConnection conn, final HttpException ex) { shutdownConnection(conn); context.debug(conn + ": " + ex.getMessage()); } public void exception(final NHttpClientConnection conn, final IOException ex) { shutdownConnection(conn); context.debug(conn + ": " + ex.getMessage()); } public void timeout(final NHttpClientConnection conn) { context.debug(conn + ": timeout"); shutdownConnection(conn); } private void shutdownConnection(final HttpConnection conn) { try { conn.shutdown(); } catch (IOException ignore) { } } } class ProxyTask { public static final String ATTRIB = "nhttp.proxy-task"; public static final int IDLE = 0; public static final int CONNECTED = 1; public static final int REQUEST_RECEIVED = 2; public static final int REQUEST_SENT = 3; public static final int REQUEST_BODY_STREAM = 4; public static final int REQUEST_BODY_DONE = 5; public static final int RESPONSE_RECEIVED = 6; public static final int RESPONSE_SENT = 7; public static final int RESPONSE_BODY_STREAM = 8; public static final int RESPONSE_BODY_DONE = 9; private final ByteBuffer inBuffer; private final ByteBuffer outBuffer; private HttpHost target; private IOControl originIOControl; private IOControl clientIOControl; private int originState; private int clientState; private HttpRequest request; private HttpResponse response; public ProxyTask() { super(); this.originState = IDLE; this.clientState = IDLE; this.inBuffer = ByteBuffer.allocateDirect(10240); this.outBuffer = ByteBuffer.allocateDirect(10240); } public ByteBuffer getInBuffer() { return this.inBuffer; } public ByteBuffer getOutBuffer() { return this.outBuffer; } public HttpHost getTarget() { return this.target; } public void setTarget(final HttpHost target) { this.target = target; } public HttpRequest getRequest() { return this.request; } public void setRequest(final HttpRequest request) { this.request = request; } public HttpResponse getResponse() { return this.response; } public void setResponse(final HttpResponse response) { this.response = response; } public IOControl getClientIOControl() { return this.clientIOControl; } public void setClientIOControl(final IOControl clientIOControl) { this.clientIOControl = clientIOControl; } public IOControl getOriginIOControl() { return this.originIOControl; } public void setOriginIOControl(final IOControl originIOControl) { this.originIOControl = originIOControl; } public int getOriginState() { return this.originState; } public void setOriginState(int state) { this.originState = state; } public int getClientState() { return this.clientState; } public void setClientState(int state) { this.clientState = state; } public void reset() { this.inBuffer.clear(); this.outBuffer.clear(); this.originState = IDLE; this.clientState = IDLE; this.request = null; this.response = null; } public void shutdown() { if (this.clientIOControl != null) { try { this.clientIOControl.shutdown(); } catch (IOException ignore) { } } if (this.originIOControl != null) { try { this.originIOControl.shutdown(); } catch (IOException ignore) { } } } } }