diff -r f08c1d7a5c53 -r 420445d16008 src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java --- a/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java Thu Oct 18 23:05:01 2018 -0700 +++ b/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java Fri Oct 19 14:23:43 2018 +0100 @@ -26,21 +26,26 @@ package jdk.internal.net.http; import java.io.IOException; +import java.io.UncheckedIOException; import java.net.ConnectException; import java.net.http.HttpConnectTimeoutException; import java.util.Iterator; import java.util.LinkedList; import java.security.AccessControlContext; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.Flow; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.net.http.HttpClient; +import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodySubscriber; import java.net.http.HttpResponse.PushPromiseHandler; import java.net.http.HttpTimeoutException; import jdk.internal.net.http.common.Log; @@ -200,11 +205,60 @@ return cf; } + // return true if the response is a type where a response body is never possible + // and therefore doesn't have to include header information which indicates no + // body is present. This is distinct from responses that also do not contain + // response bodies (possibly ever) but which are required to have content length + // info in the header (eg 205). Those cases do not have to be handled specially + + private static boolean bodyNotPermitted(Response r) { + return r.statusCode == 204; + } + + private boolean bodyIsPresent(Response r) { + HttpHeaders headers = r.headers(); + if (headers.firstValue("Content-length").isPresent()) + return true; + if (headers.firstValue("Transfer-encoding").isPresent()) + return true; + return false; + } + + // Call the user's body handler to get an empty body object + + private CompletableFuture> handleNoBody(Response r, Exchange exch) { + BodySubscriber bs = responseHandler.apply(new ResponseInfoImpl(r.statusCode(), + r.headers(), r.version())); + CompletionStage cs = bs.getBody(); + bs.onSubscribe(new NullSubscription()); + bs.onComplete(); + MinimalFuture> result = new MinimalFuture<>(); + cs.whenComplete((nullBody, exception) -> { + if (exception != null) + result.completeExceptionally(exception); + else { + this.response = + new HttpResponseImpl<>(r.request(), r, this.response, nullBody, exch); + result.complete(this.response); + } + }); + return result; + } + private CompletableFuture> responseAsync0(CompletableFuture start) { return start.thenCompose( v -> responseAsyncImpl()) .thenCompose((Response r) -> { Exchange exch = getExchange(); + if (bodyNotPermitted(r)) { + if (bodyIsPresent(r)) { + IOException ioe = new IOException( + "unexpected content length header with 204 response"); + exch.cancel(ioe); + return MinimalFuture.failedFuture(ioe); + } else + return handleNoBody(r, exch); + } return exch.readBodyAsync(responseHandler) .thenApply((T body) -> { this.response = @@ -214,6 +268,16 @@ }); } + static class NullSubscription implements Flow.Subscription { + @Override + public void request(long n) { + } + + @Override + public void cancel() { + } + } + private CompletableFuture responseAsyncImpl() { CompletableFuture cf; if (attempts.incrementAndGet() > max_attempts) {