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 2016/11/09 20:37:53 UTC

svn commit: r1769014 - in /httpcomponents/httpcore/trunk: httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/ httpcore5-testing/src/test/java/org/apache/hc/core5/testing/n...

Author: olegk
Date: Wed Nov  9 20:37:53 2016
New Revision: 1769014

URL: http://svn.apache.org/viewvc?rev=1769014&view=rev
Log:
Expect-continue handshake logic moved from server stream handlers to server exchange handlers; client stream handlers changed to propagate informational response messages to client exchange handlers; fixed potential race conditions in stream handlers

Removed:
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ExpectationChannel.java
Modified:
    httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp2StreamHandler.java
    httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp2StreamHandler.java
    httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushHttp2StreamHandler.java
    httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/EchoHandler.java
    httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/Http1IntegrationTest.java
    httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http2/Http2IntegrationTest.java
    httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractClassicServerExchangeHandler.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java
    httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/ImmediateResponseExchangeHandler.java

Modified: httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp2StreamHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp2StreamHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp2StreamHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientHttp2StreamHandler.java Wed Nov  9 20:37:53 2016
@@ -42,6 +42,7 @@ import org.apache.hc.core5.http.HttpStat
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.ProtocolException;
 import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
+import org.apache.hc.core5.http.impl.LazyEntityDetails;
 import org.apache.hc.core5.http.impl.nio.MessageState;
 import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
@@ -53,7 +54,6 @@ import org.apache.hc.core5.http2.H2Conne
 import org.apache.hc.core5.http2.H2Error;
 import org.apache.hc.core5.http2.impl.DefaultH2RequestConverter;
 import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter;
-import org.apache.hc.core5.http.impl.LazyEntityDetails;
 
 class ClientHttp2StreamHandler implements Http2StreamHandler {
 
@@ -142,7 +142,12 @@ class ClientHttp2StreamHandler implement
             } else {
                 final Header h = request.getFirstHeader(HttpHeaders.EXPECT);
                 final boolean expectContinue = h != null && "100-continue".equalsIgnoreCase(h.getValue());
-                requestState = expectContinue ? MessageState.ACK : MessageState.BODY;
+                if (expectContinue) {
+                    requestState = MessageState.ACK;
+                } else {
+                    requestState = MessageState.BODY;
+                    exchangeHandler.produce(dataChannel);
+                }
             }
         } else {
             throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Request already committed");
@@ -180,16 +185,24 @@ class ClientHttp2StreamHandler implement
             throw new ProtocolException("Unexpected message headers");
         }
         final HttpResponse response = DefaultH2ResponseConverter.INSTANCE.convert(headers);
-        final EntityDetails entityDetails = endStream ? null : new LazyEntityDetails(response);
-
-        if (response.getCode() < 200) {
-            if (response.getCode() == HttpStatus.SC_CONTINUE && requestState == MessageState.ACK) {
+        final int status = response.getCode();
+        if (status < HttpStatus.SC_INFORMATIONAL) {
+            throw new ProtocolException("Invalid response: " + status);
+        }
+        if (status < HttpStatus.SC_SUCCESS) {
+            exchangeHandler.consumeInformation(response);
+        }
+        if (requestState == MessageState.ACK) {
+            if (status == HttpStatus.SC_CONTINUE || status >= HttpStatus.SC_SUCCESS) {
                 requestState = MessageState.BODY;
                 exchangeHandler.produce(dataChannel);
             }
+        }
+        if (status < HttpStatus.SC_SUCCESS) {
             return;
         }
 
+        final EntityDetails entityDetails = endStream ? null : new LazyEntityDetails(response);
         context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
         httpProcessor.process(response, entityDetails, context);
         connMetrics.incrementResponseCount();

Modified: httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp2StreamHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp2StreamHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp2StreamHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerHttp2StreamHandler.java Wed Nov  9 20:37:53 2016
@@ -35,7 +35,6 @@ import org.apache.hc.core5.http.EntityDe
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpConnection;
 import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
@@ -44,11 +43,9 @@ import org.apache.hc.core5.http.Protocol
 import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
 import org.apache.hc.core5.http.impl.LazyEntityDetails;
 import org.apache.hc.core5.http.impl.nio.MessageState;
-import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.nio.AsyncPushProducer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.HandlerFactory;
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
@@ -119,11 +116,27 @@ public class ServerHttp2StreamHandler im
         this.responseState = MessageState.IDLE;
     }
 
+    private void commitInformation(final HttpResponse response) throws IOException, HttpException {
+        if (responseCommitted.get()) {
+            throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Response already committed");
+        }
+        final int status = response.getCode();
+        if (status < HttpStatus.SC_INFORMATIONAL || status >= HttpStatus.SC_SUCCESS) {
+            throw new HttpException("Invalid intermediate response: " + status);
+        }
+        final List<Header> responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response);
+        outputChannel.submit(responseHeaders, false);
+    }
+
     private void commitResponse(
             final HttpResponse response,
             final EntityDetails responseEntityDetails) throws HttpException, IOException {
         if (responseCommitted.compareAndSet(false, true)) {
 
+            final int status = response.getCode();
+            if (status < HttpStatus.SC_SUCCESS) {
+                throw new HttpException("Invalid response: " + status);
+            }
             context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
             httpProcessor.process(response, responseEntityDetails, context);
 
@@ -135,7 +148,7 @@ public class ServerHttp2StreamHandler im
                 responseState = MessageState.COMPLETE;
             } else {
                 responseState = MessageState.BODY;
-                exchangeHandler.produce(dataChannel);
+                exchangeHandler.produce(outputChannel);
             }
         } else {
             throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Response already committed");
@@ -153,13 +166,6 @@ public class ServerHttp2StreamHandler im
         connMetrics.incrementRequestCount();
     }
 
