You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ro...@apache.org on 2007/12/08 17:55:13 UTC

svn commit: r602506 - in /jakarta/httpcomponents/httpcore/trunk: RELEASE_NOTES.txt module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java module-main/src/test/java/org/apache/http/impl/TestDefaultConnectionReuseStrategy.java

Author: rolandw
Date: Sat Dec  8 08:55:13 2007
New Revision: 602506

URL: http://svn.apache.org/viewvc?rev=602506&view=rev
Log:
HTTPCORE-112: patch committed with testcases

Modified:
    jakarta/httpcomponents/httpcore/trunk/RELEASE_NOTES.txt
    jakarta/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java
    jakarta/httpcomponents/httpcore/trunk/module-main/src/test/java/org/apache/http/impl/TestDefaultConnectionReuseStrategy.java

Modified: jakarta/httpcomponents/httpcore/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/jakarta/httpcomponents/httpcore/trunk/RELEASE_NOTES.txt?rev=602506&r1=602505&r2=602506&view=diff
==============================================================================
--- jakarta/httpcomponents/httpcore/trunk/RELEASE_NOTES.txt (original)
+++ jakarta/httpcomponents/httpcore/trunk/RELEASE_NOTES.txt Sat Dec  8 08:55:13 2007
@@ -1,5 +1,8 @@
 Changes since 4.0 Alpha 6
 
+* [HTTPCORE-112] DefaultConnectionReuseStrategy interprets token sequences
+  Contributed by Roland Weber <rolandw at apache.org>
+
 * [HTTPCORE-122] new interface TokenIterator and basic implementation
   Contributed by Roland Weber <rolandw at apache.org>
 

Modified: jakarta/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java
URL: http://svn.apache.org/viewvc/jakarta/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java?rev=602506&r1=602505&r2=602506&view=diff
==============================================================================
--- jakarta/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java (original)
+++ jakarta/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java Sat Dec  8 08:55:13 2007
@@ -34,36 +34,46 @@
 import org.apache.http.ConnectionReuseStrategy;
 import org.apache.http.HttpConnection;
 import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpVersion;
+import org.apache.http.ParseException;
 import org.apache.http.ProtocolVersion;
 import org.apache.http.protocol.HTTP;
 import org.apache.http.protocol.HttpContext;
 import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.TokenIterator;
+import org.apache.http.message.BasicTokenIterator;
+
 
 /**
  * Default implementation of a strategy deciding about connection re-use.
  *
  * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
  *
  * @version $Revision$
  * 
  * @since 4.0
  */
-public class DefaultConnectionReuseStrategy implements ConnectionReuseStrategy {
+public class DefaultConnectionReuseStrategy
+    implements ConnectionReuseStrategy {
 
     public DefaultConnectionReuseStrategy() {
         super();
     }
 
     // see interface ConnectionReuseStrategy
-    public boolean keepAlive(final HttpResponse response, final HttpContext context) {
+    public boolean keepAlive(final HttpResponse response,
+                             final HttpContext context) {
         if (response == null) {
-            throw new IllegalArgumentException("HTTP response may not be null");
+            throw new IllegalArgumentException
+                ("HTTP response may not be null.");
         }
         if (context == null) {
-            throw new IllegalArgumentException("HTTP context may not be null");
+            throw new IllegalArgumentException
+                ("HTTP context may not be null.");
         }
         
         HttpConnection conn = (HttpConnection)
@@ -73,34 +83,90 @@
             return false;
         // do NOT check for stale connection, that is an expensive operation
 
+        // Check for a self-terminating entity. If the end of the entity will
+        // be indicated by closing the connection, there is no keep-alive.
         HttpEntity entity = response.getEntity();
         ProtocolVersion ver = response.getStatusLine().getProtocolVersion();
         if (entity != null) {
             if (entity.getContentLength() < 0) {
-                if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
+                if (!entity.isChunked() ||
+                    ver.lessEquals(HttpVersion.HTTP_1_0)) {
                     // if the content length is not known and is not chunk
                     // encoded, the connection cannot be reused
                     return false;
                 }
             }
         }
-        // Check for 'Connection' directive
-        Header connheader = response.getFirstHeader(HTTP.CONN_DIRECTIVE);
-        if (connheader == null) {
-            connheader = response.getFirstHeader("Proxy-Connection");
-        }
-        if (connheader != null) {
-            String conndirective = connheader.getValue(); 
-            if (HTTP.CONN_CLOSE.equalsIgnoreCase(conndirective)) {
+
+        // Check for the "Connection" header. If that is absent, check for
+        // the "Proxy-Connection" header. The latter is an unspecified and
+        // broken but unfortunately common extension of HTTP.
+        HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE);
+        if (!hit.hasNext())
+            hit = response.headerIterator("Proxy-Connection");
+
+        // Experimental usage of the "Connection" header in HTTP/1.0 is
+        // documented in RFC 2068, section 19.7.1. A token "keep-alive" is
+        // used to indicate that the connection should be persistent.
+        // Note that the final specification of HTTP/1.1 in RFC 2616 does not
+        // include this information. Neither is the "Connection" header
+        // mentioned in RFC 1945, which informally describes HTTP/1.0.
+        //
+        // RFC 2616 specifies "close" as the only connection token with a
+        // specific meaning: it disables persistent connections.
+        //
+        // The "Proxy-Connection" header is not formally specified anywhere,
+        // but is commonly used to carry one token, "close" or "keep-alive".
+        // The "Connection" header, on the other hand, is defined as a
+        // sequence of tokens, where each token is a header name, and the
+        // token "close" has the above-mentioned additional meaning.
+        //
+        // To get through this mess, we treat the "Proxy-Connection" header
+        // in exactly the same way as the "Connection" header, but only if
+        // the latter is missing. We scan the sequence of tokens for both
+        // "close" and "keep-alive". As "close" is specified by RFC 2068,
+        // it takes precedence and indicates a non-persistent connection.
+        // If there is no "close" but a "keep-alive", we take the hint.
+
+        if (hit.hasNext()) {
+            try {
+                TokenIterator ti = createTokenIterator(hit);
+                boolean keepalive = false;
+                while (ti.hasNext()) {
+                    final String token = ti.nextToken();
+                    if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
+                        return false;
+                    } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) {
+                        // continue the loop, there may be a "close" afterwards
+                        keepalive = true;
+                    }
+                }
+                if (keepalive)
+                    return true;
+                // neither "close" nor "keep-alive", use default policy
+
+            } catch (ParseException px) {
+                // invalid connection header means no persistent connection
+                // we don't have logging in HttpCore, so the exception is lost
                 return false;
-            } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(conndirective)) {
-                return true;
-            } else {
-                // log unknown directive
             }
         }
-        // Resorting to protocol version default close connection policy
+
+        // default since HTTP/1.1 is persistent, before it was non-persistent
         return !ver.lessEquals(HttpVersion.HTTP_1_0);
     }
-            
+
+
+    /**
+     * Creates a token iterator from a header iterator.
+     * This method can be overridden to replace the implementation of
+     * the token iterator.
+     *
+     * @param hit       the header iterator
+     *
+     * @return  the token iterator
+     */
+    protected TokenIterator createTokenIterator(HeaderIterator hit) {
+        return new BasicTokenIterator(hit);
+    }
 }

