You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2021/09/04 09:03:25 UTC

[httpcomponents-core] branch master updated: Bug fix: async HTTP/1.1 server side protocol handler fails to correctly terminate message exchanges with identity transfer encoded responses

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git


The following commit(s) were added to refs/heads/master by this push:
     new c1e18d5  Bug fix: async HTTP/1.1 server side protocol handler fails to correctly terminate message exchanges with identity transfer encoded responses
c1e18d5 is described below

commit c1e18d58090cc9e779724a41f4e01b88ad6b2114
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Fri Sep 3 13:35:57 2021 +0200

    Bug fix: async HTTP/1.1 server side protocol handler fails to correctly terminate message exchanges with identity transfer encoded responses
---
 .../hc/core5/testing/nio/Http1IntegrationTest.java | 98 +++++++++++++++++++---
 .../http/impl/nio/AbstractHttp1StreamDuplexer.java |  8 ++
 .../http/impl/nio/ServerHttp1StreamDuplexer.java   |  8 +-
 3 files changed, 101 insertions(+), 13 deletions(-)

diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
index 3975996..a243646 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
@@ -112,6 +112,7 @@ import org.apache.hc.core5.http.nio.support.BasicRequestConsumer;
 import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
 import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http.nio.support.BasicResponseProducer;
+import org.apache.hc.core5.http.nio.support.ImmediateResponseExchangeHandler;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityConsumer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityProducer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicServerExchangeHandler;
@@ -249,19 +250,92 @@ public class Http1IntegrationTest extends InternalHttp1ServerTestBase {
         final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT);
 
         client.start();
-        final Future<ClientSessionEndpoint> connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT);
-        final ClientSessionEndpoint streamEndpoint = connectFuture.get();
 
-        final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
-                new BasicRequestProducer(Method.GET, createRequestURI(serverEndpoint, "/hello")),
-                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
-        final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
-        Assert.assertNotNull(result);
-        final HttpResponse response = result.getHead();
-        final String entity = result.getBody();
-        Assert.assertNotNull(response);
-        Assert.assertEquals(200, response.getCode());
-        Assert.assertEquals("Hi there", entity);
+        final int reqNo = 5;
+
+        for (int i = 0; i < reqNo; i++) {
+            final Future<ClientSessionEndpoint> connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT);
+            final ClientSessionEndpoint streamEndpoint = connectFuture.get();
+
+            final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
+                    new BasicRequestProducer(Method.GET, createRequestURI(serverEndpoint, "/hello")),
+                    new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+            final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+            streamEndpoint.close();
+
+            Assert.assertNotNull(result);
+            final HttpResponse response = result.getHead();
+            final String entity = result.getBody();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(200, response.getCode());
+            Assert.assertEquals("Hi there", entity);
+        }
+
+    }
+
+    @Test
+    public void testPostIdentityTransfer() throws Exception {
+        server.register("/hello", () -> new SingleLineResponseHandler("Hi there"));
+        final HttpProcessor httpProcessor = new DefaultHttpProcessor(new RequestValidateHost());
+        final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT);
+
+        client.start();
+
+        final int reqNo = 5;
+
+        for (int i = 0; i < reqNo; i++) {
+            final Future<ClientSessionEndpoint> connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT);
+            final ClientSessionEndpoint streamEndpoint = connectFuture.get();
+
+            final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
+                    new BasicRequestProducer(Method.POST,
+                            createRequestURI(serverEndpoint, "/hello"),
+                            new MultiLineEntityProducer("Hello", 16 * i)),
+                    new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+            final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+            streamEndpoint.close();
+
+            Assert.assertNotNull(result);
+            final HttpResponse response = result.getHead();
+            final String entity = result.getBody();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(200, response.getCode());
+            Assert.assertEquals("Hi there", entity);
+        }
+    }
+
+    @Test
+    public void testPostIdentityTransferOutOfSequenceResponse() throws Exception {
+        server.register("/hello", () -> new ImmediateResponseExchangeHandler(500, "Go away"));
+        final HttpProcessor httpProcessor = new DefaultHttpProcessor(new RequestValidateHost());
+        final InetSocketAddress serverEndpoint = server.start(httpProcessor, Http1Config.DEFAULT);
+
+        client.start();
+
+        final int reqNo = 5;
+
+        for (int i = 0; i < reqNo; i++) {
+            final Future<ClientSessionEndpoint> connectFuture = client.connect("localhost", serverEndpoint.getPort(), TIMEOUT);
+            final ClientSessionEndpoint streamEndpoint = connectFuture.get();
+
+            final Future<Message<HttpResponse, String>> future = streamEndpoint.execute(
+                    new BasicRequestProducer(Method.POST,
+                            createRequestURI(serverEndpoint, "/hello"),
+                            new MultiLineEntityProducer("Hello", 16 * i)),
+                    new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+            final Message<HttpResponse, String> result = future.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+
+            streamEndpoint.close();
+
+            Assert.assertNotNull(result);
+            final HttpResponse response = result.getHead();
+            final String entity = result.getBody();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(500, response.getCode());
+            Assert.assertEquals("Go away", entity);
+        }
     }
 
     @Test
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java
index 35cf169..0a24189 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java
@@ -140,6 +140,14 @@ abstract class AbstractHttp1StreamDuplexer<IncomingMessage extends HttpMessage,
         return ioSession.getId();
     }
 
+    boolean isActive() {
+        return connState == ConnectionState.ACTIVE;
+    }
+
+    boolean isShuttingDown() {
+        return connState.compareTo(ConnectionState.GRACEFUL_SHUTDOWN) >= 0;
+    }
+
     void shutdownSession(final CloseMode closeMode) {
         if (closeMode == CloseMode.GRACEFUL) {
             connState = ConnectionState.GRACEFUL_SHUTDOWN;
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java
index 2b6481b..d256fef 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamDuplexer.java
@@ -393,6 +393,9 @@ public class ServerHttp1StreamDuplexer extends AbstractHttp1StreamDuplexer<HttpR
             }
             incoming = null;
         }
+        if (isShuttingDown() && outputIdle() && inputIdle()) {
+            shutdownSession(CloseMode.IMMEDIATE);
+        }
     }
 
     @Override
@@ -423,7 +426,7 @@ public class ServerHttp1StreamDuplexer extends AbstractHttp1StreamDuplexer<HttpR
             }
             outgoing = null;
         }
-        if (outgoing == null && isOpen()) {
+        if (outgoing == null && isActive()) {
             final ServerHttp1StreamHandler handler = pipeline.poll();
             if (handler != null) {
                 outgoing = handler;
@@ -433,6 +436,9 @@ public class ServerHttp1StreamDuplexer extends AbstractHttp1StreamDuplexer<HttpR
                 }
             }
         }
+        if (isShuttingDown() && outputIdle() && inputIdle()) {
+            shutdownSession(CloseMode.IMMEDIATE);
+        }
     }
 
     @Override