-    private void commitContinue() throws IOException, HttpException {
-        final HttpResponse ack = new BasicHttpResponse(HttpStatus.SC_CONTINUE);
-        final List<Header> responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(ack);
-
-        outputChannel.submit(responseHeaders, false);
-    }
-
     @Override
     public void consumePromise(final List<Header> headers) throws HttpException, IOException {
         throw new ProtocolException("Unexpected message promise");
@@ -184,10 +190,6 @@ public class ServerHttp2StreamHandler im
         if (handler == null) {
             throw new H2StreamResetException(H2Error.REFUSED_STREAM, "Stream refused");
         }
-        if (handler == null) {
-            throw new H2ConnectionException(H2Error.INTERNAL_ERROR,
-                    "Unable to handle " + request.getMethod() + " " + request.getPath());
-        }
         exchangeHandler = handler;
 
         context.setProtocolVersion(HttpVersion.HTTP_2);
@@ -198,61 +200,26 @@ public class ServerHttp2StreamHandler im
         httpProcessor.process(request, requestEntityDetails, context);
         connMetrics.incrementRequestCount();
 
-        boolean expectContinue = false;
-        if (requestEntityDetails != null) {
-            final Header h = request.getFirstHeader(HttpHeaders.EXPECT);
-            if (h != null && "100-continue".equalsIgnoreCase(h.getValue())) {
-                expectContinue = true;
-            }
-        }
-
-        if (expectContinue) {
-            exchangeHandler.verify(request, requestEntityDetails, new ExpectationChannel() {
-
-                @Override
-                public void sendResponse(
-                        final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
-                    commitResponse(response, responseEntityDetails);
-                }
-
-                @Override
-                public void sendContinue() throws HttpException, IOException {
-                    commitContinue();
-                    exchangeHandler.handleRequest(request, requestEntityDetails, new ResponseChannel() {
-
-                        @Override
-                        public void sendResponse(
-                                final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
-                            commitResponse(response, responseEntityDetails);
-                        }
-
-                        @Override
-                        public void pushPromise(
-                                final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException {
-                            commitPromise(promise, pushProducer);
-                        }
+        exchangeHandler.handleRequest(request, requestEntityDetails, new ResponseChannel() {
 
-                    });
-                }
+            @Override
+            public void sendInformation(final HttpResponse response) throws HttpException, IOException {
+                commitInformation(response);
+            }
 
-            });
-        } else {
-            exchangeHandler.handleRequest(request, requestEntityDetails, new ResponseChannel() {
+            @Override
+            public void sendResponse(
+                    final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
+                commitResponse(response, responseEntityDetails);
+            }
 
-                @Override
-                public void sendResponse(
-                        final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
-                    commitResponse(response, responseEntityDetails);
-                }
-
-                @Override
-                public void pushPromise(
-                        final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException {
-                    commitPromise(promise, pushProducer);
-                }
+            @Override
+            public void pushPromise(
+                    final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException {
+                commitPromise(promise, pushProducer);
+            }
 
-            });
-        }
+        });
     }
 
     @Override

Modified: httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushHttp2StreamHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushHttp2StreamHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushHttp2StreamHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerPushHttp2StreamHandler.java Wed Nov  9 20:37:53 2016
@@ -37,6 +37,7 @@ import org.apache.hc.core5.http.HttpConn
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.ProtocolException;
 import org.apache.hc.core5.http.impl.BasicHttpConnectionMetrics;
@@ -46,6 +47,8 @@ import org.apache.hc.core5.http.nio.Data
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
 import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.http2.H2ConnectionException;
+import org.apache.hc.core5.http2.H2Error;
 import org.apache.hc.core5.http2.impl.DefaultH2RequestConverter;
 import org.apache.hc.core5.http2.impl.DefaultH2ResponseConverter;
 
@@ -58,6 +61,7 @@ class ServerPushHttp2StreamHandler imple
     private final BasicHttpConnectionMetrics connMetrics;
     private final AsyncPushProducer pushProducer;
     private final HttpCoreContext context;
+    private final AtomicBoolean responseCommitted;
     private final AtomicBoolean done;
 
     private volatile MessageState requestState;
@@ -100,6 +104,7 @@ class ServerPushHttp2StreamHandler imple
         this.connMetrics = connMetrics;
         this.pushProducer = pushProducer;
         this.context = HttpCoreContext.create();
+        this.responseCommitted = new AtomicBoolean(false);
         this.done = new AtomicBoolean(false);
         this.requestState = MessageState.COMPLETE;
         this.responseState = MessageState.IDLE;
@@ -136,6 +141,55 @@ class ServerPushHttp2StreamHandler imple
         }
     }
 
+    private void commitInformation(final HttpResponse response) throws IOException, HttpException {
+        if (responseCommitted.get()) {
+            throw new H2ConnectionException(H2Error.INTERNAL_ERROR, "Response already committed");
+        }
+        final int status = response.getCode();
+        if (status < HttpStatus.SC_INFORMATIONAL || status >= HttpStatus.SC_SUCCESS) {
+            throw new HttpException("Invalid intermediate response: " + status);
+        }
+        final List<Header> responseHeaders = DefaultH2ResponseConverter.INSTANCE.convert(response);
+        outputChannel.submit(responseHeaders, false);
+    }
+
+    private void commitResponse(
+            final HttpResponse response,
+            final EntityDetails responseEntityDetails) throws HttpException, IOException {
+        if (responseCommitted.compareAndSet(false, true)) {
+
+            context.setProtocolVersion(HttpVersion.HTTP_2);
+            context.setAttribute(HttpCoreContext.HTTP_CONNECTION, connection);
+            context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
+            httpProcessor.process(response, responseEntityDetails, context);
+
+            final List<Header> headers = DefaultH2ResponseConverter.INSTANCE.convert(response);
+            outputChannel.submit(headers, responseEntityDetails == null);
+            connMetrics.incrementResponseCount();
+            if (responseEntityDetails == null) {
+                responseState = MessageState.COMPLETE;
+            } else {
+                responseState = MessageState.BODY;
+                pushProducer.produce(outputChannel);
+            }
+        }
+    }
+
+    private void commitPromise(
+            final HttpRequest promise,
+            final AsyncPushProducer pushProducer) throws HttpException, IOException {
+
+        context.setProtocolVersion(HttpVersion.HTTP_2);
+        context.setAttribute(HttpCoreContext.HTTP_CONNECTION, connection);
+        context.setAttribute(HttpCoreContext.HTTP_REQUEST, promise);
+        httpProcessor.process(promise, null, context);
+
+        final List<Header> headers = DefaultH2RequestConverter.INSTANCE.convert(promise);
+
+        outputChannel.push(headers, pushProducer);
+        connMetrics.incrementRequestCount();
+    }
+
     @Override
     public void produceOutput() throws HttpException, IOException {
         switch (responseState) {
@@ -143,38 +197,21 @@ class ServerPushHttp2StreamHandler imple
                 responseState = MessageState.HEADERS;
                 pushProducer.produceResponse(new ResponseChannel() {
 
-                    private final AtomicBoolean responseCommitted = new AtomicBoolean(false);
+                    @Override
+                    public void sendInformation(final HttpResponse response) throws HttpException, IOException {
+                        commitInformation(response);
+                    }
 
                     @Override
                     public void sendResponse(
                             final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
-                        if (responseCommitted.compareAndSet(false, true)) {
-
-                            context.setProtocolVersion(HttpVersion.HTTP_2);
-                            context.setAttribute(HttpCoreContext.HTTP_CONNECTION, connection);
-                            context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
-                            httpProcessor.process(response, entityDetails, context);
-
-                            final List<Header> headers = DefaultH2ResponseConverter.INSTANCE.convert(response);
-                            outputChannel.submit(headers, entityDetails == null);
-                            responseState = entityDetails == null ? MessageState.COMPLETE : MessageState.BODY;
-                            connMetrics.incrementResponseCount();
-                        }
+                        commitResponse(response, entityDetails);
                     }
 
                     @Override
                     public void pushPromise(
                             final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException {
-
-                        context.setProtocolVersion(HttpVersion.HTTP_2);
-                        context.setAttribute(HttpCoreContext.HTTP_CONNECTION, connection);
-                        context.setAttribute(HttpCoreContext.HTTP_REQUEST, promise);
-                        httpProcessor.process(promise, null, context);
-
-                        final List<Header> headers = DefaultH2RequestConverter.INSTANCE.convert(promise);
-
-                        outputChannel.push(headers, pushProducer);
-                        connMetrics.incrementRequestCount();
+                        commitPromise(promise, pushProducer);
                     }
 
                 });

Modified: httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/EchoHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/EchoHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/EchoHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/EchoHandler.java Wed Nov  9 20:37:53 2016
@@ -33,6 +33,7 @@ import java.util.List;
 import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
@@ -40,7 +41,6 @@ import org.apache.hc.core5.http.message.
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.protocol.HttpContext;
 
@@ -69,18 +69,16 @@ public class EchoHandler implements Asyn
     }
 
     @Override
-    public void verify(
-            final HttpRequest request,
-            final EntityDetails entityDetails,
-            final ExpectationChannel expectationChannel) throws HttpException, IOException {
-        expectationChannel.sendContinue();
-    }
-
-    @Override
     public void handleRequest(
             final HttpRequest request,
             final EntityDetails entityDetails,
             final ResponseChannel responseChannel) throws HttpException, IOException {
+        if (entityDetails != null) {
+            final Header h = request.getFirstHeader(HttpHeaders.EXPECT);
+            if (h != null && "100-continue".equalsIgnoreCase(h.getValue())) {
+                responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE));
+            }
+        }
         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
         responseChannel.sendResponse(response, entityDetails);
     }

Modified: httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/Http1IntegrationTest.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/Http1IntegrationTest.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/Http1IntegrationTest.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http/Http1IntegrationTest.java Wed Nov  9 20:37:53 2016
@@ -93,7 +93,6 @@ import org.apache.hc.core5.http.nio.Basi
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.ContentEncoder;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.HandlerFactory;
 import org.apache.hc.core5.http.nio.NHttpMessageParser;
 import org.apache.hc.core5.http.nio.NHttpMessageWriter;
@@ -708,7 +707,7 @@ public class Http1IntegrationTest extend
         final Future<Message<HttpResponse, String>> future2 = streamEndpoint.execute(
                 new BasicRequestProducer(request2, new MultiLineEntityProducer("0123456789abcdef", 5000)),
                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
-        final Message<HttpResponse, String> result2 = future2.get(50000, TimeUnit.SECONDS);
+        final Message<HttpResponse, String> result2 = future2.get(TIMEOUT, TimeUnit.SECONDS);
         Assert.assertNotNull(result2);
         final HttpResponse response2 = result2.getHead();
         Assert.assertNotNull(response2);
@@ -762,29 +761,29 @@ public class Http1IntegrationTest extend
                     }
 
                     @Override
-                    public void verify(
+                    public void handleRequest(
                             final HttpRequest request,
                             final EntityDetails entityDetails,
-                            final ExpectationChannel expectationChannel) throws HttpException, IOException {
+                            final ResponseChannel responseChannel) throws HttpException, IOException {
+
                         Executors.newSingleThreadExecutor().execute(new Runnable() {
                             @Override
                             public void run() {
                                 try {
-                                    Thread.sleep(random.nextInt(1000));
-                                    expectationChannel.sendContinue();
+                                    if (entityDetails != null) {
+                                        final Header h = request.getFirstHeader(HttpHeaders.EXPECT);
+                                        if (h != null && "100-continue".equalsIgnoreCase(h.getValue())) {
+                                            Thread.sleep(random.nextInt(1000));
+                                            responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE));
+                                        }
+                                        final HttpResponse response = new BasicHttpResponse(200);
+                                        responseChannel.sendResponse(response, entityProducer);
+                                    }
                                 } catch (Exception ignore) {
                                 }
                             }
                         });
-                    }
 
