/* * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/examples/org/apache/http/examples/ElementalHttpServer.java $ * $Revision: 702589 $ * $Date: 2008-10-07 21:13:28 +0200 (Tue, 07 Oct 2008) $ * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * */ package br.ufms.dct.simplerep.proxies.http; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import org.apache.axiom.soap.SOAPHeader; import org.apache.axiom.soap.SOAPHeaderBlock; import org.apache.http.ConnectionClosedException; import org.apache.http.ConnectionReuseStrategy; import org.apache.http.HttpClientConnection; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpServerConnection; import org.apache.http.ProtocolVersion; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.DefaultConnectionReuseStrategy; import org.apache.http.impl.DefaultHttpClientConnection; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.DefaultHttpServerConnection; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpResponse; 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.BasicHttpContext; 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.HttpRequestExecutor; import org.apache.http.protocol.HttpRequestHandler; import org.apache.http.protocol.HttpRequestHandlerRegistry; import org.apache.http.protocol.HttpService; 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.RequestUserAgent; 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 org.apache.http.util.EntityUtils; import org.apache.log4j.Logger; import br.ufms.dct.simplerep.SimpleRepConfiguration; import br.ufms.dct.simplerep.ar.MessageContext; import br.ufms.dct.simplerep.ar.OperationContext; import br.ufms.dct.simplerep.ar.ProcessingStatus; import br.ufms.dct.simplerep.ar.RequestProcessor; import br.ufms.dct.simplerep.ar.SequencedEnvelope; import br.ufms.dct.simplerep.ar.SystemContext; import br.ufms.dct.simplerep.enums.AddressingConstants; import br.ufms.dct.simplerep.httpprocessors.ABCastInterceptor; import br.ufms.dct.simplerep.kernels.AbstractKernel; import br.ufms.dct.simplerep.utils.HttpUtils; /** * Rudimentary HTTP/1.1 reverse proxy. * <p> * Please note the purpose of this application is demonstrate the usage of * HttpCore APIs. It is NOT intended to demonstrate the most efficient way of * building an HTTP reverse proxy. * * * @version $Revision: $ */ public class ElementalReverseProxy { static Logger logger = Logger.getLogger(ElementalReverseProxy.class.getName()); private static final String HTTP_IN_CONN = "http.proxy.in-conn"; private static final String HTTP_OUT_CONN = "http.proxy.out-conn"; private static final String HTTP_CONN_KEEPALIVE = "http.proxy.conn-keepalive"; public static void run(SimpleRepConfiguration conf) throws Exception { Thread t = new RequestListenerThread(conf); t.setDaemon(false); t.start(); } static class ProxyHandler implements HttpRequestHandler { static Logger logger = Logger.getLogger(ProxyHandler.class.getName()); private final HttpHost target; private final HttpProcessor httpproc; private final HttpRequestExecutor httpexecutor; private final ConnectionReuseStrategy connStrategy; private Socket inSocket; public ProxyHandler(final HttpHost target, final HttpProcessor httpproc, final HttpRequestExecutor httpexecutor) { super(); this.target = target; this.httpproc = httpproc; this.httpexecutor = httpexecutor; this.connStrategy = new DefaultConnectionReuseStrategy(); } public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context) throws HttpException, IOException { HttpClientConnection conn = (HttpClientConnection) context .getAttribute(HTTP_OUT_CONN); context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn); context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,this.target); System.out.println(">> Request URI: " + request.getRequestLine().getUri()); // Remove hop-by-hop headers request.removeHeaders(HTTP.CONTENT_LEN); request.removeHeaders(HTTP.TRANSFER_ENCODING); request.removeHeaders(HTTP.CONN_DIRECTIVE); request.removeHeaders("Keep-Alive"); request.removeHeaders("Proxy-Authenticate"); request.removeHeaders("TE"); request.removeHeaders("Trailers"); request.removeHeaders("Upgrade"); this.httpexecutor.preProcess(request, this.httpproc, context); // BEGIN(@JRS) SynchronousQueue<String> myOutQueue = this.getMyOutQueue(context); HttpResponse targetResponse = null; if (myOutQueue != null) { ExecutorService localInvocationExecutor = Executors.newSingleThreadExecutor(); BasicHttpEntityEnclosingRequest realRequest = (BasicHttpEntityEnclosingRequest) request; SystemContext sysContext = SimpleRepConfiguration.getConfiguration().getSystemContext(); String incomingEnvelope = EntityUtils.toString(((BasicHttpEntityEnclosingRequest) request).getEntity()); MessageContext inEnvelopeContext = MessageContext.buildMessageContext(incomingEnvelope); // este mesmo remoteHostIdentifier é usado no ThirdPartyRequestsRunner String remoteHostIdentifier = inEnvelopeContext.getRemoteHostIdentifier(); logger.debug("Incoming connection. Remote Host Identifier: " + remoteHostIdentifier); HashMap<String, SequencedEnvelope> lastEnvelopesOutQueue = (HashMap<String, SequencedEnvelope>) sysContext.get(AbstractKernel.LAST_ENVELOPES_OUT_QUEUE); SequencedEnvelope lastSentSequencedEnvelope = lastEnvelopesOutQueue.get(remoteHostIdentifier); int incomingSequenceId = inEnvelopeContext.getSequenceId(); if (logger.isDebugEnabled()) { if (incomingSequenceId == 0) { logger.debug("ResetSeqId received. It's the first interaction."); } else if (incomingSequenceId < 0) { logger.debug("The seqId could not be retrieved."); } else if (lastSentSequencedEnvelope == null) { logger.debug("No sequenced envelope has been received until now."); } else { logger.debug("Incoming sequenceId: " + incomingSequenceId + ". Last Sequence id: " + lastSentSequencedEnvelope.getSequenceId()); } } if (lastSentSequencedEnvelope != null && incomingSequenceId == lastSentSequencedEnvelope.getSequenceId()) { // we already have the response // bypassing logger.info("[ProxyHandler] Envelope already processed. Bypassing."); targetResponse = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 200, "Success"); targetResponse.setEntity(new BasicHttpEntity()); ((BasicHttpEntity) targetResponse.getEntity()).setContent(new ByteArrayInputStream(lastSentSequencedEnvelope.getEnvelope().toString().getBytes())); } else { // must be shared IN and OUT OperationContext operationContext = new OperationContext(); inEnvelopeContext.setOperationContext(operationContext); // Putting the message in the inflow if (RequestProcessor.getProcessor().inFlow(inEnvelopeContext) == ProcessingStatus.ABORT) { // Something went wrong in the InFlow // we have to restore the original envelope in the request entity (which is read-once, remember?) logger.warn("The InFlow has been aborted."); realRequest.setEntity(HttpUtils.string2BasicEntity(incomingEnvelope)); } else { // everything went well in the InFlow, replacing the original envelope // with the new (possibly modified) envelope logger.debug("Inflow OK. Sending the following envelope to the appserver: " + inEnvelopeContext.getEnvelope().toString()); realRequest.setEntity(HttpUtils.string2BasicEntity(inEnvelopeContext.getEnvelope().toString())); } LocalInvocationRunner localInvocationRunner = new LocalInvocationRunner( myOutQueue, this.httpexecutor, request, context, conn); // at this point the response may come from the local app server or from // any of the replicas String envelope = ""; MessageContext outMessageContext = null; try { logger.debug("Waiting for some envelope... "); localInvocationExecutor.execute(localInvocationRunner); envelope = myOutQueue.take(); outMessageContext = MessageContext.buildMessageContext(envelope); logger.debug("Unprocessed response: " + outMessageContext.getEnvelope().toString() + ". Putting in the outflow..."); outMessageContext.setOperationContext(operationContext); RequestProcessor.getProcessor().outFlow(outMessageContext); if (incomingSequenceId == 2) { System.err.println("Received the SECOND envelope. I'm going to sleep."); Thread.sleep(10000); System.err.println("Woke up!"); } //logger.debug("[ProxyHandler] Processed response: " + outMessageContext.getEnvelope().toString()); // Saving the envelope and sequenceId SequencedEnvelope seqEnv = new SequencedEnvelope(incomingSequenceId, outMessageContext.getEnvelope()); lastEnvelopesOutQueue.put(remoteHostIdentifier, seqEnv); } catch (InterruptedException e) { logger.fatal("Fatal error when trying to get an envelope from one of the replicas."); } targetResponse = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 200, "Success"); targetResponse.setEntity(new BasicHttpEntity()); ((BasicHttpEntity) targetResponse.getEntity()).setContent(new ByteArrayInputStream(outMessageContext.getEnvelope().toString().getBytes())); } } else { // normal proxy behaviour targetResponse = this.httpexecutor.execute(request, conn, context); } this.httpexecutor.postProcess(response, this.httpproc, context); // END(@JRS) // Remove hop-by-hop headers targetResponse.removeHeaders(HTTP.CONTENT_LEN); targetResponse.removeHeaders(HTTP.TRANSFER_ENCODING); targetResponse.removeHeaders(HTTP.CONN_DIRECTIVE); targetResponse.removeHeaders("Keep-Alive"); targetResponse.removeHeaders("TE"); targetResponse.removeHeaders("Trailers"); targetResponse.removeHeaders("Upgrade"); response.setStatusLine(targetResponse.getStatusLine()); response.setHeaders(targetResponse.getAllHeaders()); response.setEntity(targetResponse.getEntity()); logger.info("<< Response: " + response.getStatusLine()); logger.info(""); logger.info("===== End of interaction ===="); logger.info(""); logger.info(""); logger.info(""); boolean keepalive = this.connStrategy.keepAlive(response, context); context.setAttribute(HTTP_CONN_KEEPALIVE, new Boolean(keepalive)); } private SynchronousQueue<String> getMyOutQueue(HttpContext context) { SimpleRepConfiguration conf; SystemContext sysCtxt = null; sysCtxt = SimpleRepConfiguration.getConfiguration().getSystemContext(); HashMap<String, SynchronousQueue<String>> transportOutQueues = (HashMap<String, SynchronousQueue<String>>) sysCtxt.get(AbstractKernel.TRANSPORT_OUT_QUEUES); String messageId = (String) context.getAttribute(AbstractKernel.MESSAGE_ID); if (messageId == null || messageId.equals("")) { logger.fatal("wsa:MessageID could not be retrieved. Aborting!"); return null; } // This thread is going to wait on this queue for the envelope to be // sent to the client return transportOutQueues.get(messageId); } public void setInSocket(Socket inSocket) { this.inSocket = inSocket; } public Socket getInSocket() { return inSocket; } } static class RequestListenerThread extends Thread { private final HttpHost target; private final ServerSocket serversocket; private final HttpParams params; private final HttpService httpService; private SimpleRepConfiguration conf; private ProxyHandler proxyHandler; public RequestListenerThread(SimpleRepConfiguration conf) throws IOException { this.target = new HttpHost(conf.getAppServerHost(), conf .getAppServerPort()); this.serversocket = new ServerSocket(conf.getProxyPort()); this.conf = conf; this.params = new BasicHttpParams(); this.params .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000) .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"); // Set up HTTP protocol processor for incoming connections BasicHttpProcessor inhttpproc = new BasicHttpProcessor(); inhttpproc.addInterceptor(new ResponseDate()); inhttpproc.addInterceptor(new ResponseServer()); inhttpproc.addInterceptor(new ResponseContent()); inhttpproc.addInterceptor(new ResponseConnControl()); // @BEGIN(JRS) // inhttpproc.addInterceptor(new SimpleRepInFlowInterceptor()); // // old stuff BlockingQueue<MessageContext> inQueue = new SynchronousQueue<MessageContext>(); inhttpproc.addInterceptor(new ABCastInterceptor(inQueue)); // @END(JRS) // Set up HTTP protocol processor for outgoing connections BasicHttpProcessor outhttpproc = new BasicHttpProcessor(); outhttpproc.addInterceptor(new RequestContent()); outhttpproc.addInterceptor(new RequestTargetHost()); outhttpproc.addInterceptor(new RequestConnControl()); outhttpproc.addInterceptor(new RequestUserAgent()); outhttpproc.addInterceptor(new RequestExpectContinue()); // Set up outgoing request executor HttpRequestExecutor httpexecutor = new HttpRequestExecutor(); // Set up incoming request handler HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry(); this.proxyHandler = new ProxyHandler(this.target, outhttpproc, httpexecutor); reqistry.register("*", this.proxyHandler); // Set up the HTTP service this.httpService = new HttpService(inhttpproc, new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory()); this.httpService.setParams(this.params); this.httpService.setHandlerResolver(reqistry); } public void run() { logger.info("HTTP Proxy Listening on port " + this.serversocket.getLocalPort()); while (!Thread.interrupted()) { try { // Set up incoming HTTP connection Socket insocket = this.serversocket.accept(); DefaultHttpServerConnection inconn = new DefaultHttpServerConnection(); System.out.println("Incoming connection from " + insocket.getInetAddress()); this.proxyHandler.setInSocket(insocket); inconn.bind(insocket, this.params); // Set up outgoing HTTP connection Socket outsocket = new Socket(this.target.getHostName(), this.target.getPort()); DefaultHttpClientConnection outconn = new DefaultHttpClientConnection(); outconn.bind(outsocket, this.params); System.out.println("Outgoing connection to " + outsocket.getInetAddress()); // Start worker thread Thread t = new ProxyThread(this.httpService, inconn, outconn, insocket); t.setDaemon(true); t.start(); } catch (InterruptedIOException ex) { break; } catch (IOException e) { System.err .println("I/O error initialising connection thread: " + e.getMessage() + ". The application server is down."); break; } } } } static class ProxyThread extends Thread { private final HttpService httpservice; private final HttpServerConnection inconn; private final HttpClientConnection outconn; private final Socket inSocket; public ProxyThread(final HttpService httpservice, final HttpServerConnection inconn, final HttpClientConnection outconn, final Socket insocket) { super(); this.httpservice = httpservice; this.inconn = inconn; this.outconn = outconn; this.inSocket = insocket; } public void run() { System.out.println("New connection thread"); HttpContext context = new BasicHttpContext(null); // Bind connection objects to the execution context context.setAttribute(HTTP_IN_CONN, this.inconn); context.setAttribute(HTTP_OUT_CONN, this.outconn); try { while (!Thread.interrupted()) { if (!this.inconn.isOpen()) { this.outconn.close(); break; } this.httpservice.handleRequest(this.inconn, context); Boolean keepalive = (Boolean) context .getAttribute(HTTP_CONN_KEEPALIVE); if (!Boolean.TRUE.equals(keepalive)) { this.outconn.close(); this.inconn.close(); break; } } } catch (ConnectionClosedException ex) { System.err.println("Client closed connection"); } catch (IOException ex) { if (ex.getMessage().equals("Broken pipe")) { logger.debug(ex.getMessage() + ". The client has probably failed over."); } else { System.err.println("I/O error: " + ex.getMessage()); } } catch (HttpException ex) { System.err.println("Unrecoverable HTTP protocol violation: " + ex.getMessage()); } finally { try { this.inconn.shutdown(); } catch (IOException ignore) { } try { this.outconn.shutdown(); } catch (IOException ignore) { } } } } }