http-client-branch: 8201312: More evolution friendly BodyHandler API http-client-branch
authormichaelm
Tue, 10 Apr 2018 15:04:27 +0100
branchhttp-client-branch
changeset 56410 1b37529eaf3a
parent 56409 b0c62cfc1d12
child 56411 700f23d8f4fb
http-client-branch: 8201312: More evolution friendly BodyHandler API
src/java.net.http/share/classes/java/net/http/HttpResponse.java
src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseInfoImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
test/jdk/java/net/httpclient/CancelledResponse.java
test/jdk/java/net/httpclient/ConcurrentResponses.java
test/jdk/java/net/httpclient/CustomResponseSubscriber.java
test/jdk/java/net/httpclient/DependentActionsTest.java
test/jdk/java/net/httpclient/DependentPromiseActionsTest.java
test/jdk/java/net/httpclient/HttpInputStreamTest.java
test/jdk/java/net/httpclient/ImmutableFlowItems.java
test/jdk/java/net/httpclient/MappingResponseSubscriber.java
test/jdk/java/net/httpclient/ResponsePublisher.java
test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java
test/jdk/java/net/httpclient/ThrowingPushPromises.java
test/jdk/java/net/httpclient/ThrowingSubscribers.java
test/jdk/java/net/httpclient/examples/JavadocExamples.java
test/jdk/java/net/httpclient/http2/ServerPush.java
test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java
test/jdk/java/net/httpclient/offline/FixedResponseHttpClient.java
test/jdk/java/net/httpclient/security/Security.java
--- a/src/java.net.http/share/classes/java/net/http/HttpResponse.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/src/java.net.http/share/classes/java/net/http/HttpResponse.java	Tue Apr 10 15:04:27 2018 +0100
@@ -168,6 +168,30 @@
 
 
     /**
+     * Initial response information supplied to a {@link BodyHandler} when a
+     * response is initially received and before the body is processed.
+     */
+    public interface ResponseInfo {
+        /**
+         * Provides the response status code
+         * @return the response status code
+         */
+        public int statusCode();
+
+        /**
+         * Provides the response headers
+         * @return the response headers
+         */
+        public HttpHeaders headers();
+
+        /**
+         * Provides the response protocol version
+         * @return the response protocol version
+         */
+        public HttpClient.Version version();
+    }
+
+    /**
      * A handler for response bodies.  The class {@link BodyHandlers BodyHandlers}
      * provides implementations of many common body handlers.
      *
@@ -177,8 +201,8 @@
      * BodySubscriber}. The {@code BodySubscriber} consumes the actual response
      * body bytes and, typically, converts them into a higher-level Java type.
      *
-     * <p> A {@code BodyHandler} is a function that takes two parameters: the
-     * response status code and the response headers; and which returns a
+     * <p> A {@code BodyHandler} is a function that takes a {@link ResponseInfo}
+     * object; and which returns a
      * {@code BodySubscriber}. The {@code BodyHandler} is invoked when the
      * response status code and headers are available, but before the response
      * body bytes are received.
@@ -203,7 +227,7 @@
      * <pre>{@code   HttpRequest request = HttpRequest.newBuilder()
      *        .uri(URI.create("http://www.foo.com/"))
      *        .build();
-     *  BodyHandler<Path> bodyHandler = (status, headers) -> status == 200
+     *  BodyHandler<Path> bodyHandler = (rspInfo) -> rspInfo.statusCode() == 200
      *                      ? BodySubscribers.ofFile(Paths.get("/tmp/f"))
      *                      : BodySubscribers.replacing(Paths.get("/NULL"));
      *  client.sendAsync(request, bodyHandler)
@@ -228,11 +252,10 @@
          * BodyHandlers#discarding() discarding} or {@link
          * BodyHandlers#replacing(Object) replacing}.
          *
-         * @param statusCode the HTTP status code received
-         * @param responseHeaders the response headers received
+         * @param responseInfo the response info.
          * @return a body subscriber
          */