-                    @Override
-                    public void handleRequest(
-                            final HttpRequest request,
-                            final EntityDetails entityDetails,
-                            final ResponseChannel responseChannel) throws HttpException, IOException {
-                        final HttpResponse response = new BasicHttpResponse(200);
-                        responseChannel.sendResponse(response, entityProducer);
                     }
 
                     @Override
@@ -803,12 +802,16 @@ public class Http1IntegrationTest extend
 
                     @Override
                     public int available() {
-                        return entityProducer.available();
+                        synchronized (entityProducer) {
+                            return entityProducer.available();
+                        }
                     }
 
                     @Override
                     public void produce(final DataStreamChannel channel) throws IOException {
-                        entityProducer.produce(channel);
+                        synchronized (entityProducer) {
+                            entityProducer.produce(channel);
+                        }
                     }
 
                     @Override
@@ -863,11 +866,19 @@ public class Http1IntegrationTest extend
                     }
 
                     @Override
-                    public void verify(
+                    public void handleRequest(
                             final HttpRequest request,
                             final EntityDetails entityDetails,
-                            final ExpectationChannel expectationChannel) throws HttpException, IOException {
-                        expectationChannel.sendContinue();
+                            final ResponseChannel responseChannel) throws HttpException, IOException {
+                        final AsyncResponseProducer producer;
+                        final Header h = request.getFirstHeader("password");
+                        if (h != null && "secret".equals(h.getValue())) {
+                            producer = new BasicResponseProducer(HttpStatus.SC_OK, "All is well");
+                        } else {
+                            producer = new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass");
+                        }
+                        responseProducer.set(producer);
+                        responseChannel.sendResponse(producer.produceResponse(), producer.getEntityDetails());
                     }
 
                     @Override
@@ -885,22 +896,6 @@ public class Http1IntegrationTest extend
                     }
 
                     @Override
