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 2020/11/19 11:30:21 UTC

[httpcomponents-core] 01/02: Optimized request termination by the non-blocking HTTP/1.1 protocol handler in case of a premature response or `expect-continue` handshake failure

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

commit 7ad2073d8e4d7bf1982599c103897e200dad0180
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Nov 18 19:57:01 2020 +0100

    Optimized request termination by the non-blocking HTTP/1.1 protocol handler in case of a premature response or `expect-continue` handshake failure
---
 .../hc/core5/testing/nio/Http1IntegrationTest.java | 66 ++++++++++++++++++++++
 .../http/impl/nio/ClientHttp1StreamDuplexer.java   |  6 +-
 .../http/impl/nio/ClientHttp1StreamHandler.java    |  2 +-
 3 files changed, 68 insertions(+), 6 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 67859d6..8c9a20e 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
@@ -859,6 +859,72 @@ public class Http1IntegrationTest extends InternalHttp1ServerTestBase {
     }
 
     @Test
+    public void testExpectationFailedCloseConnection() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new MessageExchangeHandler<String>(new StringAsyncEntityConsumer()) {
+
+                    @Override
+                    protected void handle(
+                            final Message<HttpRequest, String> request,
+                            final AsyncServerRequestHandler.ResponseTrigger responseTrigger,
+                            final HttpContext context) throws IOException, HttpException {
+                        responseTrigger.submitResponse(new BasicResponseProducer(HttpStatus.SC_OK, "All is well"), context);
+
+                    }
+                };
+            }
+
+        });
+        final InetSocketAddress serverEndpoint = server.start(null, new Decorator<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler handler) {
+
+                return new BasicAsyncServerExpectationDecorator(handler) {
+
+                    @Override
+                    protected AsyncResponseProducer verify(final HttpRequest request, final HttpContext context) throws IOException, HttpException {
+                        final Header h = request.getFirstHeader("password");
+                        if (h != null && "secret".equals(h.getValue())) {
+                            return null;
+                        } else {
+                            final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
+                            response.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+                            return new BasicResponseProducer(response, "You shall not pass");
+                        }
+                    }
+                };
+
+            }
+        }, Http1Config.DEFAULT);
+
+        client.start();
+        final Future<IOSession> sessionFuture = client.requestSession(
+                new HttpHost("localhost", serverEndpoint.getPort()), TIMEOUT, null);
+        final IOSession ioSession = sessionFuture.get();
+        final ClientSessionEndpoint streamEndpoint = new ClientSessionEndpoint(ioSession);
+
+        final HttpRequest request1 = new BasicHttpRequest(Method.POST, createRequestURI(serverEndpoint, "/echo"));
+        final Future<Message<HttpResponse, String>> future1 = streamEndpoint.execute(
+                new BasicRequestProducer(request1, new MultiBinEntityProducer(
+                        new byte[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'},
+                        100000,
+                        ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+        final Message<HttpResponse, String> result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        Assert.assertNotNull(result1);
+        final HttpResponse response1 = result1.getHead();
+        Assert.assertNotNull(response1);
+        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response1.getCode());
+        Assert.assertNotNull("You shall not pass", result1.getBody());
+
+        Assert.assertFalse(streamEndpoint.isOpen());
+    }
+
+    @Test
     public void testDelayedExpectationVerification() throws Exception {
         server.register("*", new Supplier<AsyncServerExchangeHandler>() {
 
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java
index ca6c9a2..3cc6758 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamDuplexer.java
@@ -186,11 +186,7 @@ public class ClientHttp1StreamDuplexer extends AbstractHttp1StreamDuplexer<HttpR
             @Override
             public boolean abortGracefully() throws IOException {
                 final MessageDelineation messageDelineation = endOutputStream(null);
-                if (messageDelineation == MessageDelineation.MESSAGE_HEAD) {
-                    requestShutdown(CloseMode.GRACEFUL);
-                    return false;
-                }
-                return true;
+                return messageDelineation != MessageDelineation.MESSAGE_HEAD;
             }
 
             @Override
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java
index e508852..1721b04 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java
@@ -234,7 +234,7 @@ class ClientHttp1StreamHandler implements ResourceHolder {
             return;
         }
         if (requestState == MessageState.BODY) {
-            if (keepAlive && status >= HttpStatus.SC_CLIENT_ERROR) {
+            if (status >= HttpStatus.SC_CLIENT_ERROR) {
                 requestState = MessageState.COMPLETE;
                 if (!outputChannel.abortGracefully()) {
                     keepAlive = false;