-        public BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
+        public BodySubscriber<T> apply(ResponseInfo responseInfo);
     }
 
     /**
@@ -298,7 +321,7 @@
         public static BodyHandler<Void>
         fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber) {
             Objects.requireNonNull(subscriber);
-            return (status, headers) -> BodySubscribers.fromSubscriber(subscriber,
+            return (responseInfo) -> BodySubscribers.fromSubscriber(subscriber,
                                                                        s -> null);
         }
 
@@ -332,7 +355,7 @@
         fromSubscriber(S subscriber, Function<? super S,? extends T> finisher) {
             Objects.requireNonNull(subscriber);
             Objects.requireNonNull(finisher);
-            return (status, headers) -> BodySubscribers.fromSubscriber(subscriber,
+            return (responseInfo) -> BodySubscribers.fromSubscriber(subscriber,
                                                                       finisher);
         }
 
@@ -374,10 +397,10 @@
         public static BodyHandler<Void>
         fromLineSubscriber(Subscriber<? super String> subscriber) {
             Objects.requireNonNull(subscriber);
-            return (status, headers) ->
+            return (responseInfo) ->
                         BodySubscribers.fromLineSubscriber(subscriber,
                                                            s -> null,
-                                                           charsetFrom(headers),
+                                                           charsetFrom(responseInfo.headers()),
                                                            null);
         }
 
@@ -431,10 +454,10 @@
             // implicit null check
             if (lineSeparator != null && lineSeparator.isEmpty())
                 throw new IllegalArgumentException("empty line separator");
-            return (status, headers) ->
+            return (responseInfo) ->
                         BodySubscribers.fromLineSubscriber(subscriber,
                                                            finisher,
-                                                           charsetFrom(headers),
+                                                           charsetFrom(responseInfo.headers()),
                                                            lineSeparator);
         }
 
@@ -444,7 +467,7 @@
          * @return a response body handler
          */
         public static BodyHandler<Void> discarding() {
-            return (status, headers) -> BodySubscribers.discarding();
+            return (responseInfo) -> BodySubscribers.discarding();
         }
 
         /**
@@ -456,7 +479,7 @@
          * @return a response body handler
          */
         public static <U> BodyHandler<U> replacing(U value) {
-            return (status, headers) -> BodySubscribers.replacing(value);
+            return (responseInfo) -> BodySubscribers.replacing(value);
         }
 
         /**
@@ -470,7 +493,7 @@
          */
         public static BodyHandler<String> ofString(Charset charset) {
             Objects.requireNonNull(charset);
-            return (status, headers) -> BodySubscribers.ofString(charset);
+            return (responseInfo) -> BodySubscribers.ofString(charset);
         }
 
         /**
@@ -588,7 +611,7 @@
          * @return a response body handler
          */
         public static BodyHandler<InputStream> ofInputStream() {
-            return (status, headers) -> BodySubscribers.ofInputStream();
+            return (responseInfo) -> BodySubscribers.ofInputStream();
         }
 
         /**
@@ -605,8 +628,8 @@
          * @return a response body handler
          */
         public static BodyHandler<Stream<String>> ofLines() {
-            return (status, headers) ->
-                    BodySubscribers.ofLines(charsetFrom(headers));
+            return (responseInfo) ->
+                    BodySubscribers.ofLines(charsetFrom(responseInfo.headers()));
         }
 
         /**
@@ -629,7 +652,7 @@
         public static BodyHandler<Void>
         ofByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
             Objects.requireNonNull(consumer);
-            return (status, headers) -> BodySubscribers.ofByteArrayConsumer(consumer);
+            return (responseInfo) -> BodySubscribers.ofByteArrayConsumer(consumer);
         }
 
         /**
@@ -643,7 +666,7 @@
          * @return a response body handler
          */
         public static BodyHandler<byte[]> ofByteArray() {
-            return (status, headers) -> BodySubscribers.ofByteArray();
+            return (responseInfo) -> BodySubscribers.ofByteArray();
         }
 
         /**
@@ -661,7 +684,7 @@
          * @return a response body handler
          */
         public static BodyHandler<String> ofString() {
-            return (status, headers) -> BodySubscribers.ofString(charsetFrom(headers));
+            return (responseInfo) -> BodySubscribers.ofString(charsetFrom(responseInfo.headers()));
         }
 
         /**
@@ -683,7 +706,7 @@
          * @return a response body handler
          */
         public static BodyHandler<Publisher<List<ByteBuffer>>> ofPublisher() {
-            return (status, headers) -> BodySubscribers.ofPublisher();
+            return (responseInfo) -> BodySubscribers.ofPublisher();
         }
 
         /**
@@ -707,8 +730,8 @@
              Objects.requireNonNull(downstreamHandler);
              if (bufferSize <= 0)
                  throw new IllegalArgumentException("must be greater than 0");
-             return (status, headers) -> BodySubscribers
-                     .buffering(downstreamHandler.apply(status, headers),
+             return (responseInfo) -> BodySubscribers
+                     .buffering(downstreamHandler.apply(responseInfo),
                                 bufferSize);
          }
     }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java	Tue Apr 10 15:04:27 2018 +0100
@@ -408,7 +408,7 @@
         return cf;
     }
 
-    HttpResponse.BodySubscriber<T> ignoreBody(int status, HttpHeaders hdrs) {
+    HttpResponse.BodySubscriber<T> ignoreBody(HttpResponse.ResponseInfo hdrs) {
         return HttpResponse.BodySubscribers.replacing(null);
     }
 
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Tue Apr 10 15:04:27 2018 +0100
@@ -341,8 +341,9 @@
                                        boolean returnConnectionToPool,
                                        Executor executor)
     {
-        BodySubscriber<T> bs = handler.apply(response.responseCode(),
-                                             response.responseHeaders());
+        BodySubscriber<T> bs = handler.apply(new ResponseInfoImpl(response.responseCode(),
+                                                                  response.responseHeaders(),
+                                                                  HTTP_1_1));
         CompletableFuture<T> bodyCF = response.readBody(bs,
                                                         returnConnectionToPool,
                                                         executor);
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java	Tue Apr 10 15:04:27 2018 +0100
@@ -42,6 +42,7 @@
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.ResponseInfo;
 import java.net.http.HttpResponse.BodySubscriber;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -93,7 +94,7 @@
         }
 
         @Override
-        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
+        public BodySubscriber<Path> apply(ResponseInfo responseInfo) {
             return new PathSubscriber(file, openOptions, filePermission);
         }
     }
@@ -203,29 +204,26 @@
 
         static final List<String> PROHIBITED = List.of(".", "..", "", "~" , "|");
 
-        static final UncheckedIOException unchecked(int code,
-                                                    HttpHeaders headers,
+        static final UncheckedIOException unchecked(ResponseInfo rinfo,
                                                     String msg) {
-            String s = String.format("%s in response [%d, %s]", msg, code, headers);
+            String s = String.format("%s in response [%d, %s]", msg, rinfo.statusCode(), rinfo.headers());
             return new UncheckedIOException(new IOException(s));
         }
 
         @Override
-        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
-            String dispoHeader = headers.firstValue("Content-Disposition")
-                    .orElseThrow(() -> unchecked(statusCode, headers,
-                            "No Content-Disposition header"));
+        public BodySubscriber<Path> apply(ResponseInfo responseInfo) {
+            String dispoHeader = responseInfo.headers().firstValue("Content-Disposition")
+                    .orElseThrow(() -> unchecked(responseInfo, "No Content-Disposition header"));
 
             if (!dispoHeader.regionMatches(true, // ignoreCase
                                            0, DISPOSITION_TYPE,
                                            0, DISPOSITION_TYPE.length())) {
-                throw unchecked(statusCode, headers, "Unknown Content-Disposition type");
+                throw unchecked(responseInfo, "Unknown Content-Disposition type");
             }
 
             Matcher matcher = FILENAME.matcher(dispoHeader);
             if (!matcher.find()) {
-                throw unchecked(statusCode, headers,
-                          "Bad Content-Disposition filename parameter");
+                throw unchecked(responseInfo, "Bad Content-Disposition filename parameter");
             }
             int n = matcher.end();
 
@@ -251,19 +249,19 @@
 
             if (filenameParam.startsWith("\"")) {  // quoted-string
                 if (!filenameParam.endsWith("\"") || filenameParam.length() == 1) {
-                    throw unchecked(statusCode, headers,
+                    throw unchecked(responseInfo,
                             "Badly quoted Content-Disposition filename parameter");
                 }
                 filenameParam = filenameParam.substring(1, filenameParam.length() -1 );
             } else {  // token,
                 if (filenameParam.contains(" ")) {  // space disallowed
-                    throw unchecked(statusCode, headers,
+                    throw unchecked(responseInfo,
                             "unquoted space in Content-Disposition filename parameter");
                 }
             }
 
             if (PROHIBITED.contains(filenameParam)) {
-                throw unchecked(statusCode, headers,
+                throw unchecked(responseInfo,
                         "Prohibited Content-Disposition filename parameter:"
                                 + filenameParam);
             }
@@ -271,7 +269,7 @@
             Path file = Paths.get(directory.toString(), filenameParam);
 
             if (!file.startsWith(directory)) {
-                throw unchecked(statusCode, headers,
+                throw unchecked(responseInfo,
                         "Resulting file, " + file.toString() + ", outside of given directory");
             }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseInfoImpl.java	Tue Apr 10 15:04:27 2018 +0100
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http;
+
+import java.net.http.HttpResponse.ResponseInfo;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpClient;
+
+class ResponseInfoImpl implements ResponseInfo {
+    private final int statusCode;
+    private final HttpHeaders headers;
+    private final HttpClient.Version version;
+
+    ResponseInfoImpl(Response response) {
+        this.statusCode = response.statusCode();
+        this.headers = response.headers();
+        this.version = response.version();
+    }
+
+    ResponseInfoImpl(int statusCode, HttpHeaders headers, HttpClient.Version version) {
+        this.statusCode = statusCode;
+        this.headers = headers;
+        this.version = version;
+    }
+
+    /**
+     * Provides the response status code
+     * @return the response status code
+     */
+    public int statusCode() {
+        return statusCode;
+    }
+
+    /**
+     * Provides the response headers
+     * @return the response headers
+     */
+    public HttpHeaders headers() {
+        return headers;
+    }
+
+    /**
+     * provides the response protocol version
+     * @return the response protocol version
+     */
+    public HttpClient.Version version() {
+        return version;
+    }
+}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Tue Apr 10 15:04:27 2018 +0100
@@ -257,7 +257,7 @@
     {
         try {
             Log.logTrace("Reading body on stream {0}", streamid);
-            BodySubscriber<T> bodySubscriber = handler.apply(responseCode, response.headers);
+            BodySubscriber<T> bodySubscriber = handler.apply(new ResponseInfoImpl(response));
             CompletableFuture<T> cf = receiveData(bodySubscriber, executor);
 
             PushGroup<?> pg = exchange.getPushGroup();
--- a/test/jdk/java/net/httpclient/CancelledResponse.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/CancelledResponse.java	Tue Apr 10 15:04:27 2018 +0100
@@ -288,7 +288,7 @@
             this.cancelled = cancelled;
         }
         @Override
-        public BodySubscriber<String> apply(int statusCode, HttpHeaders responseHeaders) {
+        public BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
             assert !cancelled.get();
             return new CancellingSubscriber(expected, cancelled);
         }
--- a/test/jdk/java/net/httpclient/ConcurrentResponses.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/ConcurrentResponses.java	Tue Apr 10 15:04:27 2018 +0100
@@ -201,7 +201,7 @@
      * and capacity, if the client mistakenly passes it any that is should not.
      */
     static class CustomSubscriber implements BodySubscriber<String> {
-        static final BodyHandler<String> handler = (r,h) -> new CustomSubscriber();
+        static final BodyHandler<String> handler = (r) -> new CustomSubscriber();
         private final BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
 
         @Override
--- a/test/jdk/java/net/httpclient/CustomResponseSubscriber.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/CustomResponseSubscriber.java	Tue Apr 10 15:04:27 2018 +0100
@@ -135,8 +135,8 @@
 
     static class CRSBodyHandler implements BodyHandler<String> {
         @Override
-        public BodySubscriber<String> apply(int statusCode, HttpHeaders responseHeaders) {
-            assertEquals(statusCode, 200);
+        public BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
             return new CRSBodySubscriber();
         }
     }
--- a/test/jdk/java/net/httpclient/DependentActionsTest.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/DependentActionsTest.java	Tue Apr 10 15:04:27 2018 +0100
@@ -525,9 +525,9 @@
             this.bodyHandler = bodyHandler;
         }
         @Override
-        public BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders) {
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
             stalling.accept(Where.BODY_HANDLER);
-            BodySubscriber<T> subscriber = bodyHandler.apply(statusCode, responseHeaders);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
             return new StallingBodySubscriber(stalling, subscriber);
         }
     }