-                    public void handleRequest(
-                            final HttpRequest request,
-                            final EntityDetails entityDetails,
-                            final ResponseChannel responseChannel) throws HttpException, IOException {
-                        final AsyncResponseProducer producer;
-                        final Header h = request.getFirstHeader("password");
-                        if (h != null && "secret".equals(h.getValue())) {
-                            producer = new BasicResponseProducer(HttpStatus.SC_OK, "All is well");
-                        } else {
-                            producer = new BasicResponseProducer(HttpStatus.SC_UNAUTHORIZED, "You shall not pass");
-                        }
-                        responseProducer.set(producer);
-                        responseChannel.sendResponse(producer.produceResponse(), producer.getEntityDetails());
-                    }
-
-                    @Override
                     public int available() {
                         final AsyncResponseProducer producer = responseProducer.get();
                         return producer.available();

Modified: httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http2/Http2IntegrationTest.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http2/Http2IntegrationTest.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http2/Http2IntegrationTest.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/http2/Http2IntegrationTest.java Wed Nov  9 20:37:53 2016
@@ -75,7 +75,6 @@ import org.apache.hc.core5.http.nio.Basi
 import org.apache.hc.core5.http.nio.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.nio.Supplier;
 import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer;
@@ -736,14 +735,6 @@ public class Http2IntegrationTest extend
                     }
 
                     @Override
