You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by ri...@apache.org on 2007/12/12 11:34:00 UTC

svn commit: r603540 - in /geronimo/sandbox/AsyncHttpClient/src: main/java/org/apache/ahc/ main/java/org/apache/ahc/codec/ test/java/org/apache/ahc/

Author: rickmcguire
Date: Wed Dec 12 02:33:59 2007
New Revision: 603540

URL: http://svn.apache.org/viewvc?rev=603540&view=rev
Log:
GERONIMO-3686 AsyncHttpClient does not reuse connection even if connections are persistent

Patch submitted by Sangjin Lee. 


Modified:
    geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/AsyncHttpClient.java
    geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java
    geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpIoHandler.java
    geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpMessage.java
    geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientTest.java
    geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientWithFutureTest.java
    geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AuthTest.java
    geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/TimeoutTest.java

Modified: geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/AsyncHttpClient.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/AsyncHttpClient.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/AsyncHttpClient.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/AsyncHttpClient.java Wed Dec 12 02:33:59 2007
@@ -27,10 +27,12 @@
 
 import javax.net.ssl.SSLContext;
 
+import org.apache.ahc.codec.HttpDecoder;
 import org.apache.ahc.codec.HttpIoHandler;
 import org.apache.ahc.codec.HttpProtocolCodecFactory;
 import org.apache.ahc.codec.HttpRequestMessage;
 import org.apache.ahc.codec.ResponseFuture;
+import org.apache.ahc.codec.SessionCache;
 import org.apache.ahc.ssl.TrustManagerFactoryImpl;
 import org.apache.ahc.util.AsyncHttpClientException;
 import org.apache.mina.common.ConnectFuture;
@@ -38,6 +40,7 @@
 import org.apache.mina.common.IoFutureListener;
 import org.apache.mina.common.IoSession;
 import org.apache.mina.common.RuntimeIOException;
+import org.apache.mina.common.support.DefaultConnectFuture;
 import org.apache.mina.filter.SSLFilter;
 import org.apache.mina.filter.codec.ProtocolCodecFilter;
 import org.apache.mina.transport.socket.nio.SocketConnector;