--- a/test/jdk/java/net/httpclient/DependentPromiseActionsTest.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/DependentPromiseActionsTest.java	Tue Apr 10 15:04:27 2018 +0100
@@ -587,9 +587,9 @@
             this.bodyHandler = bodyHandler;
         }
         @Override
-        public BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders) {
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
             stalling.accept(Where.BODY_HANDLER);
-            BodySubscriber<T> subscriber = bodyHandler.apply(statusCode, responseHeaders);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
             return new StallingBodySubscriber(stalling, subscriber);
         }
     }
--- a/test/jdk/java/net/httpclient/HttpInputStreamTest.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/HttpInputStreamTest.java	Tue Apr 10 15:04:27 2018 +0100
@@ -78,7 +78,7 @@
 
         @Override
         public HttpResponse.BodySubscriber<InputStream>
-                apply(int i, HttpHeaders hh) {
+                apply(HttpResponse.ResponseInfo rinfo) {
             return new HttpResponseInputStream(maxBuffers);
         }
 
--- a/test/jdk/java/net/httpclient/ImmutableFlowItems.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/ImmutableFlowItems.java	Tue Apr 10 15:04:27 2018 +0100
@@ -120,8 +120,8 @@
 
     static class CRSBodyHandler implements BodyHandler<String> {
         @Override
-        public BodySubscriber<String> apply(int statusCode, HttpHeaders responseHeaders) {
-            assertEquals(statusCode, 200);
+        public BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
             return new CRSBodySubscriber();
         }
     }
--- a/test/jdk/java/net/httpclient/MappingResponseSubscriber.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/MappingResponseSubscriber.java	Tue Apr 10 15:04:27 2018 +0100
@@ -140,8 +140,8 @@
 
     static class CRSBodyHandler implements BodyHandler<byte[]> {
         @Override
-        public BodySubscriber<byte[]> apply(int statusCode, HttpHeaders responseHeaders) {
-            assertEquals(statusCode, 200);
+        public BodySubscriber<byte[]> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
             return BodySubscribers.mapping(
                 new CRSBodySubscriber(), (s) -> s.getBytes(UTF_8)
             );
@@ -323,26 +323,26 @@
         HttpClient client = null;
         HttpRequest req = null;
 
-        HttpResponse<Integer> r1 = client.send(req, (c,h) ->
+        HttpResponse<Integer> r1 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> 1));
-        HttpResponse<Number>  r2 = client.send(req, (c,h) ->
+        HttpResponse<Number>  r2 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> 1));
-        HttpResponse<String>  r3 = client.send(req, (c,h) ->
+        HttpResponse<String>  r3 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> "s"));
-        HttpResponse<CharSequence> r4 = client.send(req, (c,h) ->
+        HttpResponse<CharSequence> r4 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> "s"));
 