-                    public void verify(
-                            final HttpRequest request,
-                            final EntityDetails entityDetails,
-                            final ExpectationChannel expectationChannel) throws HttpException, IOException {
-                        expectationChannel.sendContinue();
-                    }
-
-                    @Override
                     public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
                         capacityChannel.update(Integer.MAX_VALUE);
                     }

Modified: httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/AsyncReverseProxyExample.java Wed Nov  9 20:37:53 2016
@@ -75,7 +75,6 @@ import org.apache.hc.core5.http.nio.Asyn
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.RequestChannel;
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.nio.Supplier;
@@ -286,14 +285,6 @@ public class AsyncReverseProxyExample {
         }
 
         @Override
-        public void verify(
-                final HttpRequest request,
-                final EntityDetails entityDetails,
-                final ExpectationChannel expectationChannel) throws HttpException, IOException {
-            expectationChannel.sendContinue();
-        }
-
-        @Override
         public void handleRequest(
                 final HttpRequest incomingRequest,
                 final EntityDetails entityDetails,
@@ -305,6 +296,13 @@ public class AsyncReverseProxyExample {
                 exchangeState.request = incomingRequest;
                 exchangeState.inputEnd = entityDetails == null;
                 exchangeState.responseMessageChannel = responseChannel;
+
+                if (entityDetails != null) {
+                    final Header h = incomingRequest.getFirstHeader(HttpHeaders.EXPECT);
+                    if (h != null && "100-continue".equalsIgnoreCase(h.getValue())) {
+                        responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE));
+                    }
+                }
             }
 
             System.out.println("[proxy->origin] " + exchangeState.id + " request connection to " + targetHost);

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractClassicServerExchangeHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractClassicServerExchangeHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractClassicServerExchangeHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractClassicServerExchangeHandler.java Wed Nov  9 20:37:53 2016
@@ -52,7 +52,6 @@ import org.apache.hc.core5.http.message.
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.nio.entity.ContentInputStream;
 import org.apache.hc.core5.http.nio.entity.ContentOutputStream;
@@ -98,19 +97,17 @@ public abstract class AbstractClassicSer
             HttpContext context) throws IOException, HttpException;
 
     @Override
-    public final void verify(
-            final HttpRequest request,
-            final EntityDetails entityDetails,
-            final ExpectationChannel expectationChannel) throws HttpException, IOException {
-        expectationChannel.sendContinue();
-    }
-
-    @Override
     public final void handleRequest(
             final HttpRequest request,
             final EntityDetails entityDetails,
             final ResponseChannel responseChannel) throws HttpException, IOException {
 
+        if (entityDetails != null) {
+            final Header h = request.getFirstHeader(HttpHeaders.EXPECT);
+            if (h != null && "100-continue".equalsIgnoreCase(h.getValue())) {
+                responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE));
+            }
+        }
         final AtomicBoolean responseCommitted = new AtomicBoolean(false);
 
         final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/AbstractHttp1StreamDuplexer.java Wed Nov  9 20:37:53 2016
@@ -35,6 +35,7 @@ import java.nio.channels.ClosedChannelEx
 import java.nio.channels.ReadableByteChannel;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -81,6 +82,7 @@ abstract class AbstractHttp1StreamDuplex
     private final NHttpMessageWriter<OutgoingMessage> outgoingMessageWriter;
     private final ConnectionListener connectionListener;
     private final Lock outputLock;
+    private final AtomicInteger outputRequests;
 
     private volatile Message<IncomingMessage, ContentDecoder> incomingMessage;
     private volatile Message<OutgoingMessage, ContentEncoder> outgoingMessage;
@@ -109,6 +111,7 @@ abstract class AbstractHttp1StreamDuplex
         this.outgoingMessageWriter = outgoingMessageWriter;
         this.connectionListener = connectionListener;
         this.outputLock = new ReentrantLock();
+        this.outputRequests = new AtomicInteger(0);
         this.connState = ConnectionState.READY;
     }
 
@@ -218,6 +221,7 @@ abstract class AbstractHttp1StreamDuplex
                             break;
                         } else {
                             inputEnd();
+                            ioSession.setEvent(SelectionKey.OP_READ);
                         }
                     }
                 } while (bytesRead > 0);
