diff --git a/CHANGELOG.md b/CHANGELOG.md index fb3090a..2b413fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Unreleased +# 2.1.3 +* ensure unbuffered-stream correctly handles 204 responses with no body in the response. + # 2.1.2 * add support for multiple `Set-Cookie` headers in a response separated by newlines. * update project.clj to remove composite profiles that include maps, it is deprecated in lein 2.11.0 diff --git a/src/java/com/puppetlabs/http/client/impl/StreamingAsyncResponseConsumer.java b/src/java/com/puppetlabs/http/client/impl/StreamingAsyncResponseConsumer.java index 98e5692..0cc52be 100644 --- a/src/java/com/puppetlabs/http/client/impl/StreamingAsyncResponseConsumer.java +++ b/src/java/com/puppetlabs/http/client/impl/StreamingAsyncResponseConsumer.java @@ -1,10 +1,13 @@ package com.puppetlabs.http.client.impl; +import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.nio.IOControl; import org.apache.http.nio.client.methods.AsyncByteConsumer; import org.apache.http.protocol.HttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.PipedInputStream; @@ -18,6 +21,8 @@ public class StreamingAsyncResponseConsumer extends AsyncByteConsumer promise; private volatile Promise ioExceptionPromise = new Promise<>(); + private static final Logger LOGGER = LoggerFactory.getLogger(StreamingAsyncResponseConsumer.class); + public void setFinalResult(IOException ioException) { ioExceptionPromise.deliver(ioException); } @@ -28,10 +33,16 @@ public StreamingAsyncResponseConsumer(Deliverable promise) { @Override protected void onResponseReceived(final HttpResponse response) throws IOException { - PipedInputStream pis = new ExceptionInsertingPipedInputStream(ioExceptionPromise); - pos = new PipedOutputStream(); - pos.connect(pis); - ((BasicHttpEntity) response.getEntity()).setContent(pis); + HttpEntity entity = response.getEntity(); + if (entity != null) { + PipedInputStream pis = new ExceptionInsertingPipedInputStream(ioExceptionPromise); + pos = new PipedOutputStream(); + pos.connect(pis); + ((BasicHttpEntity) entity).setContent(pis); + } else { + // this can happen if the server sends no response, like with a 204. + LOGGER.debug("Null entity when processing response"); + } this.response = response; promise.deliver(response); } diff --git a/test/puppetlabs/http/client/async_unbuffered_test.clj b/test/puppetlabs/http/client/async_unbuffered_test.clj index d8add85..7500cec 100644 --- a/test/puppetlabs/http/client/async_unbuffered_test.clj +++ b/test/puppetlabs/http/client/async_unbuffered_test.clj @@ -324,3 +324,13 @@ (deftest java-existing-streaming-with-large-payload-with-decompression (testing "java :stream with 1M payload and decompression" (java-blocking-streaming (generate-data (* 1024 1024)) ResponseBodyType/STREAM true))) + +(deftest java-204-streaming + (testing "client handles a webserver that returns a 204 response and no body correctly" + (testwebserver/with-test-webserver-and-config + (constantly {:status 204}) port {:shutdown-timeout-seconds 1} + (with-open [client (async/create-client {:connect-timeout-milliseconds 100})] + (let [response @(common/post client (str "http://localhost:" port "/something") {:method :post + :as :unbuffered-stream})] + (is (= 204 (:status response)))))))) +