Modified: jakarta/httpcomponents/httpcore/trunk/module-main/src/test/java/org/apache/http/impl/TestDefaultConnectionReuseStrategy.java
URL: http://svn.apache.org/viewvc/jakarta/httpcomponents/httpcore/trunk/module-main/src/test/java/org/apache/http/impl/TestDefaultConnectionReuseStrategy.java?rev=602506&r1=602505&r2=602506&view=diff
==============================================================================
--- jakarta/httpcomponents/httpcore/trunk/module-main/src/test/java/org/apache/http/impl/TestDefaultConnectionReuseStrategy.java (original)
+++ jakarta/httpcomponents/httpcore/trunk/module-main/src/test/java/org/apache/http/impl/TestDefaultConnectionReuseStrategy.java Sat Dec  8 08:55:13 2007
@@ -223,6 +223,78 @@
         assertFalse(reuseStrategy.keepAlive(response, context));
     }
 
+    public void testConnectionTokens1() throws Exception {
+        // Use HTTP 1.1
+        HttpResponse response =
+            createResponse(HttpVersion.HTTP_1_1, 200, "OK", true, -1);
+        response.addHeader("Connection", "yadda, cLOSe, dumdy");
+
+        assertFalse(reuseStrategy.keepAlive(response, context));
+    }
+
+    public void testConnectionTokens2() throws Exception {
+        // Use HTTP 1.1
+        HttpResponse response =
+            createResponse(HttpVersion.HTTP_1_1, 200, "OK", true, -1);
+        response.addHeader("Connection", "yadda, kEEP-alive, dumdy");
+
+        assertTrue(reuseStrategy.keepAlive(response, context));
+    }
+
+    public void testConnectionTokens3() throws Exception {
+        // Use HTTP 1.1
+        HttpResponse response =
+            createResponse(HttpVersion.HTTP_1_1, 200, "OK", true, -1);
+        response.addHeader("Connection", "yadda, keep-alive, close, dumdy");
+
+        assertFalse(reuseStrategy.keepAlive(response, context));
+    }
+
+    public void testConnectionTokens4() throws Exception {
+        // Use HTTP 1.1
+        HttpResponse response =
+            createResponse(HttpVersion.HTTP_1_1, 200, "OK", true, -1);
+        response.addHeader("Connection", "yadda, close, dumdy");
+        response.addHeader("Proxy-Connection", "keep-alive");
+
+        // Connection takes precedence over Proxy-Connection
+        assertFalse(reuseStrategy.keepAlive(response, context));
+    }
+
+    public void testConnectionTokens5() throws Exception {
+        // Use HTTP 1.1
+        HttpResponse response =
+            createResponse(HttpVersion.HTTP_1_1, 200, "OK", true, -1);
+        response.addHeader("Connection", "yadda, dumdy");
+        response.addHeader("Proxy-Connection", "close");
+
+        // Connection takes precedence over Proxy-Connection,
+        // even if it doesn't contain a recognized token.
+        // Default for HTTP/1.1 is to keep alive.
+        assertTrue(reuseStrategy.keepAlive(response, context));
+    }
+
+    public void testConnectionTokens6() throws Exception {
+        // Use HTTP 1.1
+        HttpResponse response =
+            createResponse(HttpVersion.HTTP_1_1, 200, "OK", true, -1);
+        response.addHeader("Connection", "");
+        response.addHeader("Proxy-Connection", "close");
+
+        // Connection takes precedence over Proxy-Connection,
+        // even if it is empty. Default for HTTP/1.1 is to keep alive.
+        assertTrue(reuseStrategy.keepAlive(response, context));
+    }
+
+    public void testConnectionTokensInvalid() throws Exception {
+        // Use HTTP 1.1
+        HttpResponse response =
+            createResponse(HttpVersion.HTTP_1_1, 200, "OK", true, -1);
+        response.addHeader("Connection", "keep-alive=true");
+
+        assertFalse(reuseStrategy.keepAlive(response, context));
+    }
+
 
     /**
      * Creates a response without an entity.