-        HttpResponse<Integer> x1 = client.send(req, (c,h) ->
+        HttpResponse<Integer> x1 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f1));
-        HttpResponse<Number>  x2 = client.send(req, (c,h) ->
+        HttpResponse<Number>  x2 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f1));
-        HttpResponse<Number>  x3 = client.send(req, (c,h) ->
+        HttpResponse<Number>  x3 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f2));
-        HttpResponse<Integer> x4 = client.send(req, (c,h) ->
+        HttpResponse<Integer> x4 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f3));
-        HttpResponse<Number>  x5 = client.send(req, (c,h) ->
+        HttpResponse<Number>  x5 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f3));
-        HttpResponse<Number>  x7 = client.send(req, (c,h) ->
+        HttpResponse<Number>  x7 = client.send(req, (ri) ->
                 BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f4));
     }
 }
--- a/test/jdk/java/net/httpclient/ResponsePublisher.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/ResponsePublisher.java	Tue Apr 10 15:04:27 2018 +0100
@@ -318,8 +318,8 @@
     // A BodyHandler that returns PublishingBodySubscriber instances
     static class PublishingBodyHandler implements BodyHandler<Publisher<List<ByteBuffer>>> {
         @Override
-        public BodySubscriber<Publisher<List<ByteBuffer>>> apply(int statusCode, HttpHeaders responseHeaders) {
-            assertEquals(statusCode, 200);
+        public BodySubscriber<Publisher<List<ByteBuffer>>> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
             return new PublishingBodySubscriber();
         }
     }