@@ -51,6 +54,9 @@
  */
 public class AsyncHttpClient {
 
+	/** The Constant DEFAULT_REUSE_CONNECTION. */
+	public static final boolean DEFAULT_REUSE_CONNECTION = false;
+	
     /** The Constant DEFAULT_CONNECTION_TIMEOUT. */
     public static final int DEFAULT_CONNECTION_TIMEOUT = 30000;
 
@@ -92,13 +98,16 @@
 
     /** The thread pool. */
     private final ExecutorService threadPool;
-
+    
     /** The HttpIoHandler handler. */
     private final HttpIoHandler handler;
 
     /** The ssl filter. */
     private SSLFilter sslFilter;
-
+    
+    /** connection reuse option */
+    private volatile boolean reuseConnection = DEFAULT_REUSE_CONNECTION;
+    
     /** The Reuse Address Socket Parameter. */
     private boolean reuseAddress = DEFAULT_REUSE_ADDRESS;
 
@@ -124,6 +133,24 @@
     private boolean tcpNoDelay = DEFAULT_TCP_NO_DELAY;
 
     /**
+     * Returns if it reuses established connections for more requests.
+     * 
+     * @return true if it reuses connections
+     */
+    public boolean isReuseConnection() {
+    	return reuseConnection;
+    }
+    
+    /**
+     * Sets if it should reuse established connections for more requests.
+     * 
+     * @param reuseConnection the new value
+     */
+    public void setReuseConnection(boolean reuseConnection) {
+    	this.reuseConnection = reuseConnection;
+    }
+    
+    /**
      * Checks if is reuse address.
      *
      * @return true, if is reuse address
@@ -266,7 +293,7 @@
     public void setTcpNoDelay(boolean tcpNoDelay) {
         this.tcpNoDelay = tcpNoDelay;
     }
-
+    
     /**
      * Instantiates a new AsyncHttpClient.  It will use a single threaded model and is good for
      * use in one-off connections.
@@ -336,7 +363,7 @@
      * @see HttpRequestMessage
      */
     public ResponseFuture sendRequest(HttpRequestMessage message) {
-    	return sendRequest(message, null);
+        return sendRequest(message, null);
     }
     
     /**
@@ -357,7 +384,7 @@
      * returned.
      */
     public ResponseFuture sendRequest(HttpRequestMessage message, 
-    		BlockingQueue<ResponseFuture> queue) {
+            BlockingQueue<ResponseFuture> queue) {
         if (threadPool != null && threadPool.isShutdown()) {
             throw new IllegalStateException("AsyncHttpClient has been destroyed and cannot be reused.");
         }
@@ -366,15 +393,43 @@
         // request unless it already exists (i.e. sendRequest() is called
         // multiple times for the request)
         if (message.getResponseFuture() == null) {
-        	message.setResponseFuture(new ResponseFuture(message, queue));
+            message.setResponseFuture(new ResponseFuture(message, queue));
         }
         
-        String host = message.getHost();
-        int port = message.getPort();
-        ConnectFuture future = connector.connect(new InetSocketAddress(host, port), handler);
+        // *IF* connection reuse is enabled, we should see if we have a cached 
+        // connection first; if not, always open a new one
+        ConnectFuture future = null;
+        if (reuseConnection) {
+            future = getCachedConnection(message);
+        } else {
+            // add the Connection close header explicitly
+            message.setHeader(HttpDecoder.CONNECTION, HttpDecoder.CLOSE);
+        }
+        
+        // if no cached connection is found or keep-alive is disabled, force a
+        // new connection
+        if (future == null) {
+            future = openConnection(message);
+        }
         future.addListener(new FutureListener(message));
         return message.getResponseFuture();
     }
+    
+    private ConnectFuture openConnection(HttpRequestMessage message) {
+        return connector.connect(new InetSocketAddress(message.getHost(), message.getPort()), handler);
+    }
+    
+    private ConnectFuture getCachedConnection(HttpRequestMessage message) {
+        IoSession cached = SessionCache.getInstance().getActiveSession(message);
+        if (cached == null) {
+            return null;
+        }
+
+        // create a containing future object and set the session right away
+        ConnectFuture future = new DefaultConnectFuture();
+        future.setSession(cached);
+        return future;
+    }
 
     /**
      * Gets the connection timeout.
@@ -424,6 +479,8 @@
      * connection occurs, it is also responsible for sending the request.
      */
     class FutureListener implements IoFutureListener {
+        static final String SSL_FILTER = "SSL";
+        static final String PROTOCOL_FILTER = "protocolFilter";
 
         /** The request. */
         final HttpRequestMessage request;
@@ -449,28 +506,16 @@
             ConnectFuture connFuture = (ConnectFuture) future;
             if (connFuture.isConnected()) {
                 IoSession sess = future.getSession();
-                String scheme = request.getUrl().getProtocol();
 
-                //Add the https filter
-                if (scheme.toLowerCase().equals("https")) {
-                    if (sslFilter == null) {
-                        try {
-                            sslFilter = new SSLFilter(createClientSSLContext());
-                            sslFilter.setUseClientMode(true);
-                        } catch (GeneralSecurityException e) {
-                            try {
-                                sess.getHandler().exceptionCaught(sess, e);
-                            } catch (Exception e1) {
-                                //Do nothing...we just reported it
-                            }
-                        }
-                    }
-                    sess.getFilterChain().addLast("SSL", sslFilter);
+                // see if we need to add the SSL filter
+                addSSLFilter(sess);
+                // add the protocol filter (if it's not there already like in a
+                // reused session)
+                if (!sess.getFilterChain().contains(PROTOCOL_FILTER)) {
+                    sess.getFilterChain().addLast(PROTOCOL_FILTER, new ProtocolCodecFilter(
+                            new HttpProtocolCodecFactory()));
                 }
-
-                sess.getFilterChain().addLast("protocolFilter", new ProtocolCodecFilter(
-                        new HttpProtocolCodecFactory()));
-
+                
                 sess.setAttribute(HttpIoHandler.CURRENT_REQUEST, request);
 
                 sess.setAttachment(AsyncHttpClient.this);
@@ -494,6 +539,31 @@
                 } catch (RuntimeIOException e) {
                     //Set the callback exception
                     request.getCallback().onException(e);
+                }
+            }
+        }
+
+        private void addSSLFilter(IoSession sess) {
+            String scheme = request.getUrl().getProtocol();
+            
+            //Add the https filter
+            if (scheme.toLowerCase().equals("https")) {
+                // add the SSL filter if it's not there already like in a reused
+                // session
+                if (!sess.getFilterChain().contains(SSL_FILTER)) {
+                    if (sslFilter == null) {
+                        try {
+                            sslFilter = new SSLFilter(createClientSSLContext());
+                            sslFilter.setUseClientMode(true);
+                        } catch (GeneralSecurityException e) {
+                            try {
+                                sess.getHandler().exceptionCaught(sess, e);
+                            } catch (Exception e1) {
+                                //Do nothing...we just reported it
+                            }
+                        }
+                    }
+                    sess.getFilterChain().addLast(SSL_FILTER, sslFilter);
                 }
             }
         }

Modified: geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpDecoder.java Wed Dec 12 02:33:59 2007
@@ -26,10 +26,6 @@
 
 import org.apache.ahc.util.DateUtil;
 import org.apache.ahc.util.NameValuePair;
-import org.apache.ahc.auth.AuthChallengeParser;
-import org.apache.ahc.auth.AuthScheme;
-import org.apache.ahc.auth.AuthPolicy;
-import org.apache.ahc.auth.AuthState;
 import org.apache.mina.common.ByteBuffer;
 
 /**
@@ -42,6 +38,9 @@
 
     /** The Constant CONNECTION. */
     public static final String CONNECTION = "Connection";
+    
+    /** The Constant CLOSE as a value for the Connection header */
+    public static final String CLOSE = "close";
 
     /** The Constant COOKIE_COMMENT. */
     public static final String COOKIE_COMMENT = "comment";

Modified: geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpIoHandler.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpIoHandler.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpIoHandler.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpIoHandler.java Wed Dec 12 02:33:59 2007
@@ -52,7 +52,7 @@
      * The Constant CURRENT_RESPONSE.
      */
     public static final String CURRENT_RESPONSE = "CURRENT_RESPONSE";
-
+    
     /**
      * The Constant CONNECTION_CLOSE.
      */
@@ -123,8 +123,8 @@
             //Send the redirect
             client.sendRequest(request);
 
-            //Close the current session since we are done with it
-            ioSession.close();
+            // cache the session before we return
+            SessionCache.getInstance().cacheSession(ioSession);
             return;
         }
 