@@ -238,6 +242,7 @@ abstract class AbstractHttp1StreamDuplex
                 if (contentDecoder.isCompleted()) {
                     incomingMessage = null;
                     inputEnd();
+                    ioSession.setEvent(SelectionKey.OP_READ);
                 }
                 if (bytesRead == 0) {
                     break;
@@ -262,15 +267,21 @@ abstract class AbstractHttp1StreamDuplex
             if (isOutputReady()) {
                 produceOutput();
             } else {
+                final int pendingOutputRequests = outputRequests.get();
+                final boolean outputPending;
                 outputLock.lock();
                 try {
-                    if (!outbuf.hasData()) {
-                        ioSession.clearEvent(SelectionKey.OP_WRITE);
-                    }
+                    outputPending = outbuf.hasData();
                 } finally {
                     outputLock.unlock();
                 }
+                if (!outputPending && outputRequests.compareAndSet(pendingOutputRequests, 0)) {
+                    ioSession.clearEvent(SelectionKey.OP_WRITE);
+                } else {
+                    outputRequests.addAndGet(-pendingOutputRequests);
+                }
             }
+
             outputLock.lock();
             final boolean outputEnd;
             try {
@@ -280,21 +291,17 @@ abstract class AbstractHttp1StreamDuplex
             }
             if (outputEnd) {
                 outputEnd();
-                processCommands();
+                if (connState.compareTo(ConnectionState.ACTIVE) == 0) {
+                    processCommands();
+                } else if (connState.compareTo(ConnectionState.GRACEFUL_SHUTDOWN) >= 0 && inputIdle() && outputIdle()) {
+                    connState = ConnectionState.SHUTDOWN;
+                }
             }
         }
-        outputLock.lock();
-        try {
-            if (connState.compareTo(ConnectionState.GRACEFUL_SHUTDOWN) >= 0 && inputIdle() && outputIdle()) {
-                connState = ConnectionState.SHUTDOWN;
-            }
-            if (!outbuf.hasData() && connState.compareTo(ConnectionState.SHUTDOWN) >= 0) {
-                ioSession.close();
-                cancelPendingCommands();
-                releaseResources();
-            }
-        } finally {
-            outputLock.unlock();
+        if (connState.compareTo(ConnectionState.SHUTDOWN) >= 0) {
+            ioSession.close();
+            cancelPendingCommands();
+            releaseResources();
         }
     }
 
@@ -371,6 +378,7 @@ abstract class AbstractHttp1StreamDuplex
     }
 
     void requestSessionOutput() {
+        outputRequests.incrementAndGet();
         ioSession.setEvent(SelectionKey.OP_WRITE);
     }
 

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ClientHttp1StreamHandler.java Wed Nov  9 20:37:53 2016
@@ -175,6 +175,7 @@ class ClientHttp1StreamHandler implement
                     connection.setSocketTimeout(h1Config.getWaitForContinueTimeout());
                 } else {
                     requestState = MessageState.BODY;
+                    exchangeHandler.produce(internalDataChannel);
                 }
             }
         } else {
@@ -204,36 +205,32 @@ class ClientHttp1StreamHandler implement
         }
     }
 