--- a/test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java	Tue Apr 10 15:04:27 2018 +0100
@@ -33,6 +33,7 @@
 import java.net.http.HttpHeaders;
 import java.net.http.HttpRequest.BodyPublishers;
 import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.ResponseInfo;
 import java.net.http.HttpResponse.BodyHandlers;
 import java.net.http.HttpResponse.BodySubscriber;
 import java.net.http.HttpResponse.BodySubscribers;
@@ -159,7 +160,7 @@
     }
 
     static class NoOpHandler implements BodyHandler<Void> {
-        @Override public BodySubscriber<Void> apply(int code, HttpHeaders hrds) { return null; }
+        @Override public BodySubscriber<Void> apply(ResponseInfo rinfo) { return null; }
     }
 
     static class NoOpSubscriber implements BodySubscriber<Void> {
--- a/test/jdk/java/net/httpclient/ThrowingPushPromises.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/ThrowingPushPromises.java	Tue Apr 10 15:04:27 2018 +0100
@@ -569,9 +569,9 @@
             this.bodyHandler = bodyHandler;
         }
         @Override
-        public BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders) {
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
             throwing.accept(Where.BODY_HANDLER);
-            BodySubscriber<T> subscriber = bodyHandler.apply(statusCode, responseHeaders);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
             return new ThrowingBodySubscriber(throwing, subscriber);
         }
     }