@@ -150,8 +150,8 @@
                 request.setAuthCount(authCount);
                 client.sendRequest(request);
 
-                //Close the current session since we are done with it
-                ioSession.close();
+                // cache the session before we return
+                SessionCache.getInstance().cacheSession(ioSession);
                 return;
             }
         }
@@ -161,8 +161,9 @@
         // complete the future which will also fire the callback
         ResponseFuture result = request.getResponseFuture();
         result.set(response);
-        
-        ioSession.close();
+
+        // cache the session before we return
+        SessionCache.getInstance().cacheSession(ioSession);
     }
 
     /**
@@ -196,11 +197,13 @@
     public void sessionClosed(IoSession ioSession) throws Exception {
         //Clean up if any in-proccess decoding was occurring
         ioSession.removeAttribute(CURRENT_RESPONSE);
+        // remove it from the cache
+        SessionCache.getInstance().removeSession(ioSession);
         HttpRequestMessage request = (HttpRequestMessage) ioSession.getAttribute(CURRENT_REQUEST);
         cancelTasks(request);
         AsyncHttpClientCallback callback = request.getCallback();
         if (callback != null) {
-        	callback.onClosed();
+            callback.onClosed();
         }
     }
 

Modified: geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpMessage.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpMessage.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpMessage.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/main/java/org/apache/ahc/codec/HttpMessage.java Wed Dec 12 02:33:59 2007
@@ -22,6 +22,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.ahc.util.NameValuePair;
@@ -129,16 +130,17 @@
 
 
     /**
-     * Gets the headers.
+     * Returns all headers.
      * 
-     * @return the headers
+     * @return all headers
      */
     public List<NameValuePair> getHeaders() {
         return headers;
     }
 
     /**
-     * Sets the headers.
+     * Sets the headers.  It removes any headers that were stored before the
+     * call.
      * 
      * @param headers the new headers
      */
