From ad5759875a692b41eb7de9731844bdae3da04835 Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Tue, 14 Oct 2014 20:05:09 +0400 Subject: [PATCH 1/2] Allow custom processing for "Response already submitted" errors --- .../http/nio/protocol/HttpAsyncService.java | 30 +++++++++++++------ .../nio/protocol/TestHttpAsyncService.java | 10 +++---- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java b/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java index 12d278028c..af781e9877 100644 --- a/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java +++ b/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java @@ -31,6 +31,7 @@ import java.net.SocketTimeoutException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.http.ConnectionReuseStrategy; import org.apache.http.ExceptionLogger; @@ -640,6 +641,17 @@ protected HttpAsyncResponseProducer handleException( new NStringEntity(message, ContentType.DEFAULT_TEXT), false); } + protected void handleAlreadySubmittedResponse( + final Cancellable cancellable, final HttpContext context) { + throw new IllegalStateException("Response already submitted"); + } + + protected void handleAlreadySubmittedResponse( + final HttpAsyncResponseProducer responseProducer, + final HttpContext context) { + throw new IllegalStateException("Response already submitted"); + } + private boolean canResponseHaveBody(final HttpRequest request, final HttpResponse response) { if (request != null && "HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) { return false; @@ -948,16 +960,15 @@ public String toString() { } - static class HttpAsyncExchangeImpl implements HttpAsyncExchange { + class HttpAsyncExchangeImpl implements HttpAsyncExchange { + private final AtomicBoolean completed = new AtomicBoolean(); private final HttpRequest request; private final HttpResponse response; private final State state; private final NHttpServerConnection conn; private final HttpContext context; - private volatile boolean completed; - public HttpAsyncExchangeImpl( final HttpRequest request, final HttpResponse response, @@ -984,8 +995,9 @@ public HttpResponse getResponse() { @Override public void setCallback(final Cancellable cancellable) { - Asserts.check(!this.completed, "Response already submitted"); - if (this.state.isTerminated() && cancellable != null) { + if (this.completed.get()) { + handleAlreadySubmittedResponse(cancellable, context); + } else if (this.state.isTerminated() && cancellable != null) { cancellable.cancel(); } else { this.state.setCancellable(cancellable); @@ -995,9 +1007,9 @@ public void setCallback(final Cancellable cancellable) { @Override public void submitResponse(final HttpAsyncResponseProducer responseProducer) { Args.notNull(responseProducer, "Response producer"); - Asserts.check(!this.completed, "Response already submitted"); - this.completed = true; - if (!this.state.isTerminated()) { + if (this.completed.getAndSet(true)) { + handleAlreadySubmittedResponse(responseProducer, context); + } else if (!this.state.isTerminated()) { final HttpResponse response = responseProducer.generateResponse(); final Outgoing outgoing = new Outgoing( this.request, response, responseProducer, this.context); @@ -1023,7 +1035,7 @@ public void submitResponse() { @Override public boolean isCompleted() { - return this.completed; + return this.completed.get(); } @Override diff --git a/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestHttpAsyncService.java b/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestHttpAsyncService.java index 099bbe570e..c41656fb87 100644 --- a/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestHttpAsyncService.java +++ b/httpcore-nio/src/test/java/org/apache/http/nio/protocol/TestHttpAsyncService.java @@ -559,7 +559,7 @@ public void testRequestExpectationFailed() throws Exception { this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state); final HttpContext exchangeContext = new BasicHttpContext(); - final HttpAsyncExchange httpexchanage = new HttpAsyncService.HttpAsyncExchangeImpl( + final HttpAsyncExchange httpexchanage = protocolHandler.new HttpAsyncExchangeImpl( new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1), new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"), state, this.conn, exchangeContext); @@ -589,7 +589,7 @@ public void testRequestExpectationFailedInvalidResponseProducer() throws Excepti this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state); final HttpContext exchangeContext = new BasicHttpContext(); - final HttpAsyncExchange httpexchanage = new HttpAsyncService.HttpAsyncExchangeImpl( + final HttpAsyncExchange httpexchanage = protocolHandler.new HttpAsyncExchangeImpl( new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1), new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"), state, this.conn, exchangeContext); @@ -687,7 +687,7 @@ public void testRequestContinue() throws Exception { this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state); final HttpContext exchangeContext = new BasicHttpContext(); - final HttpAsyncExchange httpexchanage = new HttpAsyncService.HttpAsyncExchangeImpl( + final HttpAsyncExchange httpexchanage = protocolHandler.new HttpAsyncExchangeImpl( new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1), new BasicHttpResponse(HttpVersion.HTTP_1_1, 100, "Continue"), state, this.conn, exchangeContext); @@ -1191,7 +1191,7 @@ public void testResponseTrigger() throws Exception { this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state); final HttpContext exchangeContext = new BasicHttpContext(); - final HttpAsyncExchange httpexchanage = new HttpAsyncService.HttpAsyncExchangeImpl( + final HttpAsyncExchange httpexchanage = protocolHandler.new HttpAsyncExchangeImpl( new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1), new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"), state, this.conn, exchangeContext); @@ -1221,7 +1221,7 @@ public void testResponseTriggerInvalidResponseProducer() throws Exception { this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state); final HttpContext exchangeContext = new BasicHttpContext(); - final HttpAsyncExchange httpexchanage = new HttpAsyncService.HttpAsyncExchangeImpl( + final HttpAsyncExchange httpexchanage = protocolHandler.new HttpAsyncExchangeImpl( new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1), new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK"), state, this.conn, exchangeContext); From b8761f4ae2130a7739a7946924f3e450093c547e Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Wed, 15 Oct 2014 15:15:27 +0400 Subject: [PATCH 2/2] javadocs added for HttpAsyncService.handleAlreadySubmittedResponse(...) --- .../http/nio/protocol/HttpAsyncService.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java b/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java index af781e9877..c2a7ee6364 100644 --- a/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java +++ b/httpcore-nio/src/main/java/org/apache/http/nio/protocol/HttpAsyncService.java @@ -641,11 +641,28 @@ protected HttpAsyncResponseProducer handleException( new NStringEntity(message, ContentType.DEFAULT_TEXT), false); } + /** + * This method can be used to handle callback set up happened after + * response submission. + * + * @param cancellable Request cancellation callback. + * @param context Request context. + * + * @since 4.4 + */ protected void handleAlreadySubmittedResponse( final Cancellable cancellable, final HttpContext context) { throw new IllegalStateException("Response already submitted"); } + /** + * This method can be used to handle double response submission. + * + * @param responseProducer Response producer for second response. + * @param context Request context. + * + * @since 4.4 + */ protected void handleAlreadySubmittedResponse( final HttpAsyncResponseProducer responseProducer, final HttpContext context) {