--- a/test/jdk/java/net/httpclient/ThrowingSubscribers.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/ThrowingSubscribers.java	Tue Apr 10 15:04:27 2018 +0100
@@ -567,9 +567,9 @@
             this.bodyHandler = bodyHandler;
         }
         @Override
-        public BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders) {
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
             throwing.accept(Where.BODY_HANDLER);
-            BodySubscriber<T> subscriber = bodyHandler.apply(statusCode, responseHeaders);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
             return new ThrowingBodySubscriber(throwing, subscriber);
         }
     }
--- a/test/jdk/java/net/httpclient/examples/JavadocExamples.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/examples/JavadocExamples.java	Tue Apr 10 15:04:27 2018 +0100
@@ -131,7 +131,7 @@
         HttpRequest request1 = HttpRequest.newBuilder()
                 .uri(URI.create("http://www.foo.com/"))
                 .build();
-        BodyHandler<Path> bodyHandler = (status, headers) -> status == 200
+        BodyHandler<Path> bodyHandler = (info) -> info.statusCode() == 200
                 ? BodySubscribers.ofFile(Paths.get("/tmp/f"))
                 : BodySubscribers.replacing(Paths.get("/NULL"));
         client.sendAsync(request, bodyHandler)
--- a/test/jdk/java/net/httpclient/http2/ServerPush.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/ServerPush.java	Tue Apr 10 15:04:27 2018 +0100
@@ -244,7 +244,7 @@
 
         PushPromiseHandler<Void> pushPromiseHandler = (initial, pushRequest, acceptor) -> {
             CompletableFuture<HttpResponse<Void>> cf = acceptor.apply(
-                    (statusCode, headers) -> {
+                    (info) -> {
                         ByteArrayConsumer bc = new ByteArrayConsumer();
                         byteArrayConsumerMap.put(pushRequest, bc);
                         return BodySubscribers.ofByteArrayConsumer(bc); } );
--- a/test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java	Tue Apr 10 15:04:27 2018 +0100
@@ -139,8 +139,7 @@
         }
 
         @Override
-        public HttpResponse.BodySubscriber<BodyAndType<?>> apply(int statusCode,
-                                                                 HttpHeaders responseHeaders) {
+        public HttpResponse.BodySubscriber<BodyAndType<?>> apply(HttpResponse.ResponseInfo info) {
             int whichType = count++ % 3;  // real world may base this on the request metadata
             switch (whichType) {
                 case 0: // String
--- a/test/jdk/java/net/httpclient/offline/FixedResponseHttpClient.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/offline/FixedResponseHttpClient.java	Tue Apr 10 15:04:27 2018 +0100
@@ -51,6 +51,8 @@
     private final ByteBuffer responseBodyBytes;
     private final int responseStatusCode;
     private final HttpHeaders responseHeaders;
+    private final HttpClient.Version responseVersion;
+    private final HttpResponse.ResponseInfo responseInfo;
 
     private FixedResponseHttpClient(HttpClient.Builder builder,
                                     int responseStatusCode,
@@ -60,6 +62,8 @@
         this.responseStatusCode = responseStatusCode;
         this.responseHeaders = responseHeaders;
         this.responseBodyBytes = responseBodyBytes;
+        this.responseVersion = HttpClient.Version.HTTP_1_1; // should be added to constructor
+        this.responseInfo = new FixedResponseInfo();
     }
 
     /**
@@ -136,6 +140,42 @@
         return sendAsync(request, responseBodyHandler).join();  // unwrap exceptions if needed
     }
 
+    class FixedResponseInfo implements HttpResponse.ResponseInfo {
+        private final int statusCode;
+        private final HttpHeaders headers;
+        private final HttpClient.Version version;
+
+        FixedResponseInfo() {
+            this.statusCode = responseStatusCode;
+            this.headers = responseHeaders;
+            this.version = HttpClient.Version.HTTP_1_1;
+        }
+
+        /**
+         * Provides the response status code
+         * @return the response status code
+         */
+        public int statusCode() {
+            return statusCode;
+        }
+
+        /**
+         * Provides the response headers
+         * @return the response headers
+         */
+        public HttpHeaders headers() {
+            return headers;
+        }
+
+        /**
+         * provides the response protocol version
+         * @return the response protocol version
+         */
+        public HttpClient.Version version() {
+            return version;
+        }
+    }
+
     @Override
     public <T> CompletableFuture<HttpResponse<T>>
     sendAsync(HttpRequest request,
@@ -155,7 +195,7 @@
         }
 
         BodySubscriber<T> bodySubscriber =
-                responseBodyHandler.apply(responseStatusCode, responseHeaders);
+                responseBodyHandler.apply(responseInfo);
         SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
         publisher.subscribe(bodySubscriber);
         publisher.submit(responseBody);
--- a/test/jdk/java/net/httpclient/security/Security.java	Tue Apr 10 13:39:38 2018 +0100
+++ b/test/jdk/java/net/httpclient/security/Security.java	Tue Apr 10 15:04:27 2018 +0100
@@ -330,8 +330,8 @@
                 CompletableFuture<HttpResponse<String>> cf =
                     client.sendAsync(request, new HttpResponse.BodyHandler<String>() {
                         @Override
-                        public HttpResponse.BodySubscriber<String> apply(int status, HttpHeaders responseHeaders)  {
-                            final HttpResponse.BodySubscriber<String> stproc = sth.apply(status, responseHeaders);
+                        public HttpResponse.BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
+                            final HttpResponse.BodySubscriber<String> stproc = sth.apply(rinfo);
                             return new HttpResponse.BodySubscriber<String>() {
                                 @Override
                                 public CompletionStage<String> getBody() {