@@ -164,6 +166,87 @@
      */
     public void addHeader(String name, String value) {
         headers.add(new NameValuePair(name, value));
+    }
+
+    /**
+     * Removes the headers that have the given name.
+     * 
+     * @param name the name
+     */
+    public void removeHeader(String name) {
+        Iterator<NameValuePair> it = headers.iterator();
+        while (it.hasNext()) {
+            NameValuePair header = it.next();
+            if (header.getName().equals(name)) {
+                it.remove();
+            }
+        }
+    }
+    
+    /**
+     * Sets the header with the given name and the value.  This differs from
+     * <code>addHeader()</code> in that it removes any existing header under the
+     * name and adds the new one.
+     * 
+     * @param name the name
+     * @param value the value
+     * @throws IllegalArgumentException if either the name or the value is null.
+     */
+    public void setHeader(String name, String value) {
+        if (name == null || value == null) {
+            throw new IllegalArgumentException("null name or value was passed in");
+        }
+        
+        // we're resetting the value, so remove it first
+        removeHeader(name);
+        addHeader(name, value);
+    }
+    
+    /**
+     * Returns the value for the header with the given name.  If there are more
+     * than one header stored, it returns the first entry it finds in the list.
+     * 
+     * @param name the name
+     * @return the value for the header, or null if it is not found
+     * @throws IllegalArgumentException if the name is null
+     */
+    public String getHeader(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("null name was passed in");
+        }
+        
+        Iterator<NameValuePair> it = headers.iterator();
+        while (it.hasNext()) {
+            NameValuePair header = it.next();
+            if (header.getName().equals(name)) {
+                return header.getValue();
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Returns an array of values for the header with the given name.
+     * 
+     * @param name the name
+     * @return the value for the header.  If there is no entry under the name,
+     * an empty array is returned.
+     * @throws IllegalArgumentException if the name is null
+     */
+    public String[] getHeaders(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("null name was passed in");
+        }
+        
+        List<String> values = new ArrayList<String>();
+        Iterator<NameValuePair> it = headers.iterator();
+        while (it.hasNext()) {
+            NameValuePair header = it.next();
+            if (header.getName().equals(name)) {
+                values.add(header.getValue());
+            }
+        }
+        return values.toArray(new String[]{});
     }
 
     /**

Modified: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientTest.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientTest.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientTest.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientTest.java Wed Dec 12 02:33:59 2007
@@ -137,6 +137,7 @@
                               boolean testForException, 
                               TestCallback callback) throws Exception {
         AsyncHttpClient ahc = new AsyncHttpClient();
+        ahc.setTcpNoDelay(true);
 
         ahc.sendRequest(request);
 

Modified: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientWithFutureTest.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientWithFutureTest.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientWithFutureTest.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AsyncHttpClientWithFutureTest.java Wed Dec 12 02:33:59 2007
@@ -123,6 +123,7 @@
     												  BlockingQueue<ResponseFuture> queue)
     		throws Exception {
     	AsyncHttpClient ahc = new AsyncHttpClient();
+    	ahc.setTcpNoDelay(true);
     	return ahc.sendRequest(request, queue);
     }
 }

Modified: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AuthTest.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AuthTest.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AuthTest.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/AuthTest.java Wed Dec 12 02:33:59 2007
@@ -79,6 +79,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         ahc = new AsyncHttpClient();
+        ahc.setTcpNoDelay(true);
     }
 
     protected void tearDown() throws Exception {

Modified: geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/TimeoutTest.java
URL: http://svn.apache.org/viewvc/geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/TimeoutTest.java?rev=603540&r1=603539&r2=603540&view=diff
==============================================================================
--- geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/TimeoutTest.java (original)
+++ geronimo/sandbox/AsyncHttpClient/src/test/java/org/apache/ahc/TimeoutTest.java Wed Dec 12 02:33:59 2007
@@ -32,6 +32,7 @@
         TestCallback callback = new TestCallback();
 
         AsyncHttpClient ahc = new AsyncHttpClient();
+        ahc.setTcpNoDelay(true);
 
         HttpRequestMessage request = new HttpRequestMessage(new URL("http://localhost:8282/timeout.jsp"), callback);