-    private void validateStatus(final HttpResponse response) throws ProtocolException {
-        if (response.getCode() < HttpStatus.SC_INFORMATIONAL) {
-            throw new ProtocolException("Invalid response code");
-        }
-        if (response.getCode() < HttpStatus.SC_SUCCESS) {
-            if (response.getCode() != HttpStatus.SC_CONTINUE) {
-                throw new ProtocolException("Unsupported intermediate response code");
-            }
-        }
-    }
-
     void consumeHeader(final HttpResponse response, final boolean endStream) throws HttpException, IOException {
         if (done.get() || responseState != MessageState.HEADERS) {
             throw new ProtocolException("Unexpected message head");
         }
 
-        validateStatus(response);
-
+        final int status = response.getCode();
+        if (status < HttpStatus.SC_INFORMATIONAL) {
+            throw new ProtocolException("Invalid response: " + status);
+        }
+        if (status < HttpStatus.SC_SUCCESS) {
+            exchangeHandler.consumeInformation(response);
+        }
         if (requestState == MessageState.ACK) {
-            connection.setSocketTimeout(timeout);
-            requestState = MessageState.BODY;
-            if (response.getCode() < HttpStatus.SC_CLIENT_ERROR) {
-                exchangeHandler.produce(internalDataChannel);
+            if (status == HttpStatus.SC_CONTINUE || status >= HttpStatus.SC_SUCCESS) {
+                connection.setSocketTimeout(timeout);
+                requestState = MessageState.BODY;
+                if (status < HttpStatus.SC_CLIENT_ERROR) {
+                    exchangeHandler.produce(internalDataChannel);
+                }
             }
         }
-        if (response.getCode() == HttpStatus.SC_CONTINUE) {
+        if (status < HttpStatus.SC_SUCCESS) {
             return;
         }
         if (requestState == MessageState.BODY) {
-            boolean keepAlive = response.getCode() < HttpStatus.SC_CLIENT_ERROR;
+            boolean keepAlive = status < HttpStatus.SC_CLIENT_ERROR;
             if (keepAlive) {
                 final Header h = response.getFirstHeader(HttpHeaders.CONNECTION);
                 if (h != null && HeaderElements.CLOSE.equalsIgnoreCase(h.getValue())) {

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/ServerHttp1StreamHandler.java Wed Nov  9 20:37:53 2016
@@ -36,7 +36,6 @@ import org.apache.hc.core5.http.EntityDe
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpConnection;
 import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
@@ -47,14 +46,12 @@ import org.apache.hc.core5.http.Protocol
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.UnsupportedHttpVersionException;
 import org.apache.hc.core5.http.impl.LazyEntityDetails;
-import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.nio.AsyncPushProducer;
 import org.apache.hc.core5.http.nio.AsyncResponseProducer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.ContentDecoder;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.HandlerFactory;
 import org.apache.hc.core5.http.nio.ResourceHolder;
 import org.apache.hc.core5.http.nio.ResponseChannel;
@@ -145,6 +142,11 @@ class ServerHttp1StreamHandler implement
             final EntityDetails responseEntityDetails) throws HttpException, IOException {
         if (responseCommitted.compareAndSet(false, true)) {
 
+            final int status = response.getCode();
+            if (status < HttpStatus.SC_SUCCESS) {
+                throw new HttpException("Invalid response: " + status);
+            }
+
             Asserts.notNull(receivedRequest, "Received request");
             final String method = receivedRequest.getMethod();
             context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
@@ -164,14 +166,19 @@ class ServerHttp1StreamHandler implement
         }
     }
 
-    private void commitContinue() throws IOException, HttpException {
-        final HttpResponse ack = new BasicHttpResponse(HttpStatus.SC_CONTINUE);
-        outputChannel.submit(ack, false);
-        responseState = MessageState.ACK;
+    private void commitInformation(final HttpResponse response) throws IOException, HttpException {
+        if (responseCommitted.get()) {
+            throw new HttpException("Response already committed");
+        }
+        final int status = response.getCode();
+        if (status < HttpStatus.SC_INFORMATIONAL || status >= HttpStatus.SC_SUCCESS) {
+            throw new HttpException("Invalid intermediate response: " + status);
+        }
+        outputChannel.submit(response, true);
     }
 
     private void commitPromise() throws HttpException {
-        throw new ProtocolException("HTTP/1.1 does not support server push");
+        throw new HttpException("HTTP/1.1 does not support server push");
     }
 
     void activateChannel() throws IOException, HttpException {
@@ -218,15 +225,6 @@ class ServerHttp1StreamHandler implement
         receivedRequest = request;
         requestState = requestEndStream ? MessageState.COMPLETE : MessageState.BODY;
 
-        final EntityDetails requestEntityDetails = requestEndStream ? null : new LazyEntityDetails(request);
-        boolean expectContinue = false;
-        if (requestEntityDetails != null) {
-            final Header h = request.getFirstHeader(HttpHeaders.EXPECT);
-            if (h != null && "100-continue".equalsIgnoreCase(h.getValue())) {
-                expectContinue = true;
-            }
-        }
-
         AsyncServerExchangeHandler handler;
         try {
             handler = exchangeHandlerFactory.create(request);
@@ -248,54 +246,41 @@ class ServerHttp1StreamHandler implement
 
         exchangeHandler.setContext(context);
 
+        final EntityDetails requestEntityDetails = requestEndStream ? null : new LazyEntityDetails(request);
         try {
             httpProcessor.process(request, requestEntityDetails, context);
         } catch (HttpException ex) {
-            expectContinue = false;
             final AsyncResponseProducer responseProducer = handleException(ex);
             exchangeHandler = new ImmediateResponseExchangeHandler(responseProducer);
+            exchangeHandler.setContext(context);
         }
 
-        if (expectContinue) {
-            exchangeHandler.verify(request, requestEntityDetails, new ExpectationChannel() {
-
-                @Override
-                public void sendResponse(
-                        final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
-                    validateResponse(response, responseEntityDetails);
-                    commitResponse(response, responseEntityDetails);
-                }
+        exchangeHandler.handleRequest(request, requestEntityDetails, new ResponseChannel() {
 
-                @Override
-                public void sendContinue() throws HttpException, IOException {
-                    commitContinue();
-                }
+            @Override
+            public void sendInformation(final HttpResponse response) throws HttpException, IOException {
+                commitInformation(response);
+            }
 
-            });
-        } else {
-            exchangeHandler.handleRequest(request, requestEntityDetails, new ResponseChannel() {
+            @Override
+            public void sendResponse(
+                    final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
+                validateResponse(response, responseEntityDetails);
+                commitResponse(response, responseEntityDetails);
+            }
 
-                @Override
-                public void sendResponse(
-                        final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
-                    validateResponse(response, responseEntityDetails);
-                    commitResponse(response, responseEntityDetails);
-                }
+            @Override
+            public void pushPromise(
+                    final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException {
+                commitPromise();
+            }
 
-                @Override
-                public void pushPromise(
-                        final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException {
-                    commitPromise();
-                }
+        });
 
-            });
-        }
     }
 
     boolean isOutputReady() {
         switch (responseState) {
-            case ACK:
-                return true;
             case BODY:
                 return exchangeHandler.available() > 0;
             default:
@@ -305,26 +290,6 @@ class ServerHttp1StreamHandler implement
 
     void produceOutput() throws HttpException, IOException {
         switch (responseState) {
-            case ACK:
-                responseState = MessageState.HEADERS;
-                Asserts.notNull(receivedRequest, "Received request");
-                exchangeHandler.handleRequest(receivedRequest, new LazyEntityDetails(receivedRequest), new ResponseChannel() {
-
-                    @Override
-                    public void sendResponse(
-                            final HttpResponse response, final EntityDetails responseEntityDetails) throws HttpException, IOException {
-                        validateResponse(response, responseEntityDetails);
-                        commitResponse(response, responseEntityDetails);
-                    }
-
-                    @Override
-                    public void pushPromise(
-                            final HttpRequest promise, final AsyncPushProducer pushProducer) throws HttpException, IOException {
-                        commitPromise();
-                    }
-
-                });
-                break;
             case BODY:
                 exchangeHandler.produce(internalDataChannel);
                 break;

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncClientExchangeHandler.java Wed Nov  9 20:37:53 2016
@@ -44,6 +44,8 @@ public interface AsyncClientExchangeHand
 
     void consumeResponse(HttpResponse response, EntityDetails entityDetails) throws HttpException, IOException;
 
+    void consumeInformation(HttpResponse response) throws HttpException, IOException;
+
     void failed(Exception cause);
 
     void cancel();

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/AsyncServerExchangeHandler.java Wed Nov  9 20:37:53 2016
@@ -43,11 +43,6 @@ public interface AsyncServerExchangeHand
 
     void setContext(HttpContext context);
 
-    void verify(
-            HttpRequest request,
-            EntityDetails entityDetails,
-            ExpectationChannel expectationChannel) throws HttpException, IOException;
-
     void handleRequest(
             HttpRequest request,
             EntityDetails entityDetails,

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/ResponseChannel.java Wed Nov  9 20:37:53 2016
@@ -46,6 +46,8 @@ import org.apache.hc.core5.http.HttpResp
 @Contract(threading = ThreadingBehavior.SAFE)
 public interface ResponseChannel {
 
+    void sendInformation(HttpResponse response) throws HttpException, IOException;
+
     void sendResponse(HttpResponse response, EntityDetails entityDetails) throws HttpException, IOException;
 
     void pushPromise(HttpRequest promise, AsyncPushProducer pushProducer) throws HttpException, IOException;

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractServerExchangeHandler.java Wed Nov  9 20:37:53 2016
@@ -35,8 +35,10 @@ import org.apache.hc.core5.concurrent.Fu
 import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.nio.AsyncPushProducer;
 import org.apache.hc.core5.http.nio.AsyncRequestConsumer;
 import org.apache.hc.core5.http.nio.AsyncResponseProducer;
@@ -44,7 +46,6 @@ import org.apache.hc.core5.http.nio.Asyn
 import org.apache.hc.core5.http.nio.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.util.Asserts;
@@ -86,21 +87,6 @@ public abstract class AbstractServerExch
     }
 
     @Override
-    public final void verify(
-            final HttpRequest request,
-            final EntityDetails entityDetails,
-            final ExpectationChannel expectationChannel) throws HttpException, IOException {
-        final AsyncResponseProducer producer = verify(request, context);
-        if (producer != null) {
-            expectationFailed = true;
-            responseProducerRef.set(producer);
-            expectationChannel.sendResponse(producer.produceResponse(), producer.getEntityDetails());
-        } else {
-            expectationChannel.sendContinue();
-        }
-    }
-
-    @Override
     public final void handleRequest(
             final HttpRequest request,
             final EntityDetails entityDetails,
@@ -111,6 +97,21 @@ public abstract class AbstractServerExch
             throw new HttpException("Unable to handle request");
         }
         requestConsumerRef.set(requestConsumer);
+
+        if (entityDetails != null) {
+            final Header h = request.getFirstHeader(HttpHeaders.EXPECT);
+            if (h != null && "100-continue".equalsIgnoreCase(h.getValue())) {
+                final AsyncResponseProducer producer = verify(request, context);
+                if (producer != null) {
+                    expectationFailed = true;
+                    responseProducerRef.set(producer);
+                    responseChannel.sendResponse(producer.produceResponse(), producer.getEntityDetails());
+                    return;
+                } else {
+                    responseChannel.sendInformation(new BasicHttpResponse(HttpStatus.SC_CONTINUE));
+                }
+            }
+        }
         final ResponseTrigger responseTrigger = new ResponseTrigger() {
 
             @Override

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/BasicClientExchangeHandler.java Wed Nov  9 20:37:53 2016
@@ -88,6 +88,10 @@ public class BasicClientExchangeHandler<
     }
 
     @Override
+    public void consumeInformation(final HttpResponse response) throws HttpException, IOException {
+    }
+
+    @Override
     public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
         if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) {
             outputTerminated.set(true);

Modified: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/ImmediateResponseExchangeHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/ImmediateResponseExchangeHandler.java?rev=1769014&r1=1769013&r2=1769014&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/ImmediateResponseExchangeHandler.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/ImmediateResponseExchangeHandler.java Wed Nov  9 20:37:53 2016
@@ -42,7 +42,6 @@ import org.apache.hc.core5.http.nio.Asyn
 import org.apache.hc.core5.http.nio.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.ExpectationChannel;
 import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer;
 import org.apache.hc.core5.http.protocol.HttpContext;
@@ -80,14 +79,6 @@ public final class ImmediateResponseExch
     }
 
     @Override
-    public void verify(
-            final HttpRequest request,
-            final EntityDetails entityDetails,
-            final ExpectationChannel expectationChannel) throws HttpException, IOException {
-        expectationChannel.sendContinue();
-    }
-
-    @Override
     public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
         capacityChannel.update(Integer.MAX_VALUE);
     }