8087112: HTTP API and HTTP/1.1 implementation
authormichaelm
Thu, 25 Feb 2016 23:14:22 +0000
changeset 36131 379db4b2f95d
parent 36130 5423259ef9ea
child 36132 c99a60377145
8087112: HTTP API and HTTP/1.1 implementation Reviewed-by: alanb, chegar, coffeys, psandoz, rriggs
jdk/make/src/classes/build/tools/module/boot.modules
jdk/src/java.base/share/classes/java/net/Authenticator.java
jdk/src/java.base/share/classes/java/net/ProxySelector.java
jdk/src/java.base/share/classes/java/net/package-info.java
jdk/src/java.httpclient/share/classes/java/net/http/AsyncEvent.java
jdk/src/java.httpclient/share/classes/java/net/http/AuthenticationFilter.java
jdk/src/java.httpclient/share/classes/java/net/http/BufferHandler.java
jdk/src/java.httpclient/share/classes/java/net/http/ConnectionPool.java
jdk/src/java.httpclient/share/classes/java/net/http/CookieFilter.java
jdk/src/java.httpclient/share/classes/java/net/http/Exchange.java
jdk/src/java.httpclient/share/classes/java/net/http/ExchangeImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/ExecutorWrapper.java
jdk/src/java.httpclient/share/classes/java/net/http/FilterFactory.java
jdk/src/java.httpclient/share/classes/java/net/http/HeaderFilter.java
jdk/src/java.httpclient/share/classes/java/net/http/HeaderParser.java
jdk/src/java.httpclient/share/classes/java/net/http/Http1Exchange.java
jdk/src/java.httpclient/share/classes/java/net/http/Http1Request.java
jdk/src/java.httpclient/share/classes/java/net/http/Http1Response.java
jdk/src/java.httpclient/share/classes/java/net/http/Http2ClientImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/Http2Connection.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpClient.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpClientBuilderImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpClientImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpConnection.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpHeaders.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpHeaders1.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpHeadersImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpRedirectImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpRequest.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpRequestBuilderImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpRequestImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpResponse.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpResponseImpl.java
jdk/src/java.httpclient/share/classes/java/net/http/HttpTimeoutException.java
jdk/src/java.httpclient/share/classes/java/net/http/Log.java
jdk/src/java.httpclient/share/classes/java/net/http/MultiExchange.java
jdk/src/java.httpclient/share/classes/java/net/http/Pair.java
jdk/src/java.httpclient/share/classes/java/net/http/PlainHttpConnection.java
jdk/src/java.httpclient/share/classes/java/net/http/PlainProxyConnection.java
jdk/src/java.httpclient/share/classes/java/net/http/PlainTunnelingConnection.java
jdk/src/java.httpclient/share/classes/java/net/http/RawChannel.java
jdk/src/java.httpclient/share/classes/java/net/http/RedirectFilter.java
jdk/src/java.httpclient/share/classes/java/net/http/ResponseContent.java
jdk/src/java.httpclient/share/classes/java/net/http/ResponseHeaders.java
jdk/src/java.httpclient/share/classes/java/net/http/SSLConnection.java
jdk/src/java.httpclient/share/classes/java/net/http/SSLDelegate.java
jdk/src/java.httpclient/share/classes/java/net/http/SSLTunnelConnection.java
jdk/src/java.httpclient/share/classes/java/net/http/Stream.java
jdk/src/java.httpclient/share/classes/java/net/http/TimeoutEvent.java
jdk/src/java.httpclient/share/classes/java/net/http/Utils.java
jdk/src/java.httpclient/share/classes/java/net/http/package-info.java
jdk/test/com/sun/net/httpserver/FileServerHandler.java
jdk/test/java/net/httpclient/APIErrors.java
jdk/test/java/net/httpclient/BasicAuthTest.java
jdk/test/java/net/httpclient/HeadersTest.java
jdk/test/java/net/httpclient/HttpUtils.java
jdk/test/java/net/httpclient/ImmutableHeaders.java
jdk/test/java/net/httpclient/LightWeightHttpServer.java
jdk/test/java/net/httpclient/ManyRequests.java
jdk/test/java/net/httpclient/ProxyServer.java
jdk/test/java/net/httpclient/QuickResponses.java
jdk/test/java/net/httpclient/RequestBodyTest.java
jdk/test/java/net/httpclient/Server.java
jdk/test/java/net/httpclient/SmokeTest.java
jdk/test/java/net/httpclient/SplitResponse.java
jdk/test/java/net/httpclient/TimeoutTest.java
jdk/test/java/net/httpclient/docs/files/foo.txt
jdk/test/java/net/httpclient/docs/files/notsobigfile.txt
jdk/test/java/net/httpclient/docs/files/smallfile.txt
jdk/test/java/net/httpclient/security/0.policy
jdk/test/java/net/httpclient/security/1.policy
jdk/test/java/net/httpclient/security/10.policy
jdk/test/java/net/httpclient/security/11.policy
jdk/test/java/net/httpclient/security/12.policy
jdk/test/java/net/httpclient/security/15.policy
jdk/test/java/net/httpclient/security/2.policy
jdk/test/java/net/httpclient/security/3.policy
jdk/test/java/net/httpclient/security/4.policy
jdk/test/java/net/httpclient/security/5.policy
jdk/test/java/net/httpclient/security/6.policy
jdk/test/java/net/httpclient/security/7.policy
jdk/test/java/net/httpclient/security/8.policy
jdk/test/java/net/httpclient/security/9.policy
jdk/test/java/net/httpclient/security/Security.java
--- a/jdk/make/src/classes/build/tools/module/boot.modules	Thu Feb 25 11:27:30 2016 -0800
+++ b/jdk/make/src/classes/build/tools/module/boot.modules	Thu Feb 25 23:14:22 2016 +0000
@@ -2,6 +2,7 @@
 java.compiler
 java.datatransfer
 java.desktop
+java.httpclient
 java.instrument
 java.logging
 java.management
--- a/jdk/src/java.base/share/classes/java/net/Authenticator.java	Thu Feb 25 11:27:30 2016 -0800
+++ b/jdk/src/java.base/share/classes/java/net/Authenticator.java	Thu Feb 25 23:14:22 2016 +0000
@@ -320,6 +320,48 @@
     }
 
     /**
+     * Ask this authenticator for a password.
+     *
+     * @param host The hostname of the site requesting authentication.
+     * @param addr The InetAddress of the site requesting authorization,
+     *             or null if not known.
+     * @param port the port for the requested connection
+     * @param protocol The protocol that's requesting the connection
+     *          ({@link java.net.Authenticator#getRequestingProtocol()})
+     * @param prompt A prompt string for the user
+     * @param scheme The authentication scheme
+     * @param url The requesting URL that caused the authentication
+     * @param reqType The type (server or proxy) of the entity requesting
+     *              authentication.
+     *
+     * @return The username/password, or null if one can't be gotten
+     *
+     * @since 9
+     */
+    public PasswordAuthentication
+    requestPasswordAuthenticationInstance(String host,
+                                          InetAddress addr,
+                                          int port,
+                                          String protocol,
+                                          String prompt,
+                                          String scheme,
+                                          URL url,
+                                          RequestorType reqType) {
+        synchronized (this) {
+            this.reset();
+            this.requestingHost = host;
+            this.requestingSite = addr;
+            this.requestingPort = port;
+            this.requestingProtocol = protocol;
+            this.requestingPrompt = prompt;
+            this.requestingScheme = scheme;
+            this.requestingURL = url;
+            this.requestingAuthType = reqType;
+            return this.getPasswordAuthentication();
+        }
+    }
+
+    /**
      * Gets the {@code hostname} of the
      * site or proxy requesting authentication, or {@code null}
      * if not available.
--- a/jdk/src/java.base/share/classes/java/net/ProxySelector.java	Thu Feb 25 11:27:30 2016 -0800
+++ b/jdk/src/java.base/share/classes/java/net/ProxySelector.java	Thu Feb 25 23:14:22 2016 +0000
@@ -162,4 +162,49 @@
      * @throws IllegalArgumentException if either argument is null
      */
     public abstract void connectFailed(URI uri, SocketAddress sa, IOException ioe);
+
+    /**
+     * Returns a ProxySelector which uses the given proxy address for all HTTP
+     * and HTTPS requests. If proxy is {@code null} then proxying is disabled.
+     *
+     * @param proxyAddress
+     *        The address of the proxy
+     *
+     * @return a ProxySelector
+     *
+     * @since 9
+     */
+    public static ProxySelector of(InetSocketAddress proxyAddress) {
+        return new StaticProxySelector(proxyAddress);
+    }
+
+    static class StaticProxySelector extends ProxySelector {
+        private static final List<Proxy> NO_PROXY_LIST = List.of(Proxy.NO_PROXY);
+        final List<Proxy> list;
+
+        StaticProxySelector(InetSocketAddress address){
+            Proxy p;
+            if (address == null) {
+                p = Proxy.NO_PROXY;
+            } else {
+                p = new Proxy(Proxy.Type.HTTP, address);
+            }
+            list = List.of(p);
+        }
+
+        @Override
+        public void connectFailed(URI uri, SocketAddress sa, IOException e) {
+            /* ignore */
+        }
+
+        @Override
+        public synchronized List<Proxy> select(URI uri) {
+            String scheme = uri.getScheme().toLowerCase();
+            if (scheme.equals("http") || scheme.equals("https")) {
+                return list;
+            } else {
+                return NO_PROXY_LIST;
+            }
+        }
+    }
 }
--- a/jdk/src/java.base/share/classes/java/net/package-info.java	Thu Feb 25 11:27:30 2016 -0800
+++ b/jdk/src/java.base/share/classes/java/net/package-info.java	Thu Feb 25 23:14:22 2016 +0000
@@ -121,7 +121,8 @@
  *            underlying protocol handlers like http or https.</li>
  *       <li>{@link java.net.HttpURLConnection} is a subclass of URLConnection
  *            and provides some additional functionalities specific to the
- *            HTTP protocol.</li>
+ *            HTTP protocol. This API has been superceded by the newer
+              HTTP client API described in the previous section.</li>
  * </ul>
  * <p>The recommended usage is to use {@link java.net.URI} to identify
  *    resources, then convert it into a {@link java.net.URL} when it is time to
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/AsyncEvent.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.nio.channels.SelectableChannel;
+
+/**
+ * Event handling interface from HttpClientImpl's selector.
+ *
+ * <p> If blockingChannel is true, then the channel will be put in blocking
+ * mode prior to handle() being called. If false, then it remains non-blocking.
+ */
+abstract class AsyncEvent {
+
+    /**
+     * Implement this if channel should be made blocking before calling handle()
+     */
+    public interface Blocking { }
+
+    /**
+     * Implement this if channel should remain non-blocking before calling handle()
+     */
+    public interface NonBlocking { }
+
+    /** Returns the channel */
+    public abstract SelectableChannel channel();
+
+    /** Returns the selector interest op flags OR'd */
+    public abstract int interestOps();
+
+    /** Called when event occurs */
+    public abstract void handle();
+
+    /** Called when selector is shutting down. Abort all exchanges. */
+    public abstract void abort();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/AuthenticationFilter.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import static java.net.Authenticator.RequestorType.PROXY;
+import static java.net.Authenticator.RequestorType.SERVER;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.InetSocketAddress;
+import java.net.URISyntaxException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.LinkedList;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+/**
+ * Implementation of Http Basic authentication.
+ */
+class AuthenticationFilter implements HeaderFilter {
+
+    static private final Base64.Encoder encoder = Base64.getEncoder();
+
+    static final int DEFAULT_RETRY_LIMIT = 3;
+
+    static final int retry_limit = Utils.getIntegerNetProperty(
+            "sun.net.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT);
+
+    static final int UNAUTHORIZED = 401;
+    static final int PROXY_UNAUTHORIZED = 407;
+
+    private PasswordAuthentication getCredentials(String header,
+                                                  boolean proxy,
+                                                  HttpRequestImpl req)
+        throws IOException
+    {
+        HttpClientImpl client = req.client();
+        java.net.Authenticator auth =
+                client.authenticator()
+                      .orElseThrow(() -> new IOException("No authenticator set"));
+        URI uri = req.uri();
+        HeaderParser parser = new HeaderParser(header);
+        String authscheme = parser.findKey(0);
+
+        String realm = parser.findValue("realm");
+        java.net.Authenticator.RequestorType rtype = proxy ? PROXY : SERVER;
+
+        // needs to be instance method in Authenticator
+        return auth.requestPasswordAuthenticationInstance(uri.getHost(),
+                                                          null,
+                                                          uri.getPort(),
+                                                          uri.getScheme(),
+                                                          realm,
+                                                          authscheme,
+                                                          uri.toURL(),
+                                                          rtype
+        );
+    }
+
+    private URI getProxyURI(HttpRequestImpl r) {
+        InetSocketAddress proxy = r.proxy();
+        if (proxy == null) {
+            return null;
+        }
+
+        // our own private scheme for proxy URLs
+        // eg. proxy.http://host:port/
+        String scheme = "proxy." + r.uri().getScheme();
+        try {
+            return new URI(scheme,
+                           null,
+                           proxy.getHostString(),
+                           proxy.getPort(),
+                           null,
+                           null,
+                           null);
+        } catch (URISyntaxException e) {
+            throw new InternalError(e);
+        }
+    }
+
+    @Override
+    public void request(HttpRequestImpl r) throws IOException {
+        // use preemptive authentication if an entry exists.
+        Cache cache = getCache(r);
+
+        // Proxy
+        if (r.exchange.proxyauth == null) {
+            URI proxyURI = getProxyURI(r);
+            if (proxyURI != null) {
+                CacheEntry ca = cache.get(proxyURI, true);
+                if (ca != null) {
+                    r.exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca);
+                    addBasicCredentials(r, true, ca.value);
+                }
+            }
+        }
+
+        // Server
+        if (r.exchange.serverauth == null) {
+            CacheEntry ca = cache.get(r.uri(), false);
+            if (ca != null) {
+                r.exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca);
+                addBasicCredentials(r, false, ca.value);
+            }
+        }
+    }
+
+    // TODO: refactor into per auth scheme class
+    static private void addBasicCredentials(HttpRequestImpl r,
+                                            boolean proxy,
+                                            PasswordAuthentication pw) {
+        String hdrname = proxy ? "Proxy-Authorization" : "Authorization";
+        StringBuilder sb = new StringBuilder(128);
+        sb.append(pw.getUserName()).append(':').append(pw.getPassword());
+        String s = encoder.encodeToString(sb.toString().getBytes(ISO_8859_1));
+        String value = "Basic " + s;
+        r.setSystemHeader(hdrname, value);
+    }
+
+    // Information attached to a HttpRequestImpl relating to authentication
+    static class AuthInfo {
+        final boolean fromcache;
+        final String scheme;
+        int retries;
+        PasswordAuthentication credentials; // used in request
+        CacheEntry cacheEntry; // if used
+
+        AuthInfo(boolean fromcache,
+                 String scheme,
+                 PasswordAuthentication credentials) {
+            this.fromcache = fromcache;
+            this.scheme = scheme;
+            this.credentials = credentials;
+            this.retries = 1;
+        }
+
+        AuthInfo(boolean fromcache,
+                 String scheme,
+                 PasswordAuthentication credentials,
+                 CacheEntry ca) {
+            this(fromcache, scheme, credentials);
+            assert credentials == null || (ca != null && ca.value == null);
+            cacheEntry = ca;
+        }
+    }
+
+    @Override
+    public HttpRequestImpl response(HttpResponseImpl r) throws IOException {
+        Cache cache = getCache(r.request);
+        int status = r.statusCode();
+        HttpHeaders hdrs = r.headers();
+        HttpRequestImpl req = r.request();
+
+        if (status != UNAUTHORIZED && status != PROXY_UNAUTHORIZED) {
+            // check if any authentication succeeded for first time
+            if (req.exchange.serverauth != null && !req.exchange.serverauth.fromcache) {
+                AuthInfo au = req.exchange.serverauth;
+                cache.store(au.scheme, req.uri(), false, au.credentials);
+            }
+            if (req.exchange.proxyauth != null && !req.exchange.proxyauth.fromcache) {
+                AuthInfo au = req.exchange.proxyauth;
+                cache.store(au.scheme, req.uri(), false, au.credentials);
+            }
+            return null;
+        }
+
+        boolean proxy = status == PROXY_UNAUTHORIZED;
+        String authname = proxy ? "Proxy-Authentication" : "WWW-Authenticate";
+        String authval = hdrs.firstValue(authname).orElseThrow(() -> {
+            return new IOException("Invalid auth header");
+        });
+        HeaderParser parser = new HeaderParser(authval);
+        String scheme = parser.findKey(0);
+
+        // TODO: Need to generalise from Basic only. Delegate to a provider class etc.
+
+        if (!scheme.equalsIgnoreCase("Basic")) {
+            return null;   // error gets returned to app
+        }
+
+        String realm = parser.findValue("realm");
+        AuthInfo au = proxy ? req.exchange.proxyauth : req.exchange.serverauth;
+        if (au == null) {
+            PasswordAuthentication pw = getCredentials(authval, proxy, req);
+            if (pw == null) {
+                throw new IOException("No credentials provided");
+            }
+            // No authentication in request. Get credentials from user
+            au = new AuthInfo(false, "Basic", pw);
+            if (proxy)
+                req.exchange.proxyauth = au;
+            else
+                req.exchange.serverauth = au;
+            addBasicCredentials(req, proxy, pw);
+            return req;
+        } else if (au.retries > retry_limit) {
+            throw new IOException("too many authentication attempts");
+        } else {
+            // we sent credentials, but they were rejected
+            if (au.fromcache) {
+                cache.remove(au.cacheEntry);
+            }
+            // try again
+            au.credentials = getCredentials(authval, proxy, req);
+            addBasicCredentials(req, proxy, au.credentials);
+            au.retries++;
+            return req;
+        }
+    }
+
+    static final HashMap<HttpClientImpl,Cache> caches = new HashMap<>();
+
+    static synchronized Cache getCache(HttpRequestImpl req) {
+        HttpClientImpl client = req.client();
+        Cache c = caches.get(client);
+        if (c == null) {
+            c = new Cache();
+            caches.put(client, c);
+        }
+        return c;
+    }
+
+    static class Cache {
+        final LinkedList<CacheEntry> entries = new LinkedList<>();
+
+        synchronized CacheEntry get(URI uri, boolean proxy) {
+            for (CacheEntry entry : entries) {
+                if (entry.equalsKey(uri, proxy)) {
+                    return entry;
+                }
+            }
+            return null;
+        }
+
+        synchronized void remove(String authscheme, URI domain, boolean proxy) {
+            for (CacheEntry entry : entries) {
+                if (entry.equalsKey(domain, proxy)) {
+                    entries.remove(entry);
+                }
+            }
+        }
+
+        synchronized void remove(CacheEntry entry) {
+            entries.remove(entry);
+        }
+
+        synchronized void store(String authscheme,
+                                URI domain,
+                                boolean proxy,
+                                PasswordAuthentication value) {
+            remove(authscheme, domain, proxy);
+            entries.add(new CacheEntry(authscheme, domain, proxy, value));
+        }
+    }
+
+    static class CacheEntry {
+        final String root;
+        final String scheme;
+        final boolean proxy;
+        final PasswordAuthentication value;
+
+        CacheEntry(String authscheme,
+                   URI uri,
+                   boolean proxy,
+                   PasswordAuthentication value) {
+            this.scheme = authscheme;
+            this.root = uri.resolve(".").toString(); // remove extraneous components
+            this.proxy = proxy;
+            this.value = value;
+        }
+
+        public PasswordAuthentication value() {
+            return value;
+        }
+
+        public boolean equalsKey(URI uri, boolean proxy) {
+            if (this.proxy != proxy) {
+                return false;
+            }
+            String other = uri.toString();
+            return other.startsWith(root);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/BufferHandler.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implemented by buffer pools.
+ */
+interface BufferHandler {
+
+    ByteBuffer getBuffer();
+
+    void returnBuffer(ByteBuffer buffer);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/ConnectionPool.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.ListIterator;
+import java.util.Objects;
+
+/**
+ * Http 1.1 connection pool.
+ */
+class ConnectionPool {
+
+    static final long KEEP_ALIVE = Utils.getIntegerNetProperty(
+            "sun.net.httpclient.keepalive.timeout", 1200); // seconds
+
+    // Pools of idle connections
+
+    final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool;
+    final HashMap<CacheKey,LinkedList<HttpConnection>> sslPool;
+    CacheCleaner cleaner;
+
+    /**
+     * Entries in connection pool are keyed by destination address and/or
+     * proxy address:
+     * case 1: plain TCP not via proxy (destination only)
+     * case 2: plain TCP via proxy (proxy only)
+     * case 3: SSL not via proxy (destination only)
+     * case 4: SSL over tunnel (destination and proxy)
+     */
+    static class CacheKey {
+        final InetSocketAddress proxy;
+        final InetSocketAddress destination;
+
+        CacheKey(InetSocketAddress destination, InetSocketAddress proxy) {
+            this.proxy = proxy;
+            this.destination = destination;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            final CacheKey other = (CacheKey) obj;
+            if (!Objects.equals(this.proxy, other.proxy)) {
+                return false;
+            }
+            if (!Objects.equals(this.destination, other.destination)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(proxy, destination);
+        }
+    }
+
+    static class ExpiryEntry {
+        final HttpConnection connection;
+        final long expiry; // absolute time in seconds of expiry time
+        ExpiryEntry(HttpConnection connection, long expiry) {
+            this.connection = connection;
+            this.expiry = expiry;
+        }
+    }
+
+    final LinkedList<ExpiryEntry> expiryList;
+
+    /**
+     * There should be one of these per HttpClient.
+     */
+    ConnectionPool() {
+        plainPool = new HashMap<>();
+        sslPool = new HashMap<>();
+        expiryList = new LinkedList<>();
+        cleaner = new CacheCleaner();
+    }
+
+    void start() {
+        cleaner.start();
+    }
+
+    static CacheKey cacheKey(InetSocketAddress destination,
+                             InetSocketAddress proxy) {
+        return new CacheKey(destination, proxy);
+    }
+
+    synchronized HttpConnection getConnection(boolean secure,
+                                              InetSocketAddress addr,
+                                              InetSocketAddress proxy) {
+        CacheKey key = new CacheKey(addr, proxy);
+        HttpConnection c = secure ? findConnection(key, sslPool)
+                                  : findConnection(key, plainPool);
+        //System.out.println ("getConnection returning: " + c);
+        return c;
+    }
+
+    /**
+     * Returns the connection to the pool.
+     *
+     * @param conn
+     */
+    synchronized void returnToPool(HttpConnection conn) {
+        if (conn instanceof PlainHttpConnection) {
+            putConnection(conn, plainPool);
+        } else {
+            putConnection(conn, sslPool);
+        }
+        addToExpiryList(conn);
+        //System.out.println("Return to pool: " + conn);
+    }
+
+    private HttpConnection
+    findConnection(CacheKey key,
+                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
+        LinkedList<HttpConnection> l = pool.get(key);
+        if (l == null || l.size() == 0) {
+            return null;
+        } else {
+            HttpConnection c = l.removeFirst();
+            removeFromExpiryList(c);
+            return c;
+        }
+    }
+
+    /* called from cache cleaner only  */
+    private void
+    removeFromPool(HttpConnection c,
+                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
+        //System.out.println("cacheCleaner removing: " + c);
+        LinkedList<HttpConnection> l = pool.get(c.cacheKey());
+        assert l != null;
+        boolean wasPresent = l.remove(c);
+        assert wasPresent;
+    }
+
+    private void
+    putConnection(HttpConnection c,
+                  HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
+        CacheKey key = c.cacheKey();
+        LinkedList<HttpConnection> l = pool.get(key);
+        if (l == null) {
+            l = new LinkedList<>();
+            pool.put(key, l);
+        }
+        l.add(c);
+    }
+
+    // only runs while entries exist in cache
+
+    class CacheCleaner extends Thread {
+
+        volatile boolean stopping;
+
+        CacheCleaner() {
+            super(null, null, "HTTP-Cache-cleaner", 0, false);
+            setDaemon(true);
+        }
+
+        synchronized boolean stopping() {
+            return stopping;
+        }
+
+        synchronized void stopCleaner() {
+            stopping = true;
+        }
+
+        @Override
+        public void run() {
+            while (!stopping()) {
+                try {
+                    Thread.sleep(3000);
+                } catch (InterruptedException e) {}
+                cleanCache();
+            }
+        }
+    }
+
+    synchronized void removeFromExpiryList(HttpConnection c) {
+        if (c == null) {
+            return;
+        }
+        ListIterator<ExpiryEntry> li = expiryList.listIterator();
+        while (li.hasNext()) {
+            ExpiryEntry e = li.next();
+            if (e.connection.equals(c)) {
+                li.remove();
+                return;
+            }
+        }
+        if (expiryList.isEmpty()) {
+            cleaner.stopCleaner();
+        }
+    }
+
+    private void cleanCache() {
+        long now = System.currentTimeMillis() / 1000;
+        LinkedList<HttpConnection> closelist = new LinkedList<>();
+
+        synchronized (this) {
+            ListIterator<ExpiryEntry> li = expiryList.listIterator();
+            while (li.hasNext()) {
+                ExpiryEntry entry = li.next();
+                if (entry.expiry <= now) {
+                    li.remove();
+                    HttpConnection c = entry.connection;
+                    closelist.add(c);
+                    if (c instanceof PlainHttpConnection) {
+                        removeFromPool(c, plainPool);
+                    } else {
+                        removeFromPool(c, sslPool);
+                    }
+                }
+            }
+        }
+        for (HttpConnection c : closelist) {
+            //System.out.println ("KAC: closing " + c);
+            c.close();
+        }
+    }
+
+    private synchronized void addToExpiryList(HttpConnection conn) {
+        long now = System.currentTimeMillis() / 1000;
+        long then = now + KEEP_ALIVE;
+
+        if (expiryList.isEmpty())
+            cleaner = new CacheCleaner();
+
+        ListIterator<ExpiryEntry> li = expiryList.listIterator();
+        while (li.hasNext()) {
+            ExpiryEntry entry = li.next();
+
+            if (then > entry.expiry) {
+                li.previous();
+                // insert here
+                li.add(new ExpiryEntry(conn, then));
+                return;
+            }
+        }
+        // first element of list
+        expiryList.add(new ExpiryEntry(conn, then));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/CookieFilter.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.io.IOException;
+import java.net.CookieManager;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+class CookieFilter implements HeaderFilter {
+
+    final HttpClientImpl client;
+    final CookieManager cookieMan;
+
+    CookieFilter(HttpClientImpl client) {
+        this.client = client;
+        this.cookieMan = client.cookieManager().orElseThrow(
+                () -> new IllegalArgumentException("no cookie manager"));
+    }
+
+    @Override
+    public void request(HttpRequestImpl r) throws IOException {
+        Map<String,List<String>> userheaders, cookies;
+        userheaders = r.getUserHeaders().directMap();
+        cookies = cookieMan.get(r.uri(), userheaders);
+        // add the returned cookies
+        HttpHeadersImpl systemHeaders = r.getSystemHeaders();
+        Set<String> keys = cookies.keySet();
+        for (String hdrname : keys) {
+            List<String> vals = cookies.get(hdrname);
+            for (String val : vals) {
+                systemHeaders.addHeader(hdrname, val);
+            }
+        }
+    }
+
+    @Override
+    public HttpRequestImpl response(HttpResponseImpl r) throws IOException {
+        HttpHeaders hdrs = r.headers();
+        cookieMan.put(r.uri(), hdrs.map());
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Exchange.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.InetSocketAddress;
+import java.net.SocketPermission;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLPermission;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * One request/response exchange (handles 100/101 intermediate response also).
+ * depth field used to track number of times a new request is being sent
+ * for a given API request. If limit exceeded exception is thrown.
+ *
+ * Security check is performed here:
+ * - uses AccessControlContext captured at API level
+ * - checks for appropriate URLPermission for request
+ * - if permission allowed, grants equivalent SocketPermission to call
+ * - in case of direct HTTP proxy, checks additionally for access to proxy
+ *    (CONNECT proxying uses its own Exchange, so check done there)
+ *
+ */
+class Exchange {
+
+    final HttpRequestImpl request;
+    final HttpClientImpl client;
+    ExchangeImpl exchImpl;
+    HttpResponseImpl response;
+    final List<SocketPermission> permissions = new LinkedList<>();
+    AccessControlContext acc;
+    boolean upgrading; // to HTTP/2
+
+    Exchange(HttpRequestImpl request) {
+        this.request = request;
+        this.upgrading = false;
+        this.client = request.client();
+    }
+
+    /* If different AccessControlContext to be used  */
+    Exchange(HttpRequestImpl request, AccessControlContext acc) {
+        this.request = request;
+        this.acc = acc;
+        this.upgrading = false;
+        this.client = request.client();
+    }
+
+    public HttpRequestImpl request() {
+        return request;
+    }
+
+    public HttpResponseImpl response() throws IOException, InterruptedException {
+        response = responseImpl(null);
+        return response;
+    }
+
+    public void cancel() {
+        if (exchImpl != null)
+            exchImpl.cancel();
+    }
+
+    public void h2Upgrade() {
+        upgrading = true;
+        request.setH2Upgrade();
+    }
+
+    static final SocketPermission[] SOCKET_ARRAY = new SocketPermission[0];
+
+    HttpResponseImpl responseImpl(HttpConnection connection)
+        throws IOException, InterruptedException
+    {
+        if (acc == null) {
+            acc = request.getAccessControlContext();
+        }
+        SecurityException e = securityCheck(acc);
+        if (e != null)
+            throw e;
+
+        if (permissions.size() > 0) {
+            try {
+                return AccessController.doPrivileged(
+                        (PrivilegedExceptionAction<HttpResponseImpl>)() ->
+                             responseImpl0(connection),
+                        null,
+                        permissions.toArray(SOCKET_ARRAY));
+            } catch (Throwable ee) {
+                if (ee instanceof PrivilegedActionException) {
+                    ee = ee.getCause();
+                }
+                if (ee instanceof IOException)
+                    throw (IOException)ee;
+                else
+                    throw new RuntimeException(ee); // TODO: fix
+            }
+        } else {
+            return responseImpl0(connection);
+        }
+    }
+
+    HttpResponseImpl responseImpl0(HttpConnection connection)
+        throws IOException, InterruptedException
+    {
+        exchImpl = ExchangeImpl.get(this, connection);
+        if (request.expectContinue()) {
+            request.addSystemHeader("Expect", "100-Continue");
+            exchImpl.sendHeadersOnly();
+            HttpResponseImpl resp = exchImpl.getResponse();
+            logResponse(resp);
+            if (resp.statusCode() != 100) {
+                return resp;
+            }
+            exchImpl.sendBody();
+            return exchImpl.getResponse();
+        } else {
+            exchImpl.sendRequest();
+            HttpResponseImpl resp = exchImpl.getResponse();
+            logResponse(resp);
+            return checkForUpgrade(resp, exchImpl);
+        }
+    }
+
+    // Completed HttpResponse will be null if response succeeded
+    // will be a non null responseAsync if expect continue returns an error
+
+    public CompletableFuture<HttpResponseImpl> responseAsync(Void v) {
+        return responseAsyncImpl(null);
+    }
+
+    CompletableFuture<HttpResponseImpl> responseAsyncImpl(HttpConnection connection) {
+        if (acc == null) {
+            acc = request.getAccessControlContext();
+        }
+        SecurityException e = securityCheck(acc);
+        if (e != null) {
+            CompletableFuture<HttpResponseImpl> cf = new CompletableFuture<>();
+            cf.completeExceptionally(e);
+            return cf;
+        }
+        if (permissions.size() > 0) {
+            return AccessController.doPrivileged(
+                    (PrivilegedAction<CompletableFuture<HttpResponseImpl>>)() ->
+                        responseAsyncImpl0(connection),
+                    null,
+                    permissions.toArray(SOCKET_ARRAY));
+        } else {
+            return responseAsyncImpl0(connection);
+        }
+    }
+
+    CompletableFuture<HttpResponseImpl> responseAsyncImpl0(HttpConnection connection) {
+        try {
+            exchImpl = ExchangeImpl.get(this, connection);
+        } catch (IOException | InterruptedException e) {
+            CompletableFuture<HttpResponseImpl> cf = new CompletableFuture<>();
+            cf.completeExceptionally(e);
+            return cf;
+        }
+        if (request.expectContinue()) {
+            request.addSystemHeader("Expect", "100-Continue");
+            return exchImpl.sendHeadersAsync()
+                    .thenCompose(exchImpl::getResponseAsync)
+                    .thenCompose((HttpResponseImpl r1) -> {
+                        int rcode = r1.statusCode();
+                        CompletableFuture<HttpResponseImpl> cf =
+                                checkForUpgradeAsync(r1, exchImpl);
+                        if (cf != null)
+                            return cf;
+                        if (rcode == 100) {
+                            return exchImpl.sendBodyAsync()
+                                .thenCompose(exchImpl::getResponseAsync)
+                                .thenApply((r) -> {
+                                    logResponse(r);
+                                    return r;
+                                });
+                        } else {
+                            Exchange.this.response = r1;
+                            logResponse(r1);
+                            return CompletableFuture.completedFuture(r1);
+                        }
+                    });
+        } else {
+            return exchImpl
+                .sendHeadersAsync()
+                .thenCompose((Void v) -> {
+                    // send body and get response at same time
+                    exchImpl.sendBodyAsync();
+                    return exchImpl.getResponseAsync(null);
+                })
+                    .thenCompose((HttpResponseImpl r1) -> {
+                        int rcode = r1.statusCode();
+                        CompletableFuture<HttpResponseImpl> cf =
+                                checkForUpgradeAsync(r1, exchImpl);
+                        if (cf != null) {
+                            return cf;
+                        } else {
+                            Exchange.this.response = r1;
+                            logResponse(r1);
+                            return CompletableFuture.completedFuture(r1);
+                        }
+                    })
+                .thenApply((HttpResponseImpl response) -> {
+                    this.response = response;
+                    logResponse(response);
+                    return response;
+                });
+        }
+    }
+
+    // if this response was received in reply to an upgrade
+    // then create the Http2Connection from the HttpConnection
+    // initialize it and wait for the real response on a newly created Stream
+
+    private CompletableFuture<HttpResponseImpl>
+    checkForUpgradeAsync(HttpResponseImpl resp,
+                         ExchangeImpl ex) {
+        int rcode = resp.statusCode();
+        if (upgrading && (rcode == 101)) {
+            Http1Exchange e = (Http1Exchange)ex;
+            // check for 101 switching protocols
+            return e.responseBodyAsync(HttpResponse.ignoreBody())
+                .thenCompose((Void v) ->
+                     Http2Connection.createAsync(e.connection(),
+                                                 client.client2(),
+                                                 this)
+                        .thenCompose((Http2Connection c) -> {
+                            Stream s = c.getStream(1);
+                            exchImpl = s;
+                            c.putConnection();
+                            return s.getResponseAsync(null);
+                        })
+                );
+        }
+        return CompletableFuture.completedFuture(resp);
+    }
+
+    private HttpResponseImpl checkForUpgrade(HttpResponseImpl resp,
+                                             ExchangeImpl ex)
+        throws IOException, InterruptedException
+    {
+        int rcode = resp.statusCode();
+        if (upgrading && (rcode == 101)) {
+            Http1Exchange e = (Http1Exchange) ex;
+            // must get connection from Http1Exchange
+            e.responseBody(HttpResponse.ignoreBody(), false);
+            Http2Connection h2con = new Http2Connection(e.connection(),
+                                                        client.client2(),
+                                                        this);
+            h2con.putConnection();
+            Stream s = h2con.getStream(1);
+            exchImpl = s;
+            return s.getResponse();
+        }
+        return resp;
+    }
+
+
+    <T> T responseBody(HttpResponse.BodyProcessor<T> processor) {
+        try {
+            return exchImpl.responseBody(processor);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+
+    private void logResponse(HttpResponseImpl r) {
+        if (!Log.requests())
+            return;
+        StringBuilder sb = new StringBuilder();
+        String method = r.request().method();
+        URI uri = r.uri();
+        String uristring = uri == null ? "" : uri.toString();
+        sb.append('(')
+          .append(method)
+          .append(" ")
+          .append(uristring)
+          .append(") ")
+          .append(Integer.toString(r.statusCode()));
+        Log.logResponse(sb.toString());
+    }
+
+    <T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
+        return exchImpl.responseBodyAsync(processor);
+    }
+
+    private URI getURIForSecurityCheck() {
+        URI u;
+        String method = request.method();
+        InetSocketAddress authority = request.authority();
+        URI uri = request.uri();
+
+        // CONNECT should be restricted at API level
+        if (method.equalsIgnoreCase("CONNECT")) {
+            try {
+                u = new URI("socket",
+                             null,
+                             authority.getHostString(),
+                             authority.getPort(),
+                             null,
+                             null,
+                             null);
+            } catch (URISyntaxException e) {
+                throw new InternalError(e); // shouldn't happen
+            }
+        } else {
+            u = uri;
+        }
+        return u;
+    }
+
+    /**
+     * Do the security check and return any exception.
+     * Return null if no check needed or passes.
+     *
+     * Also adds any generated permissions to the "permissions" list.
+     */
+    private SecurityException securityCheck(AccessControlContext acc) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm == null) {
+            return null;
+        }
+
+        String method = request.method();
+        HttpHeadersImpl userHeaders = request.getUserHeaders();
+        URI u = getURIForSecurityCheck();
+        URLPermission p = Utils.getPermission(u, method, userHeaders.directMap());
+
+        try {
+            assert acc != null;
+            sm.checkPermission(p, acc);
+            permissions.add(getSocketPermissionFor(u));
+        } catch (SecurityException e) {
+            return e;
+        }
+        InetSocketAddress proxy = request.proxy();
+        if (proxy != null) {
+            // may need additional check
+            if (!method.equals("CONNECT")) {
+                // a direct http proxy. Need to check access to proxy
+                try {
+                    u = new URI("socket", null, proxy.getHostString(),
+                        proxy.getPort(), null, null, null);
+                } catch (URISyntaxException e) {
+                    throw new InternalError(e); // shouldn't happen
+                }
+                p = new URLPermission(u.toString(), "CONNECT");
+                try {
+                    sm.checkPermission(p, acc);
+                } catch (SecurityException e) {
+                    permissions.clear();
+                    return e;
+                }
+                String sockperm = proxy.getHostString() +
+                        ":" + Integer.toString(proxy.getPort());
+
+                permissions.add(new SocketPermission(sockperm, "connect,resolve"));
+            }
+        }
+        return null;
+    }
+
+    private static SocketPermission getSocketPermissionFor(URI url) {
+        if (System.getSecurityManager() == null)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        String host = url.getHost();
+        sb.append(host);
+        int port = url.getPort();
+        if (port == -1) {
+            String scheme = url.getScheme();
+            if ("http".equals(scheme)) {
+                sb.append(":80");
+            } else { // scheme must be https
+                sb.append(":443");
+            }
+        } else {
+            sb.append(':')
+              .append(Integer.toString(port));
+        }
+        String target = sb.toString();
+        return new SocketPermission(target, "connect");
+    }
+
+    AccessControlContext getAccessControlContext() {
+        return acc;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/ExchangeImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+
+/**
+ * Splits request so that headers and body can be sent separately with optional
+ * (multiple) responses in between (e.g. 100 Continue). Also request and
+ * response always sent/received in different calls.
+ *
+ * Synchronous and asynchronous versions of each method are provided.
+ *
+ * Separate implementations of this class exist for HTTP/1.1 and HTTP/2
+ *      Http1Exchange   (HTTP/1.1)
+ *      Stream          (HTTP/2)
+ *
+ * These implementation classes are where work is allocated to threads.
+ */
+abstract class ExchangeImpl {
+
+    final Exchange exchange;
+
+    ExchangeImpl(Exchange e) {
+        this.exchange = e;
+    }
+
+    /**
+     * Initiates a new exchange and assigns it to a connection if one exists
+     * already. connection usually null.
+     */
+    static ExchangeImpl get(Exchange exchange, HttpConnection connection)
+        throws IOException, InterruptedException
+    {
+        HttpRequestImpl req = exchange.request();
+        if (req.version() == HTTP_1_1) {
+            return new Http1Exchange(exchange, connection);
+        } else {
+            Http2ClientImpl c2 = exchange.request().client().client2(); // TODO: improve
+            HttpRequestImpl request = exchange.request();
+            Http2Connection c = c2.getConnectionFor(request);
+            if (c == null) {
+                // no existing connection. Send request with HTTP 1 and then
+                // upgrade if successful
+                ExchangeImpl ex = new Http1Exchange(exchange, connection);
+                exchange.h2Upgrade();
+                return ex;
+            }
+            return c.createStream(exchange);
+        }
+    }
+
+    /* The following methods have separate HTTP/1.1 and HTTP/2 implementations */
+
+    /**
+     * Sends the request headers only. May block until all sent.
+     */
+    abstract void sendHeadersOnly() throws IOException, InterruptedException;
+
+    /**
+     * Gets response headers by blocking if necessary. This may be an
+     * intermediate response (like 101) or a final response 200 etc.
+     */
+    abstract HttpResponseImpl getResponse() throws IOException;
+
+    /**
+     * Sends a request body after request headers.
+     */
+    abstract void sendBody() throws IOException, InterruptedException;
+
+    /**
+     * Sends the entire request (headers and body) blocking.
+     */
+    abstract void sendRequest() throws IOException, InterruptedException;
+
+    /**
+     * Asynchronous version of sendHeaders().
+     */
+    abstract CompletableFuture<Void> sendHeadersAsync();
+
+    /**
+     * Asynchronous version of getResponse().  Requires void parameter for
+     * CompletableFuture chaining.
+     */
+    abstract CompletableFuture<HttpResponseImpl> getResponseAsync(Void v);
+
+    /**
+     * Asynchronous version of sendBody().
+     */
+    abstract CompletableFuture<Void> sendBodyAsync();
+
+    /**
+     * Cancels a request.  Not currently exposed through API.
+     */
+    abstract void cancel();
+
+    /**
+     * Asynchronous version of sendRequest().
+     */
+    abstract CompletableFuture<Void> sendRequestAsync();
+
+    abstract <T> T responseBody(HttpResponse.BodyProcessor<T> processor)
+        throws IOException;
+
+    /**
+     * Asynchronous version of responseBody().
+     */
+    abstract <T> CompletableFuture<T>
+    responseBodyAsync(HttpResponse.BodyProcessor<T> processor);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/ExecutorWrapper.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
+
+/**
+ * Wraps the supplied user ExecutorService.
+ *
+ * 1) when a Security manager set, the correct access control context
+ *    is used to execute task
+ *
+ * 2) memory fence implemented
+ */
+class ExecutorWrapper {
+
+    final ExecutorService userExecutor; // the actual executor service used
+    final Executor executor;
+
+    public static ExecutorWrapper wrap(ExecutorService userExecutor) {
+        return new ExecutorWrapper(userExecutor);
+    }
+
+    /**
+     * Returns a dummy ExecutorWrapper which uses the calling thread
+     */
+    public static ExecutorWrapper callingThread() {
+        return new ExecutorWrapper();
+    }
+
+    private ExecutorWrapper(ExecutorService userExecutor) {
+        // used for executing in calling thread
+        this.userExecutor = userExecutor;
+        this.executor = userExecutor;
+    }
+
+    private ExecutorWrapper() {
+        this.userExecutor = null;
+        this.executor = (Runnable command) -> {
+            command.run();
+        };
+    }
+
+    public ExecutorService userExecutor() {
+        return userExecutor;
+    }
+
+    public synchronized void synchronize() {}
+
+    public void execute(Runnable r, Supplier<AccessControlContext> ctxSupplier) {
+        synchronize();
+        Runnable r1 = () -> {
+            try {
+                r.run();
+            } catch (Throwable t) {
+                Log.logError(t);
+            }
+        };
+
+        if (ctxSupplier != null && System.getSecurityManager() != null) {
+            AccessControlContext acc = ctxSupplier.get();
+            if (acc == null) {
+                throw new InternalError();
+            }
+            AccessController.doPrivilegedWithCombiner(
+                (PrivilegedAction<Void>)() -> {
+                    executor.execute(r1); // all throwables must be caught
+                    return null;
+                }, acc);
+        } else {
+            executor.execute(r1); // all throwables must be caught
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/FilterFactory.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.util.LinkedList;
+import java.util.List;
+
+class FilterFactory {
+
+    final LinkedList<Class<? extends HeaderFilter>> filterClasses = new LinkedList<>();
+
+    public void addFilter(Class<? extends HeaderFilter> type) {
+        filterClasses.add(type);
+    }
+
+    List<HeaderFilter> getFilterChain() {
+        List<HeaderFilter> l = new LinkedList<>();
+        for (Class<? extends HeaderFilter> clazz : filterClasses) {
+            try {
+                l.add(clazz.newInstance());
+            } catch (ReflectiveOperationException e) {
+                throw new InternalError(e);
+            }
+        }
+        return l;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HeaderFilter.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.io.IOException;
+
+/**
+ * A header filter that can examine or modify, typically system headers for
+ * requests before they are sent, and responses before they are returned to the
+ * user. Some ability to resend requests is provided.
+ *
+ */
+interface HeaderFilter {
+
+    void request(HttpRequestImpl r) throws IOException;
+
+    /**
+     * Returns null if response ok to be given to user.  Non null is a request
+     * that must be resent and its response given to user. If impl throws an
+     * exception that is returned to user instead.
+     */
+    HttpRequestImpl response(HttpResponseImpl r) throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HeaderParser.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+/* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
+ * sensibly:
+ * From a String like: 'timeout=15, max=5'
+ * create an array of Strings:
+ * { {"timeout", "15"},
+ *   {"max", "5"}
+ * }
+ * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
+ * create one like (no quotes in literal):
+ * { {"basic", null},
+ *   {"realm", "FuzzFace"}
+ *   {"foo", "Biz Bar Baz"}
+ * }
+ * keys are converted to lower case, vals are left as is....
+ */
+class HeaderParser {
+
+    /* table of key/val pairs */
+    String raw;
+    String[][] tab;
+    int nkeys;
+    int asize = 10; // initial size of array is 10
+
+    public HeaderParser(String raw) {
+        this.raw = raw;
+        tab = new String[asize][2];
+        parse();
+    }
+
+    private HeaderParser () { }
+
+    /**
+     * Creates a new HeaderParser from this, whose keys (and corresponding
+     * values) range from "start" to "end-1"
+     */
+    public HeaderParser subsequence(int start, int end) {
+        if (start == 0 && end == nkeys) {
+            return this;
+        }
+        if (start < 0 || start >= end || end > nkeys)
+            throw new IllegalArgumentException("invalid start or end");
+        HeaderParser n = new HeaderParser();
+        n.tab = new String [asize][2];
+        n.asize = asize;
+        System.arraycopy (tab, start, n.tab, 0, (end-start));
+        n.nkeys= (end-start);
+        return n;
+    }
+
+    private void parse() {
+
+        if (raw != null) {
+            raw = raw.trim();
+            char[] ca = raw.toCharArray();
+            int beg = 0, end = 0, i = 0;
+            boolean inKey = true;
+            boolean inQuote = false;
+            int len = ca.length;
+            while (end < len) {
+                char c = ca[end];
+                if ((c == '=') && !inQuote) { // end of a key
+                    tab[i][0] = new String(ca, beg, end-beg).toLowerCase(Locale.US);
+                    inKey = false;
+                    end++;
+                    beg = end;
+                } else if (c == '\"') {
+                    if (inQuote) {
+                        tab[i++][1]= new String(ca, beg, end-beg);
+                        inQuote=false;
+                        do {
+                            end++;
+                        } while (end < len && (ca[end] == ' ' || ca[end] == ','));
+                        inKey=true;
+                        beg=end;
+                    } else {
+                        inQuote=true;
+                        end++;
+                        beg=end;
+                    }
+                } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
+                    if (inQuote) {
+                        end++;
+                        continue;
+                    } else if (inKey) {
+                        tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase(Locale.US);
+                    } else {
+                        tab[i++][1] = (new String(ca, beg, end-beg));
+                    }
+                    while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
+                        end++;
+                    }
+                    inKey = true;
+                    beg = end;
+                } else {
+                    end++;
+                }
+                if (i == asize) {
+                    asize = asize * 2;
+                    String[][] ntab = new String[asize][2];
+                    System.arraycopy (tab, 0, ntab, 0, tab.length);
+                    tab = ntab;
+                }
+            }
+            // get last key/val, if any
+            if (--end > beg) {
+                if (!inKey) {
+                    if (ca[end] == '\"') {
+                        tab[i++][1] = (new String(ca, beg, end-beg));
+                    } else {
+                        tab[i++][1] = (new String(ca, beg, end-beg+1));
+                    }
+                } else {
+                    tab[i++][0] = (new String(ca, beg, end-beg+1)).toLowerCase();
+                }
+            } else if (end == beg) {
+                if (!inKey) {
+                    if (ca[end] == '\"') {
+                        tab[i++][1] = String.valueOf(ca[end-1]);
+                    } else {
+                        tab[i++][1] = String.valueOf(ca[end]);
+                    }
+                } else {
+                    tab[i++][0] = String.valueOf(ca[end]).toLowerCase();
+                }
+            }
+            nkeys=i;
+        }
+    }
+
+    public String findKey(int i) {
+        if (i < 0 || i > asize)
+            return null;
+        return tab[i][0];
+    }
+
+    public String findValue(int i) {
+        if (i < 0 || i > asize)
+            return null;
+        return tab[i][1];
+    }
+
+    public String findValue(String key) {
+        return findValue(key, null);
+    }
+
+    public String findValue(String k, String Default) {
+        if (k == null)
+            return Default;
+        k = k.toLowerCase(Locale.US);
+        for (int i = 0; i < asize; ++i) {
+            if (tab[i][0] == null) {
+                return Default;
+            } else if (k.equals(tab[i][0])) {
+                return tab[i][1];
+            }
+        }
+        return Default;
+    }
+
+    class ParserIterator implements Iterator<String> {
+        int index;
+        boolean returnsValue; // or key
+
+        ParserIterator (boolean returnValue) {
+            returnsValue = returnValue;
+        }
+        @Override
+        public boolean hasNext () {
+            return index<nkeys;
+        }
+        @Override
+        public String next () {
+            if (index >= nkeys)
+                throw new NoSuchElementException();
+            return tab[index++][returnsValue?1:0];
+        }
+    }
+
+    public Iterator<String> keys () {
+        return new ParserIterator (false);
+    }
+
+    public Iterator<String> values () {
+        return new ParserIterator (true);
+    }
+
+    @Override
+    public String toString () {
+        Iterator<String> k = keys();
+        StringBuilder sb = new StringBuilder();
+        sb.append("{size=").append(asize).append(" nkeys=").append(nkeys)
+                .append(' ');
+        for (int i=0; k.hasNext(); i++) {
+            String key = k.next();
+            String val = findValue (i);
+            if (val != null && "".equals (val)) {
+                val = null;
+            }
+            sb.append(" {").append(key).append(val == null ? "" : "," + val)
+                    .append('}');
+            if (k.hasNext()) {
+                sb.append (',');
+            }
+        }
+        sb.append (" }");
+        return sb.toString();
+    }
+
+    public int findInt(String k, int Default) {
+        try {
+            return Integer.parseInt(findValue(k, String.valueOf(Default)));
+        } catch (Throwable t) {
+            return Default;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Http1Exchange.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Encapsulates one HTTP/1.1 request/responseAsync exchange.
+ */
+class Http1Exchange extends ExchangeImpl {
+
+    final HttpRequestImpl request;        // main request
+    final List<CompletableFuture<?>> operations; // used for cancel
+    final Http1Request requestAction;
+    volatile Http1Response response;
+    final HttpConnection connection;
+    final HttpClientImpl client;
+    final ExecutorWrapper executor;
+
+    @Override
+    public String toString() {
+        return request.toString();
+    }
+
+    HttpRequestImpl request() {
+        return request;
+    }
+
+    Http1Exchange(Exchange exchange, HttpConnection connection)
+        throws IOException
+    {
+        super(exchange);
+        this.request = exchange.request();
+        this.client = request.client();
+        this.executor = client.executorWrapper();
+        this.operations = Collections.synchronizedList(new LinkedList<>());
+        if (connection != null) {
+            this.connection = connection;
+        } else {
+            InetSocketAddress addr = getAddress(request);
+            this.connection = HttpConnection.getConnection(addr, request);
+        }
+        this.requestAction = new Http1Request(request, this.connection);
+    }
+
+    private static InetSocketAddress getAddress(HttpRequestImpl req) {
+        URI uri = req.uri();
+        if (uri == null) {
+            return req.authority();
+        }
+        int port = uri.getPort();
+        if (port == -1) {
+            if (uri.getScheme().equalsIgnoreCase("https")) {
+                port = 443;
+            } else {
+                port = 80;
+            }
+        }
+        String host = uri.getHost();
+        if (req.proxy() == null) {
+            return new InetSocketAddress(host, port);
+        } else {
+            return InetSocketAddress.createUnresolved(host, port);
+        }
+    }
+
+    HttpConnection connection() {
+        return connection;
+    }
+
+    @Override
+    <T> T responseBody(HttpResponse.BodyProcessor<T> processor)
+        throws IOException
+    {
+        return responseBody(processor, true);
+    }
+
+    <T> T responseBody(HttpResponse.BodyProcessor<T> processor,
+                       boolean return2Cache)
+        throws IOException
+    {
+        try {
+            T body = response.readBody(processor, return2Cache);
+            return body;
+        } catch (Throwable t) {
+            connection.close();
+            throw t;
+        }
+    }
+
+    @Override
+    <T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
+        CompletableFuture<T> cf = new CompletableFuture<>();
+        request.client()
+               .executorWrapper()
+               .execute(() -> {
+                            try {
+                                T body = responseBody(processor);
+                                cf.complete(body);
+                            } catch (Throwable e) {
+                                cf.completeExceptionally(e);
+                            }
+                        },
+                        () -> response.response.getAccessControlContext()); // TODO: fix
+        return cf;
+    }
+
+    @Override
+    void sendHeadersOnly() throws IOException, InterruptedException {
+        try {
+            if (!connection.connected()) {
+                connection.connect();
+            }
+            requestAction.sendHeadersOnly();
+        } catch (Throwable e) {
+            connection.close();
+            throw e;
+        }
+    }
+
+    @Override
+    void sendBody() throws IOException {
+        try {
+            requestAction.continueRequest();
+        } catch (Throwable e) {
+            connection.close();
+            throw e;
+        }
+    }
+
+    @Override
+    HttpResponseImpl getResponse() throws IOException {
+        try {
+            response = new Http1Response(connection, this);
+            response.readHeaders();
+            return response.response();
+        } catch (Throwable t) {
+            connection.close();
+            throw t;
+        }
+    }
+
+    @Override
+    void sendRequest() throws IOException, InterruptedException {
+        try {
+            if (!connection.connected()) {
+                connection.connect();
+            }
+            requestAction.sendRequest();
+        } catch (Throwable t) {
+            connection.close();
+            throw t;
+        }
+    }
+
+    private void closeConnection() {
+        connection.close();
+    }
+
+    @Override
+    CompletableFuture<Void> sendHeadersAsync() {
+        if (!connection.connected()) {
+            CompletableFuture<Void> op = connection.connectAsync()
+                    .thenCompose(this::sendHdrsAsyncImpl)
+                    .whenComplete((Void b, Throwable t) -> {
+                        if (t != null)
+                            closeConnection();
+                    });
+            operations.add(op);
+            return op;
+        } else {
+            return sendHdrsAsyncImpl(null);
+        }
+    }
+
+    private CompletableFuture<Void> sendHdrsAsyncImpl(Void v) {
+        CompletableFuture<Void> cf = new CompletableFuture<>();
+        executor.execute(() -> {
+                            try {
+                                requestAction.sendHeadersOnly();
+                                cf.complete(null);
+                            } catch (Throwable e) {
+                                cf.completeExceptionally(e);
+                                connection.close();
+                            }
+                         },
+                         () -> request.getAccessControlContext());
+        operations.add(cf);
+        return cf;
+    }
+
+    /**
+     * Cancel checks to see if request and responseAsync finished already.
+     * If not it closes the connection and completes all pending operations
+     */
+    @Override
+    synchronized void cancel() {
+        if (requestAction != null && requestAction.finished()
+                && response != null && response.finished()) {
+            return;
+        }
+        connection.close();
+        IOException e = new IOException("Request cancelled");
+        int count = 0;
+        for (CompletableFuture<?> cf : operations) {
+            cf.completeExceptionally(e);
+            count++;
+        }
+        Log.logError("Http1Exchange.cancel: count=" + count);
+    }
+
+    CompletableFuture<HttpResponseImpl> getResponseAsyncImpl(Void v) {
+        CompletableFuture<HttpResponseImpl> cf = new CompletableFuture<>();
+        try {
+            response = new Http1Response(connection, Http1Exchange.this);
+            response.readHeaders();
+            cf.complete(response.response());
+        } catch (IOException e) {
+            cf.completeExceptionally(e);
+        }
+        return cf;
+    }
+
+    @Override
+    CompletableFuture<HttpResponseImpl> getResponseAsync(Void v) {
+        CompletableFuture<HttpResponseImpl> cf =
+            connection.whenReceivingResponse()
+                      .thenCompose(this::getResponseAsyncImpl);
+
+        operations.add(cf);
+        return cf;
+    }
+
+    @Override
+    CompletableFuture<Void> sendBodyAsync() {
+        final CompletableFuture<Void> cf = new CompletableFuture<>();
+        executor.execute(() -> {
+            try {
+                requestAction.continueRequest();
+                cf.complete(null);
+            } catch (Throwable e) {
+                cf.completeExceptionally(e);
+                connection.close();
+            }
+        }, () -> request.getAccessControlContext());
+        operations.add(cf);
+        return cf;
+    }
+
+    @Override
+    CompletableFuture<Void> sendRequestAsync() {
+        CompletableFuture<Void> op;
+        if (!connection.connected()) {
+            op = connection.connectAsync()
+                .thenCompose(this::sendRequestAsyncImpl)
+                .whenComplete((Void v, Throwable t) -> {
+                    if (t != null) {
+                        closeConnection();
+                    }
+                });
+        } else {
+            op = sendRequestAsyncImpl(null);
+        }
+        operations.add(op);
+        return op;
+    }
+
+    CompletableFuture<Void> sendRequestAsyncImpl(Void v) {
+        CompletableFuture<Void> cf = new CompletableFuture<>();
+        executor.execute(() -> {
+            try {
+                requestAction.sendRequest();
+                cf.complete(null);
+            } catch (Throwable e) {
+                cf.completeExceptionally(e);
+                connection.close();
+            }
+        }, () -> request.getAccessControlContext());
+        operations.add(cf);
+        return cf;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Http1Request.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.function.LongConsumer;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+/**
+ *  A HTTP/1.1 request.
+ *
+ * send() -> Writes the request + body to the given channel, in one blocking
+ * operation.
+ */
+class Http1Request {
+
+    final HttpRequestImpl request;
+    final HttpConnection chan;
+    // Multiple buffers are used to hold different parts of request
+    // See line 206 and below for description
+    final ByteBuffer[] buffers;
+    final HttpRequest.BodyProcessor requestProc;
+    final HttpHeadersImpl userHeaders, systemHeaders;
+    final LongConsumer flowController;
+    boolean streaming;
+    long contentLength;
+
+    Http1Request(HttpRequestImpl request, HttpConnection connection)
+        throws IOException
+    {
+        this.request = request;
+        this.chan = connection;
+        buffers = new ByteBuffer[5]; // TODO: check
+        this.requestProc = request.requestProcessor();
+        this.userHeaders = request.getUserHeaders();
+        this.systemHeaders = request.getSystemHeaders();
+        this.flowController = this::dummy;
+    }
+
+    private void logHeaders() throws IOException {
+        StringBuilder sb = new StringBuilder(256);
+        sb.append("REQUEST HEADERS:\r\n");
+        collectHeaders1(sb, request, systemHeaders);
+        collectHeaders1(sb, request, userHeaders);
+        Log.logHeaders(sb.toString());
+    }
+
+    private void dummy(long x) {
+        // not used in this class
+    }
+
+    private void collectHeaders0() throws IOException {
+        if (Log.headers()) {
+            logHeaders();
+        }
+        StringBuilder sb = new StringBuilder(256);
+        collectHeaders1(sb, request, systemHeaders);
+        collectHeaders1(sb, request, userHeaders);
+        sb.append("\r\n");
+        String headers = sb.toString();
+        buffers[1] = ByteBuffer.wrap(headers.getBytes(StandardCharsets.US_ASCII));
+    }
+
+    private void collectHeaders1(StringBuilder sb,
+                                 HttpRequestImpl request,
+                                 HttpHeadersImpl headers)
+        throws IOException
+    {
+        Map<String,List<String>> h = headers.directMap();
+        Set<Map.Entry<String,List<String>>> entries = h.entrySet();
+
+        for (Map.Entry<String,List<String>> entry : entries) {
+            String key = entry.getKey();
+            sb.append(key).append(": ");
+            List<String> values = entry.getValue();
+            int num = values.size();
+            for (String value : values) {
+                sb.append(value);
+                if (--num > 0) {
+                    sb.append(',');
+                }
+            }
+            sb.append("\r\n");
+        }
+    }
+
+    private static final int BUFSIZE = 64 * 1024; // TODO: configurable?
+
+    private String getPathAndQuery(URI uri) {
+        String path = uri.getPath();
+        String query = uri.getQuery();
+        if (path == null || path.equals("")) {
+            path = "/";
+        }
+        if (query == null) {
+            query = "";
+        }
+        if (query.equals("")) {
+            return path;
+        } else {
+            return path + "?" + query;
+        }
+    }
+
+    private String authorityString(InetSocketAddress addr) {
+        return addr.getHostString() + ":" + addr.getPort();
+    }
+
+    private String requestURI() {
+        URI uri = request.uri();
+        String method = request.method();
+
+        if ((request.proxy() == null && !method.equals("CONNECT"))
+                || request.isWebSocket()) {
+            return getPathAndQuery(uri);
+        }
+        if (request.secure()) {
+            if (request.method().equals("CONNECT")) {
+                // use authority for connect itself
+                return authorityString(request.authority());
+            } else {
+                // requests over tunnel do not require full URL
+                return getPathAndQuery(uri);
+            }
+        }
+        return uri == null? authorityString(request.authority()) : uri.toString();
+    }
+
+    void sendHeadersOnly() throws IOException {
+        collectHeaders();
+        chan.write(buffers, 0, 2);
+    }
+
+    void sendRequest() throws IOException {
+        collectHeaders();
+        if (contentLength == 0) {
+            chan.write(buffers, 0, 2);
+        } else if (contentLength > 0) {
+            writeFixedContent(true);
+        } else {
+            writeStreamedContent(true);
+        }
+        setFinished();
+    }
+
+    private boolean finished;
+
+    synchronized boolean finished() {
+        return  finished;
+    }
+
+    synchronized void setFinished() {
+        finished = true;
+    }
+
+    private void collectHeaders() throws IOException {
+        if (Log.requests() && request != null) {
+            Log.logRequest(request.toString());
+        }
+        String uriString = requestURI();
+        StringBuilder sb = new StringBuilder(64);
+        sb.append(request.method())
+          .append(' ')
+          .append(uriString)
+          .append(" HTTP/1.1\r\n");
+        String cmd = sb.toString();
+
+        buffers[0] = ByteBuffer.wrap(cmd.getBytes(StandardCharsets.US_ASCII));
+        URI uri = request.uri();
+        if (uri != null) {
+            systemHeaders.setHeader("Host", uri.getHost());
+        }
+        if (request == null) {
+            // this is not a user request. No content
+            contentLength = 0;
+        } else {
+            contentLength = requestProc.onRequestStart(request, flowController);
+        }
+
+        if (contentLength == 0) {
+            systemHeaders.setHeader("Content-Length", "0");
+            collectHeaders0();
+        } else if (contentLength > 0) {
+            /* [0] request line [1] headers [2] body  */
+            systemHeaders.setHeader("Content-Length",
+                                    Integer.toString((int) contentLength));
+            streaming = false;
+            collectHeaders0();
+            buffers[2] = chan.getBuffer();
+        } else {
+            /* Chunked:
+             *
+             * [0] request line [1] headers [2] chunk header [3] chunk data [4]
+             * final chunk header and trailing CRLF of previous chunks
+             *
+             * 2,3,4 used repeatedly */
+            streaming = true;
+            systemHeaders.setHeader("Transfer-encoding", "chunked");
+            collectHeaders0();
+            buffers[3] = chan.getBuffer();
+        }
+    }
+
+    // The following two methods used by Http1Exchange to handle expect continue
+
+    void continueRequest() throws IOException {
+        if (streaming) {
+            writeStreamedContent(false);
+        } else {
+            writeFixedContent(false);
+        }
+        setFinished();
+    }
+
+    /* Entire request is sent, or just body only  */
+    private void writeStreamedContent(boolean includeHeaders)
+        throws IOException
+    {
+        if (requestProc instanceof HttpRequest.BodyProcessor) {
+            HttpRequest.BodyProcessor pullproc = requestProc;
+            int startbuf, nbufs;
+
+            if (includeHeaders) {
+                startbuf = 0;
+                nbufs = 5;
+            } else {
+                startbuf = 2;
+                nbufs = 3;
+            }
+            try {
+                // TODO: currently each write goes out as one chunk
+                // TODO: should be collecting data and buffer it.
+
+                buffers[3].clear();
+                boolean done = pullproc.onRequestBodyChunk(buffers[3]);
+                int chunklen = buffers[3].position();
+                buffers[2] = getHeader(chunklen);
+                buffers[3].flip();
+                buffers[4] = CRLF_BUFFER();
+                chan.write(buffers, startbuf, nbufs);
+                while (!done) {
+                    buffers[3].clear();
+                    done = pullproc.onRequestBodyChunk(buffers[3]);
+                    if (done)
+                        break;
+                    buffers[3].flip();
+                    chunklen = buffers[3].remaining();
+                    buffers[2] = getHeader(chunklen);
+                    buffers[4] = CRLF_BUFFER();
+                    chan.write(buffers, 2, 3);
+                }
+                buffers[3] = EMPTY_CHUNK_HEADER();
+                buffers[4] = CRLF_BUFFER();
+                chan.write(buffers, 3, 2);
+            } catch (IOException e) {
+                requestProc.onRequestError(e);
+                throw e;
+            }
+        }
+    }
+    /* Entire request is sent, or just body only */
+    private void writeFixedContent(boolean includeHeaders)
+        throws IOException
+    {
+        try {
+            int startbuf, nbufs;
+
+            if (contentLength == 0) {
+                return;
+            }
+            if (includeHeaders) {
+                startbuf = 0;
+                nbufs = 3;
+            } else {
+                startbuf = 2;
+                nbufs = 1;
+                buffers[0].clear().flip();
+                buffers[1].clear().flip();
+            }
+            buffers[2] = chan.getBuffer();
+            if (requestProc instanceof HttpRequest.BodyProcessor) {
+                HttpRequest.BodyProcessor pullproc = requestProc;
+
+                boolean done = pullproc.onRequestBodyChunk(buffers[2]);
+                buffers[2].flip();
+                long headersLength = buffers[0].remaining() + buffers[1].remaining();
+                long contentWritten = buffers[2].remaining();
+                chan.checkWrite(headersLength + contentWritten,
+                                buffers,
+                                startbuf,
+                                nbufs);
+                while (!done) {
+                    buffers[2].clear();
+                    done = pullproc.onRequestBodyChunk(buffers[2]);
+                    buffers[2].flip();
+                    long len = buffers[2].remaining();
+                    if (contentWritten + len > contentLength) {
+                        break;
+                    }
+                    chan.checkWrite(len, buffers[2]);
+                    contentWritten += len;
+                }
+                if (contentWritten != contentLength) {
+                    throw new IOException("wrong content length");
+                }
+            }
+        } catch (IOException e) {
+            requestProc.onRequestError(e);
+            throw e;
+        }
+    }
+
+    private static final byte[] CRLF = {'\r', '\n'};
+    private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};
+
+    private ByteBuffer CRLF_BUFFER() {
+        return ByteBuffer.wrap(CRLF);
+    }
+
+    private ByteBuffer EMPTY_CHUNK_HEADER() {
+        return ByteBuffer.wrap(EMPTY_CHUNK_BYTES);
+    }
+
+    /* Returns a header for a particular chunk size */
+    private static ByteBuffer getHeader(int size){
+        String hexStr =  Integer.toHexString(size);
+        byte[] hexBytes = hexStr.getBytes(US_ASCII);
+        byte[] header = new byte[hexStr.length()+2];
+        System.arraycopy(hexBytes, 0, header, 0, hexBytes.length);
+        header[hexBytes.length] = CRLF[0];
+        header[hexBytes.length+1] = CRLF[1];
+        return ByteBuffer.wrap(header);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Http1Response.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.LongConsumer;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+
+/**
+ * Handles a HTTP/1.1 response in two blocking calls. readHeaders() and
+ * readBody(). There can be more than one of these per Http exchange.
+ */
+class Http1Response {
+
+    private ResponseContent content;
+    private final HttpRequestImpl request;
+    HttpResponseImpl response;
+    private final HttpConnection connection;
+    private ResponseHeaders headers;
+    private int responseCode;
+    private ByteBuffer buffer; // same buffer used for reading status line and headers
+    private final Http1Exchange exchange;
+    private final boolean redirecting; // redirecting
+    private boolean return2Cache; // return connection to cache when finished
+
+    Http1Response(HttpConnection conn, Http1Exchange exchange) {
+        this.request = exchange.request();
+        this.exchange = exchange;
+        this.connection = conn;
+        this.redirecting = false;
+        buffer = connection.getRemaining();
+    }
+
+    // called when the initial read should come from a buffer left
+    // over from a previous response.
+    void setBuffer(ByteBuffer buffer) {
+        this.buffer = buffer;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void readHeaders() throws IOException {
+        String statusline = readStatusLine();
+        if (statusline == null) {
+            if (Log.errors()) {
+                Log.logError("Connection closed. Retry");
+            }
+            connection.close();
+            // connection was closed
+            throw new IOException("Connection closed");
+        }
+        if (!statusline.startsWith("HTTP/1.")) {
+            throw new IOException("Invalid status line: " + statusline);
+        }
+        char c = statusline.charAt(7);
+        responseCode = Integer.parseInt(statusline.substring(9, 12));
+
+        headers = new ResponseHeaders(connection, buffer);
+        headers.initHeaders();
+        if (Log.headers()) {
+            logHeaders(headers);
+        }
+        response = new HttpResponseImpl(responseCode,
+                                        exchange.exchange,
+                                        headers,
+                                        null,
+                                        connection.sslParameters(),
+                                        HTTP_1_1,
+                                        connection);
+    }
+
+    private boolean finished;
+
+    synchronized void completed() {
+        finished = true;
+    }
+
+    synchronized boolean finished() {
+        return finished;
+    }
+
+    // Blocking flow controller implementation. Only works when a
+    // thread is dedicated to reading response body
+
+    static class FlowController implements LongConsumer {
+        long window ;
+
+        @Override
+        public synchronized void accept(long value) {
+            window += value;
+            notifyAll();
+        }
+
+        public synchronized void request(long value) throws InterruptedException {
+            while (window < value) {
+                wait();
+            }
+            window -= value;
+        }
+    }
+
+    FlowController flowController;
+
+    int fixupContentLen(int clen) {
+        if (request.method().equalsIgnoreCase("HEAD")) {
+            return 0;
+        }
+        if (clen == -1) {
+            if (headers.firstValue("Transfer-encoding").orElse("")
+                       .equalsIgnoreCase("chunked")) {
+                return -1;
+            }
+            return 0;
+        }
+        return clen;
+    }
+
+    private void returnBuffer(ByteBuffer buf) {
+        // not currently used, but will be when we change SSL to use fixed
+        // sized buffers and a single buffer pool for HttpClientImpl
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T readBody(java.net.http.HttpResponse.BodyProcessor<T> p,
+                          boolean return2Cache)
+        throws IOException
+    {
+        T body = null; // TODO: check null case below
+        this.return2Cache = return2Cache;
+        final java.net.http.HttpResponse.BodyProcessor<T> pusher = p;
+
+        int clen0 = headers.getContentLength();
+        final int clen = fixupContentLen(clen0);
+
+        flowController = new FlowController();
+
+        body = pusher.onResponseBodyStart(clen, headers, flowController);
+
+        ExecutorWrapper executor;
+        if (body == null) {
+            executor = ExecutorWrapper.callingThread();
+        } else {
+            executor = request.client().executorWrapper();
+        }
+
+        final ResponseHeaders h = headers;
+        if (body == null) {
+            content = new ResponseContent(connection,
+                                          clen,
+                                          h,
+                                          pusher,
+                                          flowController);
+            content.pushBody(headers.getResidue());
+            body = pusher.onResponseComplete();
+            completed();
+            onFinished();
+            return body;
+        } else {
+            executor.execute(() -> {
+                    try {
+                        content = new ResponseContent(connection,
+                                                      clen,
+                                                      h,
+                                                      pusher,
+                                                      flowController);
+                        content.pushBody(headers.getResidue());
+                        pusher.onResponseComplete();
+                        completed();
+                        onFinished();
+                    } catch (Throwable e) {
+                        pusher.onResponseError(e);
+                    }
+                },
+                () -> response.getAccessControlContext());
+        }
+        return body;
+    }
+
+    private void onFinished() {
+        connection.buffer = content.getResidue();
+        if (return2Cache) {
+            connection.returnToCache(headers);
+        }
+    }
+
+    private void logHeaders(ResponseHeaders headers) {
+        Map<String, List<String>> h = headers.mapInternal();
+        Set<String> keys = h.keySet();
+        Set<Map.Entry<String, List<String>>> entries = h.entrySet();
+        for (Map.Entry<String, List<String>> entry : entries) {
+            String key = entry.getKey();
+            StringBuilder sb = new StringBuilder();
+            sb.append(key).append(": ");
+            List<String> values = entry.getValue();
+            if (values != null) {
+                for (String value : values) {
+                    sb.append(value).append(' ');
+                }
+            }
+            Log.logHeaders(sb.toString());
+        }
+    }
+
+    HttpResponseImpl response() {
+        return response;
+    }
+
+    boolean redirecting() {
+        return redirecting;
+    }
+
+    HttpHeaders responseHeaders() {
+        return headers;
+    }
+
+    int responseCode() {
+        return responseCode;
+    }
+
+    static final char CR = '\r';
+    static final char LF = '\n';
+
+    private ByteBuffer getBuffer() throws IOException {
+        if (buffer == null || !buffer.hasRemaining()) {
+            buffer = connection.read();
+        }
+        return buffer;
+    }
+
+    ByteBuffer buffer() {
+        return buffer;
+    }
+
+    String readStatusLine() throws IOException {
+        boolean cr = false;
+        StringBuilder statusLine = new StringBuilder(128);
+        ByteBuffer b;
+        while ((b = getBuffer()) != null) {
+            byte[] buf = b.array();
+            int offset = b.position();
+            int len = b.limit() - offset;
+
+            for (int i = 0; i < len; i++) {
+                char c = (char) buf[i+offset];
+
+                if (cr) {
+                    if (c == LF) {
+                        b.position(i + 1 + offset);
+                        return statusLine.toString();
+                    } else {
+                        throw new IOException("invalid status line");
+                    }
+                }
+                if (c == CR) {
+                    cr = true;
+                } else {
+                    statusLine.append(c);
+                }
+            }
+            // unlikely, but possible, that multiple reads required
+            b.position(b.limit());
+        }
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Http2ClientImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2015, 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
+ */
+package java.net.http;
+
+class Http2ClientImpl {
+    Http2ClientImpl(HttpClientImpl t) {}
+    String getSettingsString() {return "";}
+    void debugPrint() {}
+    Http2Connection getConnectionFor(HttpRequestImpl r) {
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Http2Connection.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2015, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.CookieManager;
+import java.net.ProxySelector;
+import java.net.URI;
+import static java.net.http.Utils.BUFSIZE;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import static java.nio.channels.SelectionKey.OP_CONNECT;
+import static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
+import java.nio.channels.Selector;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.*;
+import java.security.NoSuchAlgorithmException;
+import java.util.ListIterator;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+
+class Http2Connection {
+    static CompletableFuture<Http2Connection> createAsync(
+        HttpConnection connection, Http2ClientImpl client2, Exchange exchange) {
+            return null;
+        }
+
+    Http2Connection(HttpConnection connection, Http2ClientImpl client2,
+            Exchange exchange) throws IOException, InterruptedException {
+    }
+
+    Stream getStream(int i) {return null;}
+    Stream createStream(Exchange ex) {return null;}
+    void putConnection() {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpClient.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.net.Authenticator;
+import java.net.CookieManager;
+import java.net.InetSocketAddress;
+import java.net.NetPermission;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+
+/**
+ * A container for configuration information common to multiple {@link
+ * HttpRequest}s. All requests are associated with, and created from a {@code
+ * HttpClient}.
+ *
+ * <p> {@code HttpClient}s are immutable and created from a builder returned
+ * from {@link HttpClient#create()}. Request builders that are associated with
+ * an application created client, are created by calling {@link #request(URI) }.
+ * It is also possible to create a request builder directly which is associated
+ * with the <i>default</i> {@code HttpClient} by calling {@link
+ * HttpRequest#create(URI)}.
+ *
+ * <p> The HTTP API functions asynchronously (using {@link
+ * java.util.concurrent.CompletableFuture}) and also in a simple synchronous
+ * mode, where all work may be done on the calling thread. In asynchronous mode,
+ * work is done on the threads supplied by the client's {@link
+ * java.util.concurrent.ExecutorService}.
+ *
+ * <p> <a name="defaultclient"></a> The <i>default</i> {@code HttpClient} is
+ * used whenever a request is created without specifying a client explicitly
+ * (by calling {@link HttpRequest#create(java.net.URI) HttpRequest.create}).
+ * There is only one static instance of this {@code HttpClient}. A reference to
+ * the default client can be obtained by calling {@link #getDefault() }. If a
+ * security manager is set, then a permission is required for this.
+ *
+ * <p> See {@link HttpRequest} for examples of usage of this API.
+ *
+ * @since 9
+ */
+public abstract class HttpClient {
+
+    HttpClient() {}
+
+    private static HttpClient defaultClient;
+
+    /**
+     * Creates a new {@code HttpClient} builder.
+     *
+     * @return a {@code HttpClient.Builder}
+     */
+    public static Builder  create() {
+        return new HttpClientBuilderImpl();
+    }
+
+    //public abstract void debugPrint();
+
+    /**
+     * Returns the default {@code HttpClient} that is used when a {@link
+     * HttpRequest} is created without specifying a client. If a security
+     * manager is set, then its {@code checkPermission} method is called with a
+     * {@link java.net.NetPermission} specifying the name "getDefaultHttpClient".
+     * If the caller does not possess this permission a {@code SecurityException}
+     * is thrown.
+     *
+     * @implNote Code running under a security manager can avoid the security
+     * manager check by creating a {@code HttpClient} explicitly.
+     *
+     * @return the default {@code HttpClient}
+     * @throws SecurityException if the caller does not have the required
+     *                           permission
+     */
+    public synchronized static HttpClient getDefault() {
+        Utils.checkNetPermission("getDefaultHttpClient");
+        if (defaultClient == null) {
+            Builder b = create();
+            defaultClient = b.executorService(Executors.newCachedThreadPool())
+                             .build();
+        }
+        return defaultClient;
+    }
+
+    /**
+     * Creates a {@code HttpRequest} builder associated with this client.
+     *
+     * @return a new builder
+     */
+    public abstract HttpRequest.Builder request();
+
+    /**
+     * Creates a {@code HttpRequest} builder associated with this client and
+     * using the given request URI.
+     *
+     * @param uri the request URI
+     * @return a new builder
+     */
+    public abstract HttpRequest.Builder request(URI uri);
+
+    /**
+     * A builder of immutable {@link HttpClient}s. {@code HttpClient.Builder}s
+     * are created by calling {@link HttpClient#create()}.
+     *
+     * <p> Each of the setter methods in this class modifies the state of the
+     * builder and returns <i>this</i> (ie. the same instance). The methods are
+     * not synchronized and should not be called from multiple threads without
+     * external synchronization.
+     *
+     * <p> {@link #build() } returns a new {@code HttpClient} each time it is
+     * called.
+     *
+     * @since 9
+     */
+    public abstract static class Builder {
+
+        Builder() {}
+
+        /**
+         * Sets a cookie manager.
+         *
+         * @param manager the CookieManager
+         * @return this builder
+         * @throws NullPointerException if {@code manager} is null
+         */
+        public abstract Builder cookieManager(CookieManager manager);
+
+        /**
+         * Sets an SSLContext. If a security manager is set, then the caller
+         * must have the {@link java.net.NetPermission NetPermission}
+         * ("setSSLContext")
+         *
+         * <p> The effect of not calling this method, is that a default {@link
+         * javax.net.ssl.SSLContext} is used, which is normally adequate for
+         * client applications that do not need to specify protocols, or require
+         * client authentication.
+         *
+         * @param sslContext the SSLContext
+         * @return this builder
+         * @throws NullPointerException if {@code sslContext} is null
+         * @throws SecurityException if a security manager is set and the
+         *                           caller does not have any required permission
+         */
+        public abstract Builder sslContext(SSLContext sslContext);
+
+        /**
+         * Sets an SSLParameters. If this method is not called, then a default
+         * set of parameters are used. The contents of the given object are
+         * copied. Some parameters which are used internally by the HTTP protocol
+         * implementation (such as application protocol list) should not be set
+         * by callers, as they are ignored.
+         *
+         * @param sslParameters the SSLParameters
+         * @return this builder
+         * @throws NullPointerException if {@code sslParameters} is null
+         */
+        public abstract Builder sslParameters(SSLParameters sslParameters);
+
+        /**
+         * Sets the ExecutorService to be used for sending and receiving
+         * asynchronous requests. If this method is not called, a default
+         * executor service is set, which is the one returned from {@link
+         * java.util.concurrent.Executors#newCachedThreadPool()
+         * Executors.newCachedThreadPool}.
+         *
+         * @param s the ExecutorService
+         * @return this builder
+         * @throws NullPointerException if {@code s} is null
+         */
+        public abstract Builder executorService(ExecutorService s);
+
+        /**
+         * Specifies whether requests will automatically follow redirects issued
+         * by the server. This setting can be overridden on each request. The
+         * default value for this setting is {@link Redirect#NEVER NEVER}
+         *
+         * @param policy the redirection policy
+         * @return this builder
+         * @throws NullPointerException if {@code policy} is null
+         */
+        public abstract Builder followRedirects(Redirect policy);
+
+        /**
+         * Requests a specific HTTP protocol version where possible. If not set,
+         * the version defaults to {@link HttpClient.Version#HTTP_1_1}. If
+         * {@link HttpClient.Version#HTTP_2} is set, then each request will
+         * attempt to upgrade to HTTP/2.  If the upgrade succeeds, then the
+         * response to this request will use HTTP/2 and all subsequent requests
+         * and responses to the same
+         * <a href="https://tools.ietf.org/html/rfc6454#section-4">origin server</a>
+         * will use HTTP/2. If the upgrade fails, then the response will be
+         * handled using HTTP/1.1
+         *
+         * <p>This setting can be over-ridden per request.
+         *
+         * @param version the requested HTTP protocol version
+         * @return this builder
+         * @throws NullPointerException if {@code version} is null
+         */
+        public abstract Builder version(HttpClient.Version version);
+
+        /**
+         * Sets the default priority for any HTTP/2 requests sent from this
+         * client. The value provided must be between {@code 1} and {@code 255}.
+         *
+         * @param priority the priority weighting
+         * @return this builder
+         * @throws IllegalArgumentException if the given priority is out of range
+         */
+        public abstract Builder priority(int priority);
+
+        /**
+         * Enables pipelining mode for HTTP/1.1 requests sent through this
+         * client. When pipelining is enabled requests to the same destination
+         * are sent over existing TCP connections that may already have requests
+         * outstanding. This reduces the number of connections, but may have
+         * a performance impact since responses must be delivered in the same
+         * order that they were sent. By default, pipelining is disabled.
+         *
+         * @param enable {@code true} enables pipelining
+         * @return this builder
+         * @throws UnsupportedOperationException if pipelining mode is not
+         *                                       supported by this implementation
+         */
+        public abstract Builder pipelining(boolean enable);
+
+        /**
+         * Sets a {@link java.net.ProxySelector} for this client. If no selector
+         * is set, then no proxies are used. If a {@code null} parameter is
+         * given then the system wide default proxy selector is used.
+         *
+         * @implNote {@link java.net.ProxySelector#of(InetSocketAddress)}
+         * provides a ProxySelector which uses one proxy for all requests.
+         *
+         * @param selector the ProxySelector
+         * @return this builder
+         */
+        public abstract Builder proxy(ProxySelector selector);
+
+        /**
+         * Sets an authenticator to use for HTTP authentication.
+         *
+         * @param a the Authenticator
+         * @return this builder
+         */
+        public abstract Builder authenticator(Authenticator a);
+
+        /**
+         * Returns a {@link HttpClient} built from the current state of this
+         * builder.
+         *
+         * @return this builder
+         */
+        public abstract HttpClient build();
+    }
+
+
+    /**
+     * Returns an {@code Optional} which contains this client's {@link
+     * CookieManager}. If no CookieManager was set in this client's builder,
+     * then the {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this client's CookieManager
+     */
+    public abstract Optional<CookieManager> cookieManager();
+
+    /**
+     * Returns the follow-redirects setting for this client. The default value
+     * for this setting is {@link HttpClient.Redirect#NEVER}
+     *
+     * @return this client's follow redirects setting
+     */
+    public abstract Redirect followRedirects();
+
+    /**
+     * Returns an {@code Optional} containing the ProxySelector for this client.
+     * If no proxy is set then the {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this client's proxy selector
+     */
+    public abstract Optional<ProxySelector> proxy();
+
+    /**
+     * Returns the SSLContext, if one was set on this client. If a security
+     * manager is set then then caller must then the caller must have the
+     * {@link java.net.NetPermission NetPermission}("getSSLContext") permission.
+     * If no SSLContext was set, then the default context is returned.
+     *
+     * @return this client's SSLContext
+     */
+    public abstract SSLContext sslContext();
+
+    /**
+     * Returns an {@code Optional} containing the {@link SSLParameters} set on
+     * this client. If no {@code SSLParameters} were set in the client's builder,
+     * then the {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this client's SSLParameters
+     */
+    public abstract Optional<SSLParameters> sslParameters();
+
+    /**
+     * Returns an {@code Optional} containing the {@link Authenticator} set on
+     * this client. If no {@code Authenticator} was set in the client's builder,
+     * then the {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this client's Authenticator
+     */
+    public abstract Optional<Authenticator> authenticator();
+
+    /**
+     * Returns the HTTP protocol version requested for this client. The default
+     * value is {@link HttpClient.Version#HTTP_1_1}
+     *
+     * @return the HTTP protocol version requested
+     */
+    public abstract HttpClient.Version version();
+
+    /**
+     * Returns whether this client supports HTTP/1.1 pipelining.
+     *
+     * @return whether pipelining allowed
+     */
+    public abstract boolean pipelining();
+
+    /**
+     * Returns the {@code ExecutorService} set on this client. If an {@code
+     * ExecutorService} was not set on the client's builder, then a default
+     * object is returned. The default ExecutorService is created independently
+     * for each client.
+     *
+     * @return this client's ExecutorService
+     */
+    public abstract ExecutorService executorService();
+
+    /**
+     * The HTTP protocol version.
+     *
+     * @since 9
+     */
+    public static enum Version {
+
+        /**
+         * HTTP version 1.1
+         */
+        HTTP_1_1,
+
+        /**
+         * HTTP version 2
+         */
+        HTTP_2
+    }
+
+    /**
+     * Defines automatic redirection policy. This is checked whenever a 3XX
+     * response code is received. If redirection does not happen automatically
+     * then the response is returned to the user, where it can be handled
+     * manually.
+     *
+     * <p> {@code Redirect} policy is set via the {@link
+     * HttpClient.Builder#followRedirects(HttpClient.Redirect)} method.
+     *
+     * @since 9
+     */
+    public static enum Redirect {
+
+        /**
+         * Never redirect.
+         */
+        NEVER,
+
+        /**
+         * Always redirect.
+         */
+        ALWAYS,
+
+        /**
+         * Redirect to same protocol only. Redirection may occur from HTTP URLs
+         * to other HTTP URLs, and from HTTPS URLs to other HTTPS URLs.
+         */
+        SAME_PROTOCOL,
+
+        /**
+         * Redirect always except from HTTPS URLs to HTTP URLs.
+         */
+        SECURE
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpClientBuilderImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.net.Authenticator;
+import java.net.CookieManager;
+import java.net.ProxySelector;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+
+class HttpClientBuilderImpl extends HttpClient.Builder {
+
+    CookieManager cookieManager;
+    HttpClient.Redirect followRedirects;
+    ProxySelector proxy;
+    Authenticator authenticator;
+    HttpClient.Version version = HttpClient.Version.HTTP_1_1;
+    ExecutorService executor;
+    // Security parameters
+    SSLContext sslContext;
+    SSLParameters sslParams;
+    int priority = -1;
+
+    @Override
+    public HttpClientBuilderImpl cookieManager(CookieManager manager) {
+        Objects.requireNonNull(manager);
+        this.cookieManager = manager;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl sslContext(SSLContext sslContext) {
+        Objects.requireNonNull(sslContext);
+        Utils.checkNetPermission("setSSLContext");
+        this.sslContext = sslContext;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl sslParameters(SSLParameters sslParameters) {
+        Objects.requireNonNull(sslParameters);
+        this.sslParams = sslParameters;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl executorService(ExecutorService s) {
+        Objects.requireNonNull(s);
+        this.executor = s;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl followRedirects(HttpClient.Redirect policy) {
+        Objects.requireNonNull(policy);
+        this.followRedirects = policy;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl version(HttpClient.Version version) {
+        Objects.requireNonNull(version);
+        this.version = version;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl priority(int priority) {
+        if (priority < 1 || priority > 255) {
+            throw new IllegalArgumentException("priority must be between 1 and 255");
+        }
+        this.priority = priority;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl pipelining(boolean enable) {
+        //To change body of generated methods, choose Tools | Templates.
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl proxy(ProxySelector proxy) {
+        Objects.requireNonNull(proxy);
+        this.proxy = proxy;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl authenticator(Authenticator a) {
+        Objects.requireNonNull(a);
+        this.authenticator = a;
+        return this;
+    }
+
+    @Override
+    public HttpClient build() {
+        return HttpClientImpl.create(this);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpClientImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.CookieManager;
+import java.net.ProxySelector;
+import java.net.URI;
+import static java.net.http.Utils.BUFSIZE;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import static java.nio.channels.SelectionKey.OP_CONNECT;
+import static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
+import java.nio.channels.Selector;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.security.NoSuchAlgorithmException;
+import java.util.ListIterator;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+
+/**
+ * Client implementation. Contains all configuration information and also
+ * the selector manager thread which allows async events to be registered
+ * and delivered when they occur. See AsyncEvent.
+ */
+class HttpClientImpl extends HttpClient implements BufferHandler {
+
+    private final CookieManager cookieManager;
+    private final Redirect followRedirects;
+    private final ProxySelector proxySelector;
+    private final Authenticator authenticator;
+    private final Version version;
+    private boolean pipelining = false;
+    private final ConnectionPool connections;
+    private final ExecutorWrapper executor;
+    // Security parameters
+    private final SSLContext sslContext;
+    private final SSLParameters sslParams;
+    private final SelectorManager selmgr;
+    private final FilterFactory filters;
+    private final Http2ClientImpl client2;
+    private static final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
+    private final LinkedList<TimeoutEvent> timeouts;
+
+    //@Override
+    void debugPrint() {
+        selmgr.debugPrint();
+        client2.debugPrint();
+    }
+
+    public static HttpClientImpl create(HttpClientBuilderImpl builder) {
+        HttpClientImpl impl = new HttpClientImpl(builder);
+        impl.start();
+        return impl;
+    }
+
+    private HttpClientImpl(HttpClientBuilderImpl builder) {
+        if (builder.sslContext == null) {
+            try {
+                sslContext = SSLContext.getDefault();
+            } catch (NoSuchAlgorithmException ex) {
+                throw new InternalError(ex);
+            }
+        } else {
+            sslContext = builder.sslContext;
+        }
+        ExecutorService ex = builder.executor;
+        if (ex == null) {
+            ex = Executors.newCachedThreadPool((r) -> {
+                Thread t = defaultFactory.newThread(r);
+                t.setDaemon(true);
+                return t;
+            });
+        } else {
+            ex = builder.executor;
+        }
+        client2 = new Http2ClientImpl(this);
+        executor = ExecutorWrapper.wrap(ex);
+        cookieManager = builder.cookieManager;
+        followRedirects = builder.followRedirects == null ?
+                Redirect.NEVER : builder.followRedirects;
+        this.proxySelector = builder.proxy;
+        authenticator = builder.authenticator;
+        version = builder.version;
+        sslParams = builder.sslParams;
+        connections = new ConnectionPool();
+        connections.start();
+        timeouts = new LinkedList<>();
+        try {
+            selmgr = new SelectorManager();
+        } catch (IOException e) {
+            // unlikely
+            throw new InternalError(e);
+        }
+        selmgr.setDaemon(true);
+        selmgr.setName("HttpSelector");
+        filters = new FilterFactory();
+        initFilters();
+    }
+
+    private void start() {
+        selmgr.start();
+    }
+
+    /**
+     * Wait for activity on given exchange (assuming blocking = false).
+     * It's a no-op if blocking = true. In particular, the following occurs
+     * in the SelectorManager thread.
+     *
+     *  1) mark the connection non-blocking
+     *  2) add to selector
+     *  3) If selector fires for this exchange then
+     *  4)   - mark connection as blocking
+     *  5)   - call AsyncEvent.handle()
+     *
+     *  If exchange needs to block again, then call registerEvent() again
+     */
+    void registerEvent(AsyncEvent exchange) throws IOException {
+        selmgr.register(exchange);
+    }
+
+    Http2ClientImpl client2() {
+        return client2;
+    }
+
+    LinkedList<ByteBuffer> freelist = new LinkedList<>();
+
+    @Override
+    public synchronized ByteBuffer getBuffer() {
+        if (freelist.isEmpty()) {
+            return ByteBuffer.allocate(BUFSIZE);
+        }
+        return freelist.removeFirst();
+    }
+
+    @Override
+    public synchronized void returnBuffer(ByteBuffer buffer) {
+        buffer.clear();
+        freelist.add(buffer);
+    }
+
+
+    // Main loop for this client's selector
+
+    class SelectorManager extends Thread {
+
+        final Selector selector;
+        boolean closed;
+
+        final List<AsyncEvent> readyList;
+        final List<AsyncEvent> registrations;
+
+        List<AsyncEvent> debugList;
+
+        SelectorManager() throws IOException {
+            readyList = new LinkedList<>();
+            registrations = new LinkedList<>();
+            debugList = new LinkedList<>();
+            selector = Selector.open();
+        }
+
+        // This returns immediately. So caller not allowed to send/receive
+        // on connection.
+
+        synchronized void register(AsyncEvent e) throws IOException {
+            registrations.add(e);
+            selector.wakeup();
+        }
+
+        void wakeupSelector() {
+            selector.wakeup();
+        }
+
+        synchronized void shutdown() {
+            closed = true;
+            try {
+                selector.close();
+            } catch (IOException e) {}
+        }
+
+        private List<AsyncEvent> copy(List<AsyncEvent> list) {
+            LinkedList<AsyncEvent> c = new LinkedList<>();
+            for (AsyncEvent e : list) {
+                c.add(e);
+            }
+            return c;
+        }
+
+        synchronized void debugPrint() {
+            System.err.println("Selecting on:");
+            for (AsyncEvent e : debugList) {
+                System.err.println(opvals(e.interestOps()));
+            }
+        }
+
+        String opvals(int i) {
+            StringBuilder sb = new StringBuilder();
+            if ((i & OP_READ) != 0)
+                sb.append("OP_READ ");
+            if ((i & OP_CONNECT) != 0)
+                sb.append("OP_CONNECT ");
+            if ((i & OP_WRITE) != 0)
+                sb.append("OP_WRITE ");
+            return sb.toString();
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (true) {
+                    synchronized (this) {
+                        debugList = copy(registrations);
+                        for (AsyncEvent exchange : registrations) {
+                            SelectableChannel c = exchange.channel();
+                            try {
+                                c.configureBlocking(false);
+                                c.register(selector,
+                                           exchange.interestOps(),
+                                           exchange);
+                            } catch (IOException e) {
+                                Log.logError("HttpClientImpl: " + e);
+                                c.close();
+                                // let the exchange deal with it
+                                handleEvent(exchange);
+                            }
+                        }
+                        registrations.clear();
+                    }
+                    long timeval = getTimeoutValue();
+                    long now = System.currentTimeMillis();
+                    int n = selector.select(timeval);
+                    if (n == 0) {
+                        signalTimeouts(now);
+                        continue;
+                    }
+                    Set<SelectionKey> keys = selector.selectedKeys();
+
+                    for (SelectionKey key : keys) {
+                        if (key.isReadable() || key.isConnectable() || key.isWritable()) {
+                            key.cancel();
+                            AsyncEvent exchange = (AsyncEvent) key.attachment();
+                            readyList.add(exchange);
+                        }
+                    }
+                    selector.selectNow(); // complete cancellation
+                    selector.selectedKeys().clear();
+
+                    for (AsyncEvent exchange : readyList) {
+                        if (exchange instanceof AsyncEvent.Blocking) {
+                            exchange.channel().configureBlocking(true);
+                        } else {
+                            assert exchange instanceof AsyncEvent.NonBlocking;
+                        }
+                        executor.synchronize();
+                        handleEvent(exchange); // will be delegated to executor
+                    }
+                    readyList.clear();
+                }
+            } catch (Throwable e) {
+                if (!closed) {
+                    System.err.println("HttpClientImpl terminating on error");
+                    // This terminates thread. So, better just print stack trace
+                    String err = Utils.stackTrace(e);
+                    Log.logError("HttpClientImpl: fatal error: " + err);
+                }
+            }
+        }
+
+        void handleEvent(AsyncEvent e) {
+            if (closed) {
+                e.abort();
+            } else {
+                e.handle();
+            }
+        }
+    }
+
+    /**
+     * Creates a HttpRequest associated with this group.
+     *
+     * @throws IllegalStateException if the group has been stopped
+     */
+    @Override
+    public HttpRequestBuilderImpl request() {
+        return new HttpRequestBuilderImpl(this, null);
+    }
+
+    /**
+     * Creates a HttpRequest associated with this group.
+     *
+     * @throws IllegalStateException if the group has been stopped
+     */
+    @Override
+    public HttpRequestBuilderImpl request(URI uri) {
+        return new HttpRequestBuilderImpl(this, uri);
+    }
+
+    @Override
+    public SSLContext sslContext() {
+        Utils.checkNetPermission("getSSLContext");
+        return sslContext;
+    }
+
+    @Override
+    public Optional<SSLParameters> sslParameters() {
+        return Optional.ofNullable(sslParams);
+    }
+
+    @Override
+    public Optional<Authenticator> authenticator() {
+        return Optional.ofNullable(authenticator);
+    }
+
+    @Override
+    public ExecutorService executorService() {
+        return executor.userExecutor();
+    }
+
+    ExecutorWrapper executorWrapper() {
+        return executor;
+    }
+
+    @Override
+    public boolean pipelining() {
+        return this.pipelining;
+    }
+
+    ConnectionPool connectionPool() {
+        return connections;
+    }
+
+    @Override
+    public Redirect followRedirects() {
+        return followRedirects;
+    }
+
+
+    @Override
+    public Optional<CookieManager> cookieManager() {
+        return Optional.ofNullable(cookieManager);
+    }
+
+    @Override
+    public Optional<ProxySelector> proxy() {
+        return Optional.ofNullable(this.proxySelector);
+    }
+
+    @Override
+    public Version version() {
+        return version;
+    }
+
+    //private final HashMap<String, Boolean> http2NotSupported = new HashMap<>();
+
+    boolean getHttp2Allowed() {
+        return version.equals(Version.HTTP_2);
+    }
+
+    //void setHttp2NotSupported(String host) {
+        //http2NotSupported.put(host, false);
+    //}
+
+    final void initFilters() {
+        addFilter(AuthenticationFilter.class);
+        addFilter(RedirectFilter.class);
+    }
+
+    final void addFilter(Class<? extends HeaderFilter> f) {
+        filters.addFilter(f);
+    }
+
+    final List<HeaderFilter> filterChain() {
+        return filters.getFilterChain();
+    }
+
+    // Timer controls. Timers are implemented through timed Selector.select()
+    // calls.
+    synchronized void registerTimer(TimeoutEvent event) {
+        long elapse = event.timevalMillis();
+        ListIterator<TimeoutEvent> iter = timeouts.listIterator();
+        long listval = 0;
+        event.delta = event.timeval; // in case list empty
+        TimeoutEvent next;
+        while (iter.hasNext()) {
+            next = iter.next();
+            listval += next.delta;
+            if (elapse < listval) {
+                listval -= next.delta;
+                event.delta = elapse - listval;
+                next.delta -= event.delta;
+                iter.previous();
+                break;
+            } else if (!iter.hasNext()) {
+                event.delta = event.timeval - listval ;
+            }
+        }
+        iter.add(event);
+        //debugPrintList("register");
+        selmgr.wakeupSelector();
+    }
+
+    void debugPrintList(String s) {
+        System.err.printf("%s: {", s);
+        for (TimeoutEvent e : timeouts) {
+            System.err.printf("(%d,%d) ", e.delta, e.timeval);
+        }
+        System.err.println("}");
+    }
+
+    synchronized void signalTimeouts(long then) {
+        if (timeouts.isEmpty()) {
+            return;
+        }
+        long now = System.currentTimeMillis();
+        long duration = now - then;
+        ListIterator<TimeoutEvent> iter = timeouts.listIterator();
+        TimeoutEvent event = iter.next();
+        long delta = event.delta;
+        if (duration < delta) {
+            event.delta -= duration;
+            return;
+        }
+        event.handle();
+        iter.remove();
+        while (iter.hasNext()) {
+            event = iter.next();
+            if (event.delta == 0) {
+                event.handle();
+                iter.remove();
+            } else {
+                event.delta += delta;
+                break;
+            }
+        }
+        //debugPrintList("signalTimeouts");
+    }
+
+    synchronized void cancelTimer(TimeoutEvent event) {
+        ListIterator<TimeoutEvent> iter = timeouts.listIterator();
+        while (iter.hasNext()) {
+            TimeoutEvent ev = iter.next();
+            if (event == ev) {
+                if (iter.hasNext()) {
+                    // adjust
+                    TimeoutEvent next = iter.next();
+                    next.delta += ev.delta;
+                    iter.previous();
+                }
+                iter.remove();
+            }
+        }
+    }
+
+    // used for the connection window
+    int getReceiveBufferSize() {
+        return Utils.getIntegerNetProperty(
+                "sun.net.httpclient.connectionWindowSize", 256 * 1024
+        );
+    }
+
+    // returns 0 meaning block forever, or a number of millis to block for
+    synchronized long getTimeoutValue() {
+        if (timeouts.isEmpty()) {
+            return 0;
+        } else {
+            return timeouts.get(0).delta;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpConnection.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CompletableFuture;
+import javax.net.ssl.SSLParameters;
+
+/**
+ * Wraps socket channel layer and takes care of SSL also.
+ *
+ * Subtypes are:
+ *      PlainHttpConnection: regular direct TCP connection to server
+ *      PlainProxyConnection: plain text proxy connection
+ *      PlainTunnelingConnection: opens plain text (CONNECT) tunnel to server
+ *      SSLConnection: TLS channel direct to server
+ *      SSLTunnelConnection: TLS channel via (CONNECT) proxy tunnel
+ */
+abstract class HttpConnection implements BufferHandler {
+
+    // address we are connected to. Could be a server or a proxy
+    final InetSocketAddress address;
+    final HttpClientImpl client;
+    protected volatile ByteBuffer buffer;
+
+    HttpConnection(InetSocketAddress address, HttpClientImpl client) {
+        this.address = address;
+        this.client = client;
+    }
+
+    /**
+     * Public API to this class. addr is the ultimate destination. Any proxies
+     * etc are figured out from the request. Returns an instance of one of the
+     * following
+     *      PlainHttpConnection
+     *      PlainTunnelingConnection
+     *      SSLConnection
+     *      SSLTunnelConnection
+     *
+     * When object returned, connect() or connectAsync() must be called, which
+     * when it returns/completes, the connection is usable for requests.
+     */
+    public static HttpConnection getConnection(InetSocketAddress addr,
+                                               HttpRequestImpl request) {
+        return getConnectionImpl(addr, request);
+    }
+
+    public abstract void connect() throws IOException, InterruptedException;
+
+    public abstract CompletableFuture<Void> connectAsync();
+
+    /**
+     * Returns whether this connection is connected to its destination
+     */
+    abstract boolean connected();
+
+    abstract boolean isSecure();
+
+    abstract boolean isProxied();
+
+    /**
+     * Completes when the first byte of the response is available to be read.
+     */
+    abstract CompletableFuture<Void> whenReceivingResponse();
+
+    // must be called before reading any data off connection
+    // at beginning of response.
+    ByteBuffer getRemaining() {
+        ByteBuffer b = buffer;
+        buffer = null;
+        return b;
+    }
+
+    final boolean isOpen() {
+        return channel().isOpen();
+    }
+
+    /* Returns either a plain HTTP connection or a plain tunnelling connection
+     * for proxied websockets */
+    private static HttpConnection getPlainConnection(InetSocketAddress addr,
+                                                     InetSocketAddress proxy,
+                                                     HttpRequestImpl request) {
+        HttpClientImpl client = request.client();
+
+        if (request.isWebSocket() && proxy != null) {
+            return new PlainTunnelingConnection(addr,
+                                                proxy,
+                                                client,
+                                                request.getAccessControlContext());
+        } else {
+            if (proxy == null) {
+                return new PlainHttpConnection(addr, client);
+            } else {
+                return new PlainProxyConnection(proxy, client);
+            }
+        }
+    }
+
+    private static HttpConnection getSSLConnection(InetSocketAddress addr,
+                                                   InetSocketAddress proxy,
+                                                   HttpRequestImpl request,
+                                                   String[] alpn) {
+        HttpClientImpl client = request.client();
+        if (proxy != null) {
+            return new SSLTunnelConnection(addr,
+                                           client,
+                                           proxy,
+                                           request.getAccessControlContext());
+        } else {
+            return new SSLConnection(addr, client, alpn);
+        }
+    }
+
+    /**
+     * Main factory method.   Gets a HttpConnection, either cached or new if
+     * none available.
+     */
+    private static HttpConnection getConnectionImpl(InetSocketAddress addr,
+                                                    HttpRequestImpl request) {
+        HttpConnection c;
+        HttpClientImpl client = request.client();
+        InetSocketAddress proxy = request.proxy();
+        boolean secure = request.secure();
+        ConnectionPool pool = client.connectionPool();
+        String[] alpn =  null;
+
+        if (secure && request.requestHttp2()) {
+            alpn = new String[1];
+            alpn[0] = "h2";
+        }
+
+        if (!secure) {
+            c = pool.getConnection(false, addr, proxy);
+            if (c != null) {
+                return c;
+            } else {
+                return getPlainConnection(addr, proxy, request);
+            }
+        } else {
+            c = pool.getConnection(true, addr, proxy);
+            if (c != null) {
+                return c;
+            } else {
+                return getSSLConnection(addr, proxy, request, alpn);
+            }
+        }
+    }
+
+    void returnToCache(HttpHeaders hdrs) {
+        if (hdrs == null) {
+            // the connection was closed by server
+            close();
+            return;
+        }
+        if (!isOpen()) {
+            return;
+        }
+        ConnectionPool pool = client.connectionPool();
+        boolean keepAlive = hdrs.firstValue("Connection")
+                .map((s) -> !s.equalsIgnoreCase("close"))
+                .orElse(true);
+
+        if (keepAlive) {
+            pool.returnToPool(this);
+        } else {
+            close();
+        }
+    }
+
+    /**
+     * Also check that the number of bytes written is what was expected. This
+     * could be different if the buffer is user-supplied and its internal
+     * pointers were manipulated in a race condition.
+     */
+    final void checkWrite(long expected, ByteBuffer buffer) throws IOException {
+        long written = write(buffer);
+        if (written != expected) {
+            throw new IOException("incorrect number of bytes written");
+        }
+    }
+
+    final void checkWrite(long expected,
+                          ByteBuffer[] buffers,
+                          int start,
+                          int length)
+        throws IOException
+    {
+        long written = write(buffers, start, length);
+        if (written != expected) {
+            throw new IOException("incorrect number of bytes written");
+        }
+    }
+
+    abstract SocketChannel channel();
+
+    final InetSocketAddress address() {
+        return address;
+    }
+
+    void configureBlocking(boolean mode) throws IOException {
+        channel().configureBlocking(mode);
+    }
+
+    abstract ConnectionPool.CacheKey cacheKey();
+
+    /*
+    static PrintStream ps;
+
+    static {
+        try {
+            String propval = Utils.getNetProperty("java.net.httpclient.showData");
+            if (propval != null && propval.equalsIgnoreCase("true")) {
+                ps = new PrintStream(new FileOutputStream("/tmp/httplog.txt"), false);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    synchronized final void debugPrint(String s, ByteBuffer b) {
+        ByteBuffer[] bufs = new ByteBuffer[1];
+        bufs[0] = b;
+        debugPrint(s, bufs, 0, 1);
+    }
+
+    synchronized final void debugPrint(String s,
+                                       ByteBuffer[] bufs,
+                                       int start,
+                                       int number) {
+        if (ps == null) {
+            return;
+        }
+
+        ps.printf("\n%s:\n", s);
+
+        for (int i=start; i<start+number; i++) {
+            ByteBuffer b = bufs[i].duplicate();
+            while (b.hasRemaining()) {
+                int c = b.get();
+                if (c == 10) {
+                    ps.printf("LF \n");
+                } else if (c == 13) {
+                    ps.printf(" CR ");
+                } else if (c == 0x20) {
+                    ps.printf("_");
+                } else if (c > 0x20 && c <= 0x7F) {
+                    ps.printf("%c", (char)c);
+                } else {
+                    ps.printf("0x%02x ", c);
+                }
+            }
+        }
+        ps.printf("\n---------------------\n");
+    }
+
+    */
+
+    // overridden in SSL only
+    SSLParameters sslParameters() {
+        return null;
+    }
+
+    // Methods to be implemented for Plain TCP and SSL
+
+    abstract long write(ByteBuffer[] buffers, int start, int number)
+        throws IOException;
+
+    abstract long write(ByteBuffer buffer) throws IOException;
+
+    /**
+     * Closes this connection, by returning the socket to its connection pool.
+     */
+    abstract void close();
+
+    /**
+     * Returns a ByteBuffer with data, or null if EOF.
+     */
+    final ByteBuffer read() throws IOException {
+        return read(-1);
+    }
+
+    /**
+     * Puts position to limit and limit to capacity so we can resume reading
+     * into this buffer, but if required > 0 then limit may be reduced so that
+     * no more than required bytes are read next time.
+     */
+    static void resumeChannelRead(ByteBuffer buf, int required) {
+        int limit = buf.limit();
+        buf.position(limit);
+        int capacity = buf.capacity() - limit;
+        if (required > 0 && required < capacity) {
+            buf.limit(limit + required);
+        } else {
+            buf.limit(buf.capacity());
+        }
+    }
+
+    /**
+     * Blocks ands return requested amount.
+     */
+    final ByteBuffer read(int length) throws IOException {
+        if (length <= 0) {
+            buffer = readImpl(length);
+            return buffer;
+        }
+        buffer = readImpl(length);
+        int required = length - buffer.remaining();
+        while (buffer.remaining() < length) {
+            resumeChannelRead(buffer, required);
+            int n = readImpl(buffer);
+            required -= n;
+        }
+        return buffer;
+    }
+
+    final int read(ByteBuffer buffer) throws IOException {
+        int n = readImpl(buffer);
+        return n;
+    }
+
+    /** Reads up to length bytes. */
+    protected abstract ByteBuffer readImpl(int length) throws IOException;
+
+    /** Reads as much as possible into given buffer and returns amount read. */
+    protected abstract int readImpl(ByteBuffer buffer) throws IOException;
+
+    @Override
+    public String toString() {
+        return "HttpConnection: " + channel().toString();
+    }
+
+    @Override
+    public final ByteBuffer getBuffer() {
+        return client.getBuffer();
+    }
+
+    @Override
+    public final void returnBuffer(ByteBuffer buffer) {
+        client.returnBuffer(buffer);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpHeaders.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * A read-only view of a set of received HTTP headers.
+ *
+ * @since 9
+ */
+public interface HttpHeaders {
+
+    /**
+     * Returns an {@link java.util.Optional} containing the first value of the
+     * given named (and possibly multi-valued) header. If the header is not
+     * present, then the returned {@code Optional} is empty.
+     *
+     * @param name the header name
+     * @return an {@code Optional<String>} for the first named value
+     */
+    public Optional<String> firstValue(String name);
+
+    /**
+     * Returns an {@link java.util.Optional} containing the first value of the
+     * named header field as an {@literal Optional<Long>}. If the header is not
+     * present, then the Optional is empty. If the header is present but
+     * contains a value that does not parse as a {@code Long} value, then an
+     * exception is thrown.
+     *
+     * @param name the header name
+     * @return  an {@code Optional<Long>}
+     * @throws NumberFormatException if a value is found, but does not parse as
+     *                               a Long
+     */
+    public Optional<Long> firstValueAsLong(String name);
+
+    /**
+     * Returns an unmodifiable List of all of the values of the given named
+     * header. Always returns a List, which may be empty if the header is not
+     * present.
+     *
+     * @param name the header name
+     * @return a List of String values
+     */
+    public List<String> allValues(String name);
+
+    /**
+     * Returns an unmodifiable multi Map view of this HttpHeaders. This
+     * interface should only be used when it is required to iterate over the
+     * entire set of headers.
+     *
+     * @return the Map
+     */
+    public Map<String,List<String>> map();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpHeaders1.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+public interface HttpHeaders1 extends HttpHeaders {
+    public void makeUnmodifiable();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpHeadersImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Implementation of HttpHeaders.
+ */
+class HttpHeadersImpl implements HttpHeaders1 {
+
+    private final HashMap<String,List<String>> headers;
+    private boolean isUnmodifiable = false;
+
+    public HttpHeadersImpl() {
+        headers = new HashMap<>();
+    }
+
+    /**
+     * Replace all List<String> in headers with unmodifiable Lists. Call
+     * this only after all headers are added. The headers HashMap
+     * is wrapped with an unmodifiable HashMap in map()
+     */
+    @Override
+    public void makeUnmodifiable() {
+        if (isUnmodifiable)
+            return;
+
+        Set<String> keys = new HashSet<>(headers.keySet());
+        for (String key : keys) {
+            List<String> values = headers.remove(key);
+            if (values != null) {
+                headers.put(key, Collections.unmodifiableList(values));
+            }
+        }
+        isUnmodifiable = true;
+    }
+
+    @Override
+    public Optional<String> firstValue(String name) {
+        List<String> l = headers.get(name);
+        return Optional.ofNullable(l == null ? null : l.get(0));
+    }
+
+    @Override
+    public List<String> allValues(String name) {
+        return headers.get(name);
+    }
+
+    @Override
+    public Map<String, List<String>> map() {
+        return Collections.unmodifiableMap(headers);
+    }
+
+    Map<String, List<String>> directMap() {
+        return headers;
+    }
+
+    // package private mutators
+
+    public HttpHeadersImpl deepCopy() {
+        HttpHeadersImpl h1 = new HttpHeadersImpl();
+        HashMap<String,List<String>> headers1 = h1.headers;
+        Set<String> keys = headers.keySet();
+        for (String key : keys) {
+            List<String> vals = headers.get(key);
+            LinkedList<String> vals1 = new LinkedList<>(vals);
+            headers1.put(key, vals1);
+        }
+        return h1;
+    }
+
+    private List<String> getOrCreate(String name) {
+        List<String> l = headers.get(name);
+        if (l == null) {
+            l = new LinkedList<>();
+            headers.put(name, l);
+        }
+        return l;
+    }
+
+    void addHeader(String name, String value) {
+        List<String> l = getOrCreate(name);
+        l.add(value);
+    }
+
+    void setHeader(String name, String value) {
+        List<String> l = getOrCreate(name);
+        l.clear();
+        l.add(value);
+    }
+
+    @Override
+    public Optional<Long> firstValueAsLong(String name) {
+        List<String> l = headers.get(name);
+        if (l == null) {
+            return Optional.ofNullable(null);
+        } else {
+            String v = l.get(0);
+            Long lv = Long.parseLong(v);
+            return Optional.of(lv);
+        }
+    }
+
+    void clear() {
+        headers.clear();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpRedirectImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.net.*;
+
+interface HttpRedirectImpl {
+
+    static HttpRedirectImpl getRedirects(java.net.http.HttpClient.Redirect redir) {
+        switch (redir) {
+            case NEVER:
+                return HttpRedirectImpl.NEVER;
+            case ALWAYS:
+                return HttpRedirectImpl.ALWAYS;
+            case SECURE:
+                return HttpRedirectImpl.SECURE;
+            case SAME_PROTOCOL:
+                return HttpRedirectImpl.SAME_PROTOCOL;
+        }
+        return HttpRedirectImpl.NEVER;
+    }
+
+    static HttpClient.Redirect getRedirects(HttpRedirectImpl redir) {
+        if (redir == HttpRedirectImpl.NEVER) {
+            return HttpClient.Redirect.NEVER;
+        } else if (redir == HttpRedirectImpl.ALWAYS) {
+            return HttpClient.Redirect.ALWAYS;
+        } else if (redir == HttpRedirectImpl.SECURE) {
+            return HttpClient.Redirect.SECURE;
+        } else {
+            return HttpClient.Redirect.SAME_PROTOCOL;
+        }
+    }
+
+    /**
+     * Called to determine whether the given intermediate response
+     * with a redirection response code should be redirected. The target URI
+     * can be obtained from the "Location" header in the given response object.
+     *
+     * @param rsp the response from the redirected resource
+     * @return {@code true} if the redirect should be attempted automatically
+     * or {@code false} if not.
+     */
+    boolean redirect(HttpResponse rsp);
+
+    /**
+     * Never redirect.
+     */
+    static HttpRedirectImpl NEVER = (HttpResponse rsp) -> false;
+
+    /**
+     * Always redirect.
+     */
+    static HttpRedirectImpl ALWAYS = (HttpResponse rsp) -> true;
+
+    /**
+     * Redirect to same protocol only. Redirection may occur from HTTP URLs to
+     * other THHP URLs and from HTTPS URLs to other HTTPS URLs.
+     */
+    static HttpRedirectImpl SAME_PROTOCOL = (HttpResponse rsp) -> {
+        String orig = rsp.request().uri().getScheme().toLowerCase();
+        String redirect = URI.create(
+                rsp.headers().firstValue("Location").orElse(""))
+                .getScheme().toLowerCase();
+        return orig.equals(redirect);
+    };
+
+    /**
+     * Redirect always except from HTTPS URLs to HTTP URLs.
+     */
+    static HttpRedirectImpl SECURE = (HttpResponse rsp) -> {
+        String orig = rsp.request().uri().getScheme().toLowerCase();
+        String redirect = URI.create(
+                rsp.headers().firstValue("Location").orElse(""))
+                .getScheme().toLowerCase();
+        if (orig.equals("https")) {
+            return redirect.equals("https");
+        }
+        return true;
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpRequest.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,871 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.ProxySelector;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.*;
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.LongConsumer;
+
+/**
+ * Represents one HTTP request which can be sent to a server. {@code
+ * HttpRequest}s are built from {@code HttpRequest} {@link HttpRequest.Builder
+ * builder}s. {@code HttpRequest} builders are obtained from a {@link HttpClient}
+ * by calling {@link HttpClient#request(java.net.URI) HttpClient.request}, or
+ * by calling {@link #create(java.net.URI) HttpRequest.create} which returns a
+ * builder on the <a href="HttpClient.html#defaultclient">default</a> client.
+ * A request's {@link java.net.URI}, headers and body can be set. Request bodies
+ * are provided through a {@link BodyProcessor} object. Once all required
+ * parameters have been set in the builder, one of the builder methods should be
+ * called, which sets the request method and returns a {@code HttpRequest}.
+ * These methods are {@link Builder#GET() GET}, {@link HttpRequest.Builder#POST()
+ * POST} and {@link HttpRequest.Builder#PUT() PUT} which return a GET, POST or
+ * PUT request respectively. Alternatively, {@link
+ * HttpRequest.Builder#method(String) method} can be called to set an arbitrary
+ * method type (and return a {@code HttpRequest}). Builders can also be copied
+ * and modified multiple times in order to build multiple related requests that
+ * differ in some parameters.
+ *
+ * <p> Two simple, example HTTP interactions are shown below:
+ * <pre>
+ * {@code
+ *      // GET
+ *      HttpResponse response = HttpRequest
+ *          .create(new URI("http://www.foo.com"))
+ *          .headers("Foo", "foovalue", "Bar", "barvalue")
+ *          .GET()
+ *          .response();
+ *
+ *      int statusCode = response.statusCode();
+ *      String responseBody = response.body(asString());
+ *
+ *      // POST
+ *      response = HttpRequest
+ *          .create(new URI("http://www.foo.com"))
+ *          .body(fromString("param1=foo,param2=bar"))
+ *          .POST()
+ *          .response();}
+ * </pre>
+ *
+ * <p> The request is sent and the response obtained by calling one of the
+ * following methods.
+ * <ul><li>{@link #response() response} blocks until the entire request has been
+ * sent and the response status code and headers have been received.</li>
+ * <li>{@link #responseAsync() responseAsync} sends the request and receives the
+ * response asynchronously. Returns immediately with a
+ * {@link java.util.concurrent.CompletableFuture CompletableFuture}&lt;{@link
+ * HttpResponse}&gt;.</li>
+ * <li>{@link #multiResponseAsync(HttpResponse.MultiProcessor) multiResponseAsync}
+ * sends the request asynchronously, expecting multiple responses. This
+ * capability is of most relevance to HTTP/2 server push, but can be used for
+ * single responses (HTTP/1.1 or HTTP/2) also.</li>
+ * </ul>
+ *
+ * <p> Once a request has been sent, it is an error to try and send it again.
+ *
+ * <p> Once a {@code HttpResponse} is received, the headers and response code are
+ * available. The body can then be received by calling one of the body methods
+ * on {@code HttpResponse}.
+ *
+ * <p> See below for discussion of synchronous versus asynchronous usage.
+ *
+ * <p> <b>Request bodies</b>
+ *
+ * <p> Request bodies are sent using one of the request processor implementations
+ * below provided in {@code HttpRequest}, or else a custom implementation can be
+ * used.
+ * <ul>
+ * <li>{@link #fromByteArray(byte[]) } from byte array</li>
+ * <li>{@link #fromByteArrays(java.util.Iterator) fromByteArrays(Iterator)}
+ *      from an iterator of byte arrays</li>
+ * <li>{@link #fromFile(java.nio.file.Path) fromFile(Path)} from the file located
+ *     at the given Path</li>
+ * <li>{@link #fromString(java.lang.String) fromString(String)} from a String </li>
+ * <li>{@link #fromInputStream(java.io.InputStream) fromInputStream(InputStream)}
+ *      request body from InputStream</li>
+ * <li>{@link #noBody() } no request body is sent</li>
+ * </ul>
+ *
+ * <p> <b>Response bodies</b>
+ *
+ * <p> Responses bodies are handled by the {@link HttpResponse.BodyProcessor}
+ * {@code <T>} supplied to the {@link HttpResponse#body(HttpResponse.BodyProcessor)
+ * HttpResponse.body} and {@link HttpResponse#bodyAsync(HttpResponse.BodyProcessor)
+ * HttpResponse.bodyAsync} methods. Some implementations of {@code
+ * HttpResponse.BodyProcessor} are provided in {@link HttpResponse}:
+ * <ul>
+ * <li>{@link HttpResponse#asByteArray() } stores the body in a byte array</li>
+ * <li>{@link HttpResponse#asString()} stores the body as a String </li>
+ * <li>{@link HttpResponse#asFile(java.nio.file.Path) } stores the body in a
+ * named file</li>
+ * <li>{@link HttpResponse#ignoreBody() } ignores any received response body</li>
+ * </ul>
+ *
+ * <p> The output of a response processor is the response body, and its
+ * parameterized type {@code T} determines the type of the body object returned
+ * from {@code HttpResponse.body} and {@code HttpResponse.bodyAsync}. Therefore,
+ * as an example, the second response processor in the list above has the type
+ * {@code HttpResponse.BodyProcessor<String>} which means the type returned by
+ * {@code HttpResponse.body()} is a String. Response processors can be defined
+ * to return potentially any type as body.
+ *
+ * <p> <b>Multi responses</b>
+ *
+ * <p> With HTTP/2 it is possible for a server to return a main response and zero
+ * or more additional responses (known as server pushes) to a client-initiated
+ * request. These are handled using a special response processor called {@link
+ * HttpResponse.MultiProcessor}.
+ *
+ * <p> <b>Blocking/asynchronous behavior and thread usage</b>
+ *
+ * <p> There are two styles of request sending: <i>synchronous</i> and
+ * <i>asynchronous</i>. {@link #response() response} blocks the calling thread
+ * until the request has been sent and the response received.
+ *
+ * <p> {@link #responseAsync() responseAsync} is asynchronous and returns
+ * immediately with a {@link java.util.concurrent.CompletableFuture}&lt;{@link
+ * HttpResponse}&gt; and when this object completes (in a background thread) the
+ * response has been received.
+ *
+ * <p> {@link #multiResponseAsync(HttpResponse.MultiProcessor) multiResponseAsync}
+ * is the variant for multi responses and is also asynchronous.
+ *
+ * <p> CompletableFutures can be combined in different ways to declare the
+ * dependencies among several asynchronous tasks, while allowing for the maximum
+ * level of parallelism to be utilized.
+ *
+ * <p> <b>Security checks</b>
+ *
+ * <p> If a security manager is present then security checks are performed by
+ * the {@link #response() } and {@link #responseAsync() } methods. A {@link
+ * java.net.URLPermission} or {@link java.net.SocketPermission} is required to
+ * access any destination origin server and proxy server utilised. URLPermissions
+ * should be preferred in policy files over SocketPermissions given the more
+ * limited scope of URLPermission. Permission is always implicitly granted to a
+ * system's default proxies. The URLPermission form used to access proxies uses
+ * a method parameter of "CONNECT" (for all kinds of proxying) and a url string
+ * of the form "socket://host:port" where host and port specify the proxy's
+ * address.
+ *
+ * <p> <b>Examples</b>
+ * <pre>
+ *     import static java.net.http.HttpRequest.*;
+ *     import static java.net.http.HttpResponse.*;
+ *
+ *     //Simple blocking
+ *
+ *     HttpResponse r1 = HttpRequest.create(new URI("http://www.foo.com/"))
+ *                                  .GET()
+ *                                 .response();
+ *     int responseCode = r1.statusCode());
+ *     String body = r1.body(asString());
+ *
+ *     HttpResponse r2 = HttpRequest.create(new URI("http://www.foo.com/"))
+ *                                  .GET()
+ *                                  .response();
+ *
+ *     System.out.println("Response was " + r1.statusCode());
+ *     Path body1 = r2.body(asFile(Paths.get("/tmp/response.txt")));
+ *     // Content stored in /tmp/response.txt
+ *
+ *     HttpResponse r3 = HttpRequest.create(new URI("http://www.foo.com/"))
+ *                                  .body(fromString("param1=1, param2=2"))
+ *                                  .POST()
+ *                                  .response();
+ *
+ *     Void body2 = r3.body(ignoreBody()); // body is Void in this case
+ * </pre>
+ *
+ * <p><b>Asynchronous Example</b>
+ *
+ * <p> All of the above examples will work asynchronously, if {@link
+ * #responseAsync()} is used instead of {@link #response()} in which case the
+ * returned object is a {@code CompletableFuture<HttpResponse>} instead of
+ * {@code HttpResponse}. The following example shows how multiple requests can
+ * be sent asynchronously. It also shows how dependent asynchronous operations
+ * (receiving response, and receiving response body) can be chained easily using
+ * one of the many methods in {@code CompletableFuture}.
+ * <pre>
+ * {@code
+ *      // fetch a list of target URIs asynchronously and store them in Files.
+ *
+ *      List<URI> targets = ...
+ *
+ *      List<CompletableFuture<File>> futures = targets
+ *          .stream()
+ *          .map(target -> {
+ *              return HttpRequest
+ *                  .create(target)
+ *                  .GET()
+ *                  .responseAsync()
+ *                  .thenCompose(response -> {
+ *                      Path dest = Paths.get("base", target.getPath());
+ *                      if (response.statusCode() == 200) {
+ *                          return response.bodyAsync(asFile(dest));
+ *                      } else {
+ *                          return CompletableFuture.completedFuture(dest);
+ *                      }
+ *                  })
+ *                  // convert Path -> File
+ *                  .thenApply((Path dest) -> {
+ *                      return dest.toFile();
+ *                  });
+ *              })
+ *          .collect(Collectors.toList());
+ *
+ *      // all async operations waited for here
+ *
+ *      CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
+ *          .join();
+ *
+ *      // all elements of futures have completed and can be examined.
+ *      // Use File.exists() to check whether file was successfully downloaded
+ * }
+ * </pre>
+ *
+ * @since 9
+ */
+public abstract class HttpRequest {
+
+    HttpRequest() {}
+
+    /**
+     * A builder of {@link HttpRequest}s. {@code HttpRequest.Builder}s are
+     * created by calling {@link HttpRequest#create(URI)} or {@link
+     * HttpClient#request(URI)}.
+     *
+     * <p> Each of the setter methods in this class modifies the state of the
+     * builder and returns <i>this</i> (ie. the same instance). The methods are
+     * not synchronized and should not be called from multiple threads without
+     * external synchronization.
+     *
+     * <p> The build methods return a new {@code HttpRequest} each time they are
+     * called.
+     *
+     * @since 9
+     */
+    public abstract static class Builder {
+
+        Builder() {}
+
+        /**
+         * Sets this HttpRequest's request URI.
+         *
+         * @param uri the request URI
+         * @return this request builder
+         */
+        public abstract Builder uri(URI uri);
+
+        /**
+         * Specifies whether this request will automatically follow redirects
+         * issued by the server. The default value for this setting is the value
+         * of {@link HttpClient#followRedirects() }
+         *
+         * @param policy the redirection policy
+         * @return this request builder
+         */
+        public abstract Builder followRedirects(HttpClient.Redirect policy);
+
+        /**
+         * Request server to acknowledge request before sending request
+         * body. This is disabled by default. If enabled, the server is requested
+         * to send an error response or a 100-Continue response before the client
+         * sends the request body. This means the request processor for the
+         * request will not be invoked until this interim response is received.
+         *
+         * @param enable {@code true} if Expect continue to be sent
+         * @return this request builder
+         */
+        public abstract Builder expectContinue(boolean enable);
+
+        /**
+         * Overrides the {@link HttpClient#version()  } setting for this
+         * request.
+         *
+         * @param version the HTTP protocol version requested
+         * @return this request builder
+         */
+        public abstract Builder version(HttpClient.Version version);
+
+        /**
+         * Adds the given name value pair to the set of headers for this request.
+         *
+         * @param name the header name
+         * @param value the header value
+         * @return this request builder
+         */
+        public abstract Builder header(String name, String value);
+
+        /**
+         * Overrides the ProxySelector set on the request's client for this
+         * request.
+         *
+         * @param proxy the ProxySelector to use
+         * @return this request builder
+         */
+        public abstract Builder proxy(ProxySelector proxy);
+
+        /**
+         * Adds the given name value pairs to the set of headers for this
+         * request. The supplied Strings must alternate as names and values.
+         *
+         * @param headers the list of String name value pairs
+         * @return this request builder
+         * @throws IllegalArgumentException if there is an odd number of
+         *                                  parameters
+         */
+        public abstract Builder headers(String... headers);
+
+        /**
+         * Sets a timeout for this request. If the response is not received
+         * within the specified timeout then a {@link HttpTimeoutException} is
+         * thrown from {@link #response() } or {@link #responseAsync() }
+         * completes exceptionally with a {@code HttpTimeoutException}.
+         *
+         * @param unit the timeout units
+         * @param timeval the number of units to wait for
+         * @return this request builder
+         */
+        public abstract Builder timeout(TimeUnit unit, long timeval);
+
+        /**
+         * Sets the given name value pair to the set of headers for this
+         * request. This overwrites any previously set values for name.
+         *
+         * @param name the header name
+         * @param value the header value
+         * @return this request builder
+         */
+        public abstract Builder setHeader(String name, String value);
+
+        /**
+         * Sets a request body for this builder. See {@link HttpRequest}
+         * for example {@code BodyProcessor} implementations.
+         * If no body is specified, then no body is sent with the request.
+         *
+         * @param reqproc the request body processor
+         * @return this request builder
+         */
+        public abstract Builder body(BodyProcessor reqproc);
+
+        /**
+         * Builds and returns a GET {@link HttpRequest} from this builder.
+         *
+         * @return a {@code HttpRequest}
+         */
+        public abstract HttpRequest GET();
+
+        /**
+         * Builds and returns a POST {@link HttpRequest} from this builder.
+         *
+         * @return a {@code HttpRequest}
+         */
+        public abstract HttpRequest POST();
+
+        /**
+         * Builds and returns a PUT {@link HttpRequest} from this builder.
+         *
+         * @return a {@code HttpRequest}
+         */
+        public abstract HttpRequest PUT();
+
+        /**
+         * Builds and returns a {@link HttpRequest} from this builder using
+         * the given method String. The method string is case-sensitive, and
+         * may be rejected if an upper-case string is not used.
+         *
+         * @param method the method to use
+         * @return a {@code HttpRequest}
+         * @throws IllegalArgumentException if an unrecognised method is used
+         */
+        public abstract HttpRequest method(String method);
+
+        /**
+         * Returns an exact duplicate copy of this Builder based on current
+         * state. The new builder can then be modified independently of this
+         * builder.
+         *
+         * @return an exact copy of this Builder
+         */
+        public abstract Builder copy();
+    }
+
+    /**
+     * Creates a HttpRequest builder from the <i>default</i> HttpClient.
+     *
+     * @param uri the request URI
+     * @return a new request builder
+     */
+    public static HttpRequest.Builder create(URI uri) {
+        return HttpClient.getDefault().request(uri);
+    }
+
+    /**
+     * Returns the follow-redirects setting for this request.
+     *
+     * @return follow redirects setting
+     */
+    public abstract HttpClient.Redirect followRedirects();
+
+    /**
+     * Returns the response to this request, by sending it and blocking if
+     * necessary to get the response. The {@link HttpResponse} contains the
+     * response status and headers.
+     *
+     * @return a HttpResponse for this request
+     * @throws IOException if an I/O error occurs
+     * @throws InterruptedException if the operation was interrupted
+     * @throws SecurityException if the caller does not have the required
+     *                           permission
+     * @throws IllegalStateException if called more than once or if
+     *                               responseAsync() called previously
+     */
+    public abstract HttpResponse response()
+        throws IOException, InterruptedException;
+
+    /**
+     * Sends the request and returns the response asynchronously. This method
+     * returns immediately with a {@link CompletableFuture}&lt;{@link
+     * HttpResponse}&gt;
+     *
+     * @return a {@code CompletableFuture<HttpResponse>}
+     * @throws IllegalStateException if called more than once or if response()
+     *                               called previously.
+     */
+    public abstract CompletableFuture<HttpResponse> responseAsync();
+
+    /**
+     * Sends the request asynchronously expecting multiple responses.
+     *
+     * <p> This method must be given a {@link HttpResponse.MultiProcessor} to
+     * handle the multiple responses.
+     *
+     * <p> If a security manager is set, the caller must possess a {@link
+     * java.net.URLPermission} for the request's URI, method and any user set
+     * headers. The security manager is also checked for each incoming
+     * additional server generated request/response. Any request that fails the
+     * security check, is canceled and ignored.
+     *
+     * <p> This method can be used for both HTTP/1.1 and HTTP/2, but in cases
+     * where multiple responses are not supported, the MultiProcessor
+     * only receives the main response.
+     *
+     * <p> The aggregate {@code CompletableFuture} returned from this method
+     * returns a {@code <U>} defined by the {@link HttpResponse.MultiProcessor}
+     * implementation supplied. This will typically be a Collection of
+     * HttpResponses or of some response body type.
+     *
+     * @param <U> the aggregate response type
+     * @param rspproc the MultiProcessor for the request
+     * @return a {@code CompletableFuture<U>}
+     * @throws IllegalStateException if the request has already been sent.
+     */
+    public abstract <U> CompletableFuture<U>
+    multiResponseAsync(HttpResponse.MultiProcessor<U> rspproc);
+
+    /**
+     * Returns the request method for this request. If not set explicitly,
+     * the default method for any request is "GET".
+     *
+     * @return this request's method
+     */
+    public abstract String method();
+
+    /**
+     * Returns this request's {@link HttpRequest.Builder#expectContinue(boolean)
+     * expect continue } setting.
+     *
+     * @return this request's expect continue setting
+     */
+    public abstract boolean expectContinue();
+
+    /**
+     * Returns this request's request URI.
+     *
+     * @return this request's URI
+     */
+    public abstract URI uri();
+
+    /**
+     * Returns this request's {@link HttpClient}.
+     *
+     * @return this request's HttpClient
+     */
+    public abstract HttpClient client();
+
+    /**
+     * Returns the HTTP protocol version that this request will use or used.
+     *
+     * @return HTTP protocol version
+     */
+    public abstract HttpClient.Version version();
+
+    /**
+     * The (user-accessible) request headers that this request was (or will be)
+     * sent with.
+     *
+     * @return this request's HttpHeaders
+     */
+    public abstract HttpHeaders headers();
+
+    /**
+     * Returns a request processor whose body is the given String, converted
+     * using the {@link java.nio.charset.StandardCharsets#ISO_8859_1 ISO_8859_1}
+     * character set.
+     *
+     * @param body the String containing the body
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor fromString(String body) {
+        return fromString(body, StandardCharsets.ISO_8859_1);
+    }
+
+    /**
+     * A request processor that takes data from the contents of a File.
+     *
+     * @param path the path to the file containing the body
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor fromFile(Path path) {
+        FileChannel fc;
+        long size;
+
+        try {
+            fc = FileChannel.open(path);
+            size = fc.size();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        return new BodyProcessor() {
+            LongConsumer flow;
+
+            @Override
+            public long onRequestStart(HttpRequest hr, LongConsumer flow) {
+                // could return exact file length, but for now -1
+                this.flow = flow;
+                flow.accept(1);
+                if (size != 0) {
+                    return size;
+                } else {
+                    return -1;
+                }
+            }
+
+            @Override
+            public boolean onRequestBodyChunk(ByteBuffer buffer) throws IOException {
+                int n = fc.read(buffer);
+                if (n == -1) {
+                    fc.close();
+                    return true;
+                }
+                flow.accept(1);
+                return false;
+            }
+
+            @Override
+            public void onRequestError(Throwable t) {
+                try {
+                    fc.close();
+                } catch (IOException ex) {
+                    Log.logError(ex.toString());
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns a request processor whose body is the given String, converted
+     * using the given character set.
+     *
+     * @param s the String containing the body
+     * @param charset the character set to convert the string to bytes
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor fromString(String s, Charset charset) {
+        return fromByteArray(s.getBytes(charset));
+    }
+
+    /**
+     * Returns a request processor whose body is the given byte array.
+     *
+     * @param buf the byte array containing the body
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor fromByteArray(byte[] buf) {
+        return fromByteArray(buf, 0, buf.length);
+    }
+
+    /**
+     * Returns a request processor whose body is the content of the given byte
+     * array length bytes starting from the specified offset.
+     *
+     * @param buf the byte array containing the body
+     * @param offset the offset of the first byte
+     * @param length the number of bytes to use
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor fromByteArray(byte[] buf, int offset, int length) {
+
+        return new BodyProcessor() {
+            LongConsumer flow;
+            byte[] barray;
+            int index;
+            int sent;
+
+            @Override
+            public long onRequestStart(HttpRequest hr, LongConsumer flow) {
+                this.flow = flow;
+                flow.accept(1);
+                barray = buf;
+                index = offset;
+                return length;
+            }
+
+            @Override
+            public boolean onRequestBodyChunk(ByteBuffer buffer)
+                throws IOException
+            {
+                if (sent == length) {
+                    return true;
+                }
+
+                int remaining = buffer.remaining();
+                int left = length - sent;
+                int n = remaining > left ? left : remaining;
+                buffer.put(barray, index, n);
+                index += n;
+                sent += n;
+                flow.accept(1);
+                return sent == length;
+            }
+
+            @Override
+            public void onRequestError(Throwable t) {
+                Log.logError(t.toString());
+            }
+        };
+    }
+
+    /**
+     * A request processor that takes data from an Iterator of byte arrays.
+     *
+     * @param iter an Iterator of byte arrays
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor fromByteArrays(Iterator<byte[]> iter) {
+
+        return new BodyProcessor() {
+            LongConsumer flow;
+            byte[] current;
+            int curIndex;
+
+            @Override
+            public long onRequestStart(HttpRequest hr, LongConsumer flow) {
+                this.flow = flow;
+                flow.accept(1);
+                return -1;
+            }
+
+            @Override
+            public boolean onRequestBodyChunk(ByteBuffer buffer)
+                throws IOException
+            {
+                int remaining;
+
+                while ((remaining = buffer.remaining()) > 0) {
+                    if (current == null) {
+                        if (!iter.hasNext()) {
+                            return true;
+                        }
+                        current = iter.next();
+                        curIndex = 0;
+                    }
+                    int n = Math.min(remaining, current.length - curIndex);
+                    buffer.put(current, curIndex, n);
+                    curIndex += n;
+
+                    if (curIndex == current.length) {
+                        current = null;
+                        flow.accept(1);
+                        return false;
+                    }
+                }
+                flow.accept(1);
+                return false;
+            }
+
+            @Override
+            public void onRequestError(Throwable t) {
+                Log.logError(t.toString());
+            }
+        };
+    }
+
+    /**
+     * A request processor that reads its data from an InputStream.
+     *
+     * @param stream an InputStream
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor fromInputStream(InputStream stream) {
+        // for now, this blocks. It could be offloaded to a separate thread
+        // to do reading and guarantee that onRequestBodyChunk() won't block
+        return new BodyProcessor() {
+            LongConsumer flow;
+
+            @Override
+            public long onRequestStart(HttpRequest hr, LongConsumer flow) {
+                this.flow = flow;
+                flow.accept(1);
+                return -1;
+            }
+
+            @Override
+            public boolean onRequestBodyChunk(ByteBuffer buffer)
+                throws IOException
+            {
+                int remaining = buffer.remaining();
+                int n = stream.read(buffer.array(), buffer.arrayOffset(), remaining);
+                if (n == -1) {
+                    stream.close();
+                    return true;
+                }
+                buffer.position(buffer.position() + n);
+                flow.accept(1);
+                return false;
+            }
+
+            @Override
+            public void onRequestError(Throwable t) {
+                Log.logError(t.toString());
+            }
+        };
+    }
+
+    /**
+     * A request processor which sends no request body.
+     *
+     * @return a BodyProcessor
+     */
+    public static BodyProcessor noBody() {
+        return new BodyProcessor() {
+
+            @Override
+            public long onRequestStart(HttpRequest hr, LongConsumer flow) {
+                return 0;
+            }
+
+            @Override
+            public boolean onRequestBodyChunk(ByteBuffer buffer)
+                throws IOException
+            {
+                throw new InternalError("should never reach here");
+            }
+
+            @Override
+            public void onRequestError(Throwable t) {
+                Log.logError(t.toString());
+            }
+        };
+    }
+
+    /**
+     * A request processor which obtains the request body from some source.
+     * Implementations of this interface are provided which allow request bodies
+     * to be supplied from standard types, such as {@code String, byte[], File,
+     * InputStream}. Other implementations can be provided.
+     *
+     * <p> The methods of this interface may be called from multiple threads,
+     * but only one method is invoked at a time, and behaves as if called from
+     * one thread.
+     *
+     * <p> See {@link HttpRequest} for implementations that take request bodies
+     * from {@code byte arrays, Strings, Paths} etc.
+     *
+     * @since 9
+     */
+    public interface BodyProcessor {
+
+        /**
+         * Called before a request is sent. Is expected to return the content
+         * length of the request body. Zero means no content. Less than zero
+         * means an unknown positive content-length, and the body will be
+         * streamed.
+         *
+         * <p> The flowController object must be used to manage the flow of
+         * calls to {@link #onRequestBodyChunk(ByteBuffer)}. The typical usage
+         * for a non-blocking processor is to call it once inside
+         * onRequestStart() and once during each call to onRequestBodyChunk().
+         *
+         * @param hr the request
+         * @param flowController the HttpFlowController
+         * @return the content length
+         * @throws IOException if an I/O error occurs
+         */
+        long onRequestStart(HttpRequest hr, LongConsumer flowController)
+            throws IOException;
+
+        /**
+         * Called if sending a request body fails.
+         *
+         * @implSpec The default implementation does nothing.
+         *
+         * @param t the Throwable that caused the failure
+         */
+        default void onRequestError(Throwable t) { }
+
+        /**
+         * Called to obtain a buffer of data to send. The data must be placed
+         * in the provided buffer. The implementation should not block. The
+         * boolean return code notifies the protocol implementation if the
+         * supplied buffer is the final one (or not).
+         *
+         * @param buffer a ByteBuffer to write data into
+         * @return whether or not this is the last buffer
+         * @throws IOException if an I/O error occurs
+         */
+        boolean onRequestBodyChunk(ByteBuffer buffer) throws IOException;
+
+        /**
+         * Called when the request body has been completely sent.
+         *
+         * @implSpec The default implementation does nothing
+         */
+        default void onComplete() {
+            // TODO: need to call this
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpRequestBuilderImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.net.URI;
+import java.net.ProxySelector;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+class HttpRequestBuilderImpl extends HttpRequest.Builder {
+
+    private HttpHeadersImpl userHeaders;
+    private URI uri;
+    private String method;
+    private HttpClient.Redirect followRedirects;
+    private boolean expectContinue;
+    private HttpRequest.BodyProcessor body;
+    private HttpClient.Version version;
+    private final HttpClientImpl client;
+    private ProxySelector proxy;
+    private long timeval = 0;
+
+    public HttpRequestBuilderImpl(HttpClientImpl client, URI uri) {
+        this.client = client;
+        this.uri = uri;
+        this.version = client.version();
+        this.userHeaders = new HttpHeadersImpl();
+    }
+
+    @Override
+    public HttpRequestBuilderImpl body(HttpRequest.BodyProcessor reqproc) {
+        Objects.requireNonNull(reqproc);
+        this.body = reqproc;
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl uri(URI uri) {
+        Objects.requireNonNull(uri);
+        this.uri = uri;
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl followRedirects(HttpClient.Redirect follow) {
+        Objects.requireNonNull(follow);
+        this.followRedirects = follow;
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl header(String name, String value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        Utils.validateToken(name, "invalid header name");
+        userHeaders.addHeader(name, value);
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl headers(String... params) {
+        Objects.requireNonNull(params);
+        if (params.length % 2 != 0) {
+            throw new IllegalArgumentException("wrong number of parameters");
+        }
+        for (int i=0; i<params.length; ) {
+            String name = params[i];
+            String value = params[i+1];
+            header(name, value);
+            i+=2;
+        }
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl proxy(ProxySelector proxy) {
+        Objects.requireNonNull(proxy);
+        this.proxy = proxy;
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl copy() {
+        HttpRequestBuilderImpl b = new HttpRequestBuilderImpl(this.client, this.uri);
+        b.userHeaders = this.userHeaders.deepCopy();
+        b.method = this.method;
+        b.followRedirects = this.followRedirects;
+        b.expectContinue = this.expectContinue;
+        b.body = body;
+        b.uri = uri;
+        b.proxy = proxy;
+        return b;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl setHeader(String name, String value) {
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(value);
+        userHeaders.setHeader(name, value);
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl expectContinue(boolean enable) {
+        expectContinue = enable;
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl version(HttpClient.Version version) {
+        Objects.requireNonNull(version);
+        this.version = version;
+        return this;
+    }
+
+    HttpHeadersImpl headers() {  return userHeaders; }
+
+    URI uri() { return uri; }
+
+    String method() { return method; }
+
+    HttpClient.Redirect followRedirects() { return followRedirects; }
+
+    ProxySelector proxy() { return proxy; }
+
+    boolean expectContinue() { return expectContinue; }
+
+    HttpRequest.BodyProcessor body() { return body; }
+
+    HttpClient.Version version() { return version; }
+
+    @Override
+    public HttpRequest GET() { return method("GET"); }
+
+    @Override
+    public HttpRequest POST() { return method("POST"); }
+
+    @Override
+    public HttpRequest PUT() { return method("PUT"); }
+
+    @Override
+    public HttpRequest method(String method) {
+        Objects.requireNonNull(method);
+        this.method = method;
+        return new HttpRequestImpl(client, method, this);
+    }
+
+    @Override
+    public HttpRequest.Builder timeout(TimeUnit timeunit, long timeval) {
+        Objects.requireNonNull(timeunit);
+        this.timeval = timeunit.toMillis(timeval);
+        return this;
+    }
+
+    long timeval() { return timeval; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpRequestImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpResponse.MultiProcessor;
+import java.util.concurrent.CompletableFuture;
+import java.net.SocketPermission;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.Set;
+import static java.net.http.HttpRedirectImpl.getRedirects;
+import java.util.Locale;
+
+class HttpRequestImpl extends HttpRequest {
+
+    private final HttpHeadersImpl userHeaders;
+    private final HttpHeadersImpl systemHeaders;
+    private final URI uri;
+    private InetSocketAddress authority; // only used when URI not specified
+    private final String method;
+    private final HttpClientImpl client;
+    private final HttpRedirectImpl followRedirects;
+    private final ProxySelector proxy;
+    final BodyProcessor requestProcessor;
+    final boolean secure;
+    final boolean expectContinue;
+    private final java.net.http.HttpClient.Version version;
+    private boolean isWebSocket;
+    final MultiExchange exchange;
+    private boolean receiving;
+    private AccessControlContext acc;
+    private final long timeval;
+
+    public HttpRequestImpl(HttpClientImpl client,
+                           String method,
+                           HttpRequestBuilderImpl builder) {
+        this.client = client;
+        this.method = method == null? "GET" : method;
+        this.userHeaders = builder.headers() == null ?
+                new HttpHeadersImpl() : builder.headers();
+        dropDisallowedHeaders();
+        this.followRedirects = getRedirects(builder.followRedirects() == null ?
+                client.followRedirects() : builder.followRedirects());
+        this.systemHeaders = new HttpHeadersImpl();
+        this.uri = builder.uri();
+        this.proxy = builder.proxy();
+        this.expectContinue = builder.expectContinue();
+        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
+        this.version = builder.version();
+        if (builder.body() == null) {
+            this.requestProcessor = HttpRequest.noBody();
+        } else {
+            this.requestProcessor = builder.body();
+        }
+        this.exchange = new MultiExchange(this);
+        this.timeval = builder.timeval();
+    }
+
+    /** Creates a HttpRequestImpl using fields of an existing request impl. */
+    public HttpRequestImpl(URI uri,
+                           HttpRequest request,
+                           HttpClientImpl client,
+                           String method,
+                           HttpRequestImpl other) {
+        this.client = client;
+        this.method = method == null? "GET" : method;
+        this.userHeaders = other.userHeaders == null ?
+                new HttpHeadersImpl() : other.userHeaders;
+        dropDisallowedHeaders();
+        this.followRedirects = getRedirects(other.followRedirects() == null ?
+                client.followRedirects() : other.followRedirects());
+        this.systemHeaders = other.systemHeaders;
+        this.uri = uri;
+        this.expectContinue = other.expectContinue;
+        this.secure = other.secure;
+        this.requestProcessor = other.requestProcessor;
+        this.proxy = other.proxy;
+        this.version = other.version;
+        this.acc = other.acc;
+        this.exchange = new MultiExchange(this);
+        this.timeval = other.timeval;
+    }
+
+    /* used for creating CONNECT requests  */
+    HttpRequestImpl(HttpClientImpl client,
+                    String method,
+                    InetSocketAddress authority) {
+        this.client = client;
+        this.method = method;
+        this.followRedirects = getRedirects(client.followRedirects());
+        this.systemHeaders = new HttpHeadersImpl();
+        this.userHeaders = new HttpHeadersImpl();
+        this.uri = null;
+        this.proxy = null;
+        this.requestProcessor = HttpRequest.noBody();
+        this.version = java.net.http.HttpClient.Version.HTTP_1_1;
+        this.authority = authority;
+        this.secure = false;
+        this.expectContinue = false;
+        this.exchange = new MultiExchange(this);
+        this.timeval = 0; // block TODO: fix
+    }
+
+    @Override
+    public HttpClientImpl client() {
+        return client;
+    }
+
+
+    @Override
+    public String toString() {
+        return (uri == null ? "" : uri.toString()) + "/" + method + "("
+                + hashCode() + ")";
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        userHeaders.makeUnmodifiable();
+        return userHeaders;
+    }
+
+    InetSocketAddress authority() { return authority; }
+
+    void setH2Upgrade() {
+        Http2ClientImpl h2client = client.client2();
+        systemHeaders.setHeader("Connection", "Upgrade, HTTP2-Settings");
+        systemHeaders.setHeader("Upgrade", "h2c");
+        systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
+    }
+
+    private static final Set<String>  DISALLOWED_HEADERS_SET = Set.of(
+        "authorization", "connection", "cookie", "content-length",
+        "date", "expect", "from", "host", "origin", "proxy-authorization",
+        "referer", "user-agent", "upgrade", "via", "warning");
+
+
+    // we silently drop headers that are disallowed
+    private void dropDisallowedHeaders() {
+        Set<String> hdrnames = userHeaders.directMap().keySet();
+
+        hdrnames.removeIf((s) ->
+              DISALLOWED_HEADERS_SET.contains(s.toLowerCase())
+        );
+    }
+
+    private synchronized void receiving() {
+        if (receiving) {
+            throw new IllegalStateException("already receiving response");
+        }
+        receiving = true;
+    }
+
+    /*
+     * Response filters may result in a new HttpRequestImpl being created
+     * (but still associated with the same API HttpRequest) and the process
+     * is repeated.
+     */
+    @Override
+    public HttpResponse response() throws IOException, InterruptedException {
+        receiving(); // TODO: update docs
+        if (System.getSecurityManager() != null) {
+            acc = AccessController.getContext();
+        }
+        return exchange.response();
+    }
+
+    @Override
+    public synchronized CompletableFuture<HttpResponse> responseAsync() {
+        receiving(); // TODO: update docs
+        if (System.getSecurityManager() != null) {
+            acc = AccessController.getContext();
+        }
+        return exchange.responseAsync(null)
+            .thenApply((r) -> (HttpResponse)r);
+    }
+
+    public <U> CompletableFuture<U>
+    sendAsyncMulti(HttpResponse.MultiProcessor<U> rspproc) {
+        // To change body of generated methods, choose Tools | Templates.
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public boolean expectContinue() { return expectContinue; }
+
+    public boolean requestHttp2() {
+        return version.equals(HttpClient.Version.HTTP_2);
+        //return client.getHttp2Allowed();
+    }
+
+    AccessControlContext getAccessControlContext() { return acc; }
+
+    InetSocketAddress proxy() {
+        ProxySelector ps = this.proxy;
+        if (ps == null) {
+            ps = client.proxy().orElse(null);
+        }
+        if (ps == null || method.equalsIgnoreCase("CONNECT")) {
+            return null;
+        }
+        return (InetSocketAddress)ps.select(uri).get(0).address();
+    }
+
+    boolean secure() { return secure; }
+
+    void isWebSocket(boolean is) {
+        isWebSocket = is;
+    }
+
+    boolean isWebSocket() {
+        return isWebSocket;
+    }
+
+    /** Returns the follow-redirects setting for this request. */
+    @Override
+    public java.net.http.HttpClient.Redirect followRedirects() {
+        return getRedirects(followRedirects);
+    }
+
+    HttpRedirectImpl followRedirectsImpl() { return followRedirects; }
+
+    /**
+     * Returns the request method for this request. If not set explicitly,
+     * the default method for any request is "GET".
+     */
+    @Override
+    public String method() { return method; }
+
+    @Override
+    public URI uri() { return uri; }
+
+    HttpHeadersImpl getUserHeaders() { return userHeaders; }
+
+    HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
+
+    HttpClientImpl getClient() { return client; }
+
+    BodyProcessor requestProcessor() { return requestProcessor; }
+
+    @Override
+    public Version version() { return version; }
+
+    void addSystemHeader(String name, String value) {
+        systemHeaders.addHeader(name, value);
+    }
+
+    void setSystemHeader(String name, String value) {
+        systemHeaders.setHeader(name, value);
+    }
+
+    long timeval() { return timeval; }
+
+    @Override
+    public <U> CompletableFuture<U>
+    multiResponseAsync(MultiProcessor<U> rspproc) {
+        //To change body of generated methods, choose Tools | Templates.
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpResponse.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,977 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.LongConsumer;
+import javax.net.ssl.SSLParameters;
+
+/**
+ * Represents a response to a {@link HttpRequest}. A {@code HttpResponse} is
+ * available when the response status code and headers have been received, but
+ * before the response body is received.
+ *
+ * <p> Methods are provided in this class for accessing the response headers,
+ * and status code immediately and also methods for retrieving the response body.
+ * Static methods are provided which implement {@link BodyProcessor} for
+ * standard body types such as {@code String, byte arrays, files}.
+ *
+ * <p> The {@link #body(BodyProcessor) body} or {@link #bodyAsync(BodyProcessor)
+ * bodyAsync} which retrieve any response body must be called to ensure that the
+ * TCP connection can be re-used subsequently, and any response trailers
+ * accessed, if they exist, unless it is known that no response body was received.
+ *
+ * @since 9
+ */
+public abstract class HttpResponse {
+
+    HttpResponse() { }
+
+    /**
+     * Returns the status code for this response.
+     *
+     * @return the response code
+     */
+    public abstract int statusCode();
+
+    /**
+     * Returns the {@link HttpRequest} for this response.
+     *
+     * @return the request
+     */
+    public abstract HttpRequest request();
+
+    /**
+     * Returns the received response headers.
+     *
+     * @return the response headers
+     */
+    public abstract HttpHeaders headers();
+
+    /**
+     * Returns the received response trailers, if there are any. This must only
+     * be called after the response body has been received.
+     *
+     * @return the response trailers (may be empty)
+     * @throws IllegalStateException if the response body has not been received
+     *                               yet
+     */
+    public abstract HttpHeaders trailers();
+
+    /**
+     * Returns the body, blocking if necessary. The type T is determined by the
+     * {@link BodyProcessor} implementation supplied. The body object will be
+     * returned immediately if it is a type (such as {@link java.io.InputStream}
+     * which reads the data itself. If the body object represents the fully read
+     * body then it blocks until it is fully read.
+     *
+     * @param <T> the type of the returned body object
+     * @param processor the processor to handle the response body
+     * @return the body
+     * @throws java.io.UncheckedIOException if an I/O error occurs reading the
+     *                                      response
+     */
+    public abstract <T> T body(BodyProcessor<T> processor);
+
+    /**
+     * Returns a {@link java.util.concurrent.CompletableFuture} of type T. This
+     * always returns immediately and the future completes when the body object
+     * is available. The body will be available immediately if it is a type
+     * (such as {@link java.io.InputStream} which reads the data itself. If the
+     * body object represents the fully read body then it will not be available
+     * until it is fully read.
+     *
+     * @param <T> the type of the returned body object
+     * @param processor the processor to handle the response body
+     * @return a CompletableFuture
+     */
+    public abstract <T> CompletableFuture<T> bodyAsync(BodyProcessor<T> processor);
+
+    /**
+     * Returns the {@link javax.net.ssl.SSLParameters} in effect for this
+     * response. Returns {@code null} if this is not a https response.
+     *
+     * @return the SSLParameters associated with the response
+     */
+    public abstract SSLParameters sslParameters();
+
+    /**
+     * Returns the URI that the response was received from. This may be
+     * different from the request URI if redirection occurred.
+     *
+     * @return the URI of the response
+     */
+    public abstract URI uri();
+
+    /**
+     * Returns the HTTP protocol version that was used for this response.
+     *
+     * @return HTTP protocol version
+     */
+    public abstract HttpClient.Version version();
+
+    /**
+     * Returns a {@link BodyProcessor}&lt;{@link java.nio.file.Path}&gt; where
+     * the file is created if it does not already exist. When the Path object is
+     * returned, the body has been completely written to the file.
+     *
+     * @param file the file to store the body in
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<Path> asFile(Path file) {
+        return asFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+    }
+
+    /**
+     * Returns a {@link BodyProcessor}&lt;{@link java.nio.file.Path}&gt; where
+     * the download directory is specified, but the filename is obtained from
+     * the Content-Disposition response header. The Content-Disposition header
+     * must specify the <i>attachment</i> type and must also contain a
+     * <i>filename</i> parameter. If the filename specifies multiple path
+     * components only the final component is used as the filename (with the
+     * given directory name). When the Path object is returned, the body has
+     * been completely written to the file. The returned Path is the combination
+     * of the supplied directory name and the file name supplied by the server.
+     * If the destination directory does not exist or cannot be written to, then
+     * the response will fail with an IOException.
+     *
+     * @param directory the directory to store the file in
+     * @param openOptions open options
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<Path> asFileDownload(Path directory,
+                                                     OpenOption... openOptions) {
+        return new AbstractResponseProcessor<Path>() {
+
+            FileChannel fc;
+            Path file;
+
+            @Override
+            public Path onResponseBodyStartImpl(long contentLength,
+                                                HttpHeaders headers)
+                throws IOException
+            {
+                String dispoHeader = headers.firstValue("Content-Disposition")
+                        .orElseThrow(() -> new IOException("No Content-Disposition"));
+                if (!dispoHeader.startsWith("attachment;")) {
+                    throw new IOException("Unknown Content-Disposition type");
+                }
+                int n = dispoHeader.indexOf("filename=");
+                if (n == -1) {
+                    throw new IOException("Bad Content-Disposition type");
+                }
+                String disposition = dispoHeader.substring(n + 9,
+                                                           dispoHeader.lastIndexOf(';'));
+                file = Paths.get(directory.toString(), disposition);
+                fc = FileChannel.open(file, openOptions);
+                return null;
+            }
+
+            @Override
+            public void onResponseBodyChunkImpl(ByteBuffer b) throws IOException {
+                fc.write(b);
+            }
+
+            @Override
+            public Path onResponseComplete() throws IOException {
+                fc.close();
+                return file;
+            }
+
+            @Override
+            public void onResponseError(Throwable t) {
+                try {
+                    if (fc != null) {
+                        fc.close();
+                    }
+                } catch (IOException e) {
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns a {@link BodyProcessor}&lt;{@link java.nio.file.Path}&gt;.
+     *
+     * <p> {@link HttpResponse}s returned using this response processor complete
+     * after the entire response, including body has been read.
+     *
+     * @param file the filename to store the body in
+     * @param openOptions any options to use when opening/creating the file
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<Path> asFile(Path file,
+                                             OpenOption... openOptions) {
+        return new AbstractResponseProcessor<Path>() {
+
+            FileChannel fc;
+
+            @Override
+            public Path onResponseBodyStartImpl(long contentLength,
+                                                HttpHeaders headers)
+                throws IOException
+            {
+                fc = FileChannel.open(file, openOptions);
+                return null;
+            }
+
+            @Override
+            public void onResponseBodyChunkImpl(ByteBuffer b)
+                throws IOException
+            {
+                fc.write(b);
+            }
+
+            @Override
+            public Path onResponseComplete() throws IOException {
+                fc.close();
+                return file;
+            }
+
+            @Override
+            public void onResponseError(Throwable t) {
+                try {
+                    if (fc != null) {
+                        fc.close();
+                    }
+                } catch (IOException e) {
+                }
+            }
+        };
+    }
+
+    static class ByteArrayResponseProcessor {
+
+        static final int INITIAL_BUFLEN = 1024;
+
+        byte[] buffer;
+        int capacity;
+        boolean knownLength;
+        int position;
+
+        ByteArrayResponseProcessor() { }
+
+        public byte[] onStart(long contentLength) throws IOException {
+            if (contentLength > Integer.MAX_VALUE) {
+                throw new IllegalArgumentException(
+                        "byte array response limited to MAX_INT size");
+            }
+            capacity = (int) contentLength;
+            if (capacity != -1) {
+                buffer = new byte[capacity];
+                knownLength = true;
+            } else {
+                buffer = new byte[INITIAL_BUFLEN];
+                capacity = INITIAL_BUFLEN;
+                knownLength = false;
+            }
+            position = 0;
+            return null;
+        }
+
+        public void onBodyContent(ByteBuffer b) throws IOException {
+            int toCopy = b.remaining();
+            int size = capacity;
+            if (toCopy > capacity - position) {
+                // resize
+                size += toCopy * 2;
+            }
+            if (size != capacity) {
+                if (knownLength) {
+                    // capacity should have been right from start
+                    throw new IOException("Inconsistent content length");
+                }
+                byte[] newbuf = new byte[size];
+                System.arraycopy(buffer, 0, newbuf, 0, position);
+                buffer = newbuf;
+                capacity = size;
+            }
+            int srcposition = b.arrayOffset() + b.position();
+            System.arraycopy(b.array(), srcposition, buffer, position, toCopy);
+            b.position(b.limit());
+            position += toCopy;
+        }
+
+        public byte[] onComplete() throws IOException {
+            if (knownLength) {
+                if (position != capacity) {
+                    throw new IOException("Wrong number of bytes received");
+                }
+                return buffer;
+            }
+            byte[] buf1 = new byte[position];
+            System.arraycopy(buffer, 0, buf1, 0, position);
+            return buf1;
+        }
+
+        public void onError(Throwable t) {
+            // TODO:
+        }
+    }
+
+    static final byte[] EMPTY = new byte[0];
+
+    /**
+     * Returns a response processor which supplies the response body to the
+     * given Consumer. Each time data is received the consumer is invoked with a
+     * byte[] containing at least one byte of data. After the final buffer is
+     * received, the consumer is invoked one last time, with an empty byte
+     * array.
+     *
+     * @param consumer a Consumer to accept the response body
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<Void> asByteArrayConsumer(Consumer<byte[]> consumer) {
+        return new AbstractResponseProcessor<Void>() {
+            @Override
+            public Void onResponseBodyStartImpl(long clen,
+                                                HttpHeaders h)
+                throws IOException
+            {
+                return null;
+            }
+
+            @Override
+            public void onResponseError(Throwable t) {
+            }
+
+            @Override
+            public void onResponseBodyChunkImpl(ByteBuffer b) throws IOException {
+                if (!b.hasRemaining()) {
+                    return;
+                }
+                byte[] buf = new byte[b.remaining()];
+                b.get(buf);
+                consumer.accept(buf);
+            }
+
+            @Override
+            public Void onResponseComplete() throws IOException {
+                consumer.accept(EMPTY);
+                return null;
+            }
+        };
+    }
+
+    /**
+     * Returns a BodyProcessor which delivers the response data to a
+     * {@link java.util.concurrent.Flow.Subscriber}{@code ByteBuffer}.
+     * <p>
+     * The given {@code Supplier<U>} is invoked when the Flow is completed in
+     * order to convert the flow data into the U object that is returned as the
+     * response body.
+     *
+     * @param <U> the response body type
+     * @param subscriber the Flow.Subscriber
+     * @param bufferSize the maximum number of bytes of data to be supplied in
+     * each ByteBuffer
+     * @param bodySupplier an object that converts the received data to the body
+     * type U.
+     * @return a BodyProcessor
+     *
+     * public static <U> BodyProcessor<Flow.Subscriber<ByteBuffer>>
+     * asFlowSubscriber() {
+     *
+     * return new BodyProcessor<U>() { Flow.Subscriber<ByteBuffer> subscriber;
+     * LongConsumer flowController; FlowSubscription subscription; Supplier<U>
+     * bodySupplier; int bufferSize; // down-stream Flow window. long
+     * buffersWindow; // upstream window long bytesWindow;
+     * LinkedList<ByteBuffer> buffers = new LinkedList<>();
+     *
+     * class FlowSubscription implements Subscription { int recurseLevel = 0;
+     * @Override public void request(long n) { boolean goodToGo = recurseLevel++
+     * == 0;
+     *
+     * while (goodToGo && buffers.size() > 0 && n > 0) { ByteBuffer buf =
+     * buffers.get(0); subscriber.onNext(buf); n--; } buffersWindow += n;
+     * flowController.accept(n * bufferSize); recurseLevel--; }
+     *
+     * @Override public void cancel() { // ?? set flag and throw exception on
+     * next receipt of buffer } }
+     *
+     * @Override public U onResponseBodyStart(long contentLength, HttpHeaders
+     * responseHeaders, LongConsumer flowController) throws IOException {
+     * this.subscriber = subscriber; this.flowController = flowController;
+     * this.subscription = new FlowSubscription(); this.bufferSize = bufferSize;
+     * subscriber.onSubscribe(subscription); return null; }
+     *
+     * @Override public void onResponseError(Throwable t) {
+     * subscriber.onError(t); }
+     *
+     * @Override public void onResponseBodyChunk(ByteBuffer b) throws
+     * IOException { if (buffersWindow > 0) { buffersWindow --;
+     * subscriber.onNext(b); } else { buffers.add(b); // or could combine
+     * buffers? } }
+     *
+     * @Override public U onResponseComplete() throws IOException {
+     * subscriber.onComplete(); return bodySupplier.get(); } }; }
+     */
+    private static final ByteBuffer EOF = ByteBuffer.allocate(0);
+    private static final ByteBuffer CLOSED = ByteBuffer.allocate(0);
+
+    // prototype using ByteBuffer based flow control. InputStream feeds off a
+    // BlockingQueue. Size of Q is determined from the the bufsize (bytes) and
+    // the default ByteBuffer size. bufsize should be a reasonable multiple of
+    // ByteBuffer size to prevent underflow/starvation. The InputStream updates
+    // the flowControl window by one as each ByteBuffer is fully consumed.
+    // Special sentinels are used to indicate stream closed and EOF.
+    /**
+     * Returns a response body processor which provides an InputStream to read
+     * the body.
+     *
+     * @implNote This mechanism is provided primarily for backwards
+     * compatibility for code that expects InputStream. It is recommended for
+     * better performance to use one of the other response processor
+     * implementations.
+     *
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<InputStream> asInputStream() {
+        return new BodyProcessor<InputStream>() {
+            int queueSize = 2;
+            private volatile Throwable throwable;
+
+            BlockingQueue<ByteBuffer> queue  = new LinkedBlockingQueue<>();
+
+            private void closeImpl() {
+                try {
+                    queue.put(CLOSED);
+                } catch (InterruptedException e) { }
+            }
+
+            @Override
+            public InputStream onResponseBodyStart(long contentLength,
+                                                   HttpHeaders responseHeaders,
+                                                   LongConsumer flowController)
+                throws IOException
+            {
+                flowController.accept(queueSize);
+
+                return new InputStream() {
+                    ByteBuffer buffer;
+
+                    @Override
+                    public int read() throws IOException {
+                        byte[] bb = new byte[1];
+                        int n = read(bb, 0, 1);
+                        if (n == -1) {
+                            return -1;
+                        } else {
+                            return bb[0];
+                        }
+                    }
+
+                    @Override
+                    public int read(byte[] bb) throws IOException {
+                        return read(bb, 0, bb.length);
+                    }
+
+                    @Override
+                    public int read(byte[] bb, int offset, int length)
+                        throws IOException
+                    {
+                        int n;
+                        if (getBuffer()) {
+                            return -1; // EOF
+                        } else {
+                            int remaining = buffer.remaining();
+                            if (length >= remaining) {
+                                buffer.get(bb, offset, remaining);
+                                return remaining;
+                            } else {
+                                buffer.get(bb, offset, length);
+                                return length;
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void close() {
+                        closeImpl();
+                    }
+
+                    private boolean getBuffer() throws IOException {
+                        while (buffer == null || (buffer != EOF &&
+                                buffer != CLOSED && !buffer.hasRemaining())) {
+                            try {
+                                buffer = queue.take();
+                                flowController.accept(1);
+                            } catch (InterruptedException e) {
+                                throw new IOException(e);
+                            }
+                        }
+                        if (buffer == CLOSED) {
+                            if (throwable != null) {
+                                if (throwable instanceof IOException) {
+                                    throw (IOException) throwable;
+                                } else {
+                                    throw new IOException(throwable);
+                                }
+                            }
+                            throw new IOException("Closed");
+                        }
+
+                        if (buffer == EOF) {
+                            return true; // EOF
+                        }
+                        return false; // not EOF
+                    }
+
+                };
+            }
+
+            @Override
+            public void onResponseError(Throwable t) {
+                throwable = t;
+                closeImpl();
+            }
+
+            @Override
+            public void onResponseBodyChunk(ByteBuffer b) throws IOException {
+                try {
+                    queue.put(Utils.copy(b));
+                } catch (InterruptedException e) {
+                    // shouldn't happen as queue should never block
+                    throw new IOException(e);
+                }
+            }
+
+            @Override
+            public InputStream onResponseComplete() throws IOException {
+                try {
+                    queue.put(EOF);
+                } catch (InterruptedException e) {
+                    throw new IOException(e); // can't happen
+                }
+                return null;
+            }
+
+        };
+    }
+
+    /**
+     * Common super class that takes care of flow control
+     *
+     * @param <T>
+     */
+    private static abstract class AbstractResponseProcessor<T>
+        implements BodyProcessor<T>
+    {
+        LongConsumer flowController;
+
+        @Override
+        public final T onResponseBodyStart(long contentLength,
+                                           HttpHeaders responseHeaders,
+                                           LongConsumer flowController)
+            throws IOException
+        {
+            this.flowController = flowController;
+            flowController.accept(1);
+            return onResponseBodyStartImpl(contentLength, responseHeaders);
+        }
+
+        public abstract T onResponseBodyStartImpl(long contentLength,
+                                                  HttpHeaders responseHeaders)
+            throws IOException;
+
+        public abstract void onResponseBodyChunkImpl(ByteBuffer b)
+            throws IOException;
+
+        @Override
+        public final void onResponseBodyChunk(ByteBuffer b) throws IOException {
+            onResponseBodyChunkImpl(b);
+            flowController.accept(1);
+        }
+    }
+
+    /**
+     * Returns a {@link BodyProcessor}&lt;byte[]&gt; which returns the response
+     * body as a {@code byte array}.
+     *
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<byte[]> asByteArray() {
+        ByteArrayResponseProcessor brp = new ByteArrayResponseProcessor();
+
+        return new AbstractResponseProcessor<byte[]>() {
+
+            @Override
+            public byte[] onResponseBodyStartImpl(long contentLength,
+                                                  HttpHeaders h)
+                throws IOException
+            {
+                brp.onStart(contentLength);
+                return null;
+            }
+
+            @Override
+            public void onResponseBodyChunkImpl(ByteBuffer b)
+                throws IOException
+            {
+                brp.onBodyContent(b);
+            }
+
+            @Override
+            public byte[] onResponseComplete() throws IOException {
+                return brp.onComplete();
+            }
+
+            @Override
+            public void onResponseError(Throwable t) {
+                brp.onError(t);
+            }
+        };
+    }
+
+    /**
+     * Returns a response processor which decodes the body using the character
+     * set specified in the {@code Content-encoding} response header. If there
+     * is no such header, or the character set is not supported, then
+     * {@link java.nio.charset.StandardCharsets#ISO_8859_1 ISO_8859_1} is used.
+     *
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<String> asString() {
+        return asString(null);
+    }
+
+    /**
+     * Returns a MultiProcessor that handles multiple responses, writes the
+     * response bodies to files and which returns an aggregate response object
+     * that is a {@code Map<URI,Path>}. The keyset of the Map represents the
+     * URIs of the original request and any additional requests generated by the
+     * server. The values are the paths of the destination files. Each path uses
+     * the URI path of the request offset from the destination parent directory
+     * provided.
+     *
+     * <p> All incoming additional requests (push promises) are accepted by this
+     * multi response processor. Errors are effectively ignored and any failed
+     * responses are simply omitted from the result Map. Other implementations
+     * of MultiProcessor can handle these situations
+     *
+     * <p><b>Example usage</b>
+     * <pre>
+     * {@code
+     *    CompletableFuture<Map<URI,Path>> cf =
+     *    HttpRequest.create(new URI("https://www.foo.com/"))
+     *               .version(Version.HTTP2)
+     *               .GET()
+     *               .sendAsyncMulti(HttpResponse.multiFile("/usr/destination"));
+     *
+     *    Map<URI,Path> results = cf.join();
+     * }
+     * </pre>
+     *
+     * @param destination the destination parent directory of all response
+     * bodies
+     * @return a MultiProcessor
+     */
+    public static MultiProcessor<Map<URI, Path>> multiFile(Path destination) {
+
+        return new MultiProcessor<Map<URI, Path>>() {
+            Map<URI, CompletableFuture<Path>> bodyCFs = new HashMap<>();
+
+            Map<URI, Path> results = new HashMap<>();
+
+            @Override
+            public BiFunction<HttpRequest, CompletableFuture<HttpResponse>, Boolean>
+            onStart(HttpRequest mainRequest,
+                    CompletableFuture<HttpResponse> response) {
+                bodyCFs.put(mainRequest.uri(), getBody(mainRequest, response));
+                return (HttpRequest additional, CompletableFuture<HttpResponse> cf) -> {
+                    CompletableFuture<Path> bcf = getBody(additional, cf);
+                    bodyCFs.put(additional.uri(), bcf);
+                    // we accept all comers
+                    return true;
+                };
+            }
+
+            private CompletableFuture<Path> getBody(HttpRequest req,
+                                                    CompletableFuture<HttpResponse> cf) {
+                URI u = req.uri();
+                String path = u.getPath();
+                return cf.thenCompose((HttpResponse resp) -> {
+                    return resp.bodyAsync(HttpResponse.asFile(destination.resolve(path)));
+                });
+            }
+
+            @Override
+            public Map<URI, Path> onComplete() {
+                // all CFs have completed normally or in error.
+                Set<Map.Entry<URI, CompletableFuture<Path>>> entries = bodyCFs.entrySet();
+                for (Map.Entry<URI, CompletableFuture<Path>> entry : entries) {
+                    CompletableFuture<Path> v = entry.getValue();
+                    URI uri = entry.getKey();
+                    if (v.isDone() && !v.isCompletedExceptionally()) {
+                        results.put(uri, v.join());
+                    }
+                }
+                return results;
+            }
+        };
+    }
+
+    /**
+     * Returns a {@link BodyProcessor}&lt;{@link String}&gt;.
+     *
+     * @param charset the name of the charset to interpret the body as. If
+     * {@code null} then the processor tries to determine the character set from
+     * the {@code Content-encoding} header. If that charset is not supported
+     * then {@link java.nio.charset.StandardCharsets#ISO_8859_1 ISO_8859_1} is
+     * used.
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<String> asString(Charset charset) {
+
+        ByteArrayResponseProcessor brp = new ByteArrayResponseProcessor();
+
+        return new AbstractResponseProcessor<String>() {
+            Charset cs = charset;
+            HttpHeaders headers;
+
+            @Override
+            public String onResponseBodyStartImpl(long contentLength,
+                                                  HttpHeaders h)
+                throws IOException
+            {
+                headers = h;
+                brp.onStart(contentLength);
+                return null;
+            }
+
+            @Override
+            public void onResponseBodyChunkImpl(ByteBuffer b) throws IOException {
+                brp.onBodyContent(b);
+            }
+
+            @Override
+            public String onResponseComplete() throws IOException {
+                byte[] buf = brp.onComplete();
+                if (cs == null) {
+                    cs = headers.firstValue("Content-encoding")
+                                .map((String s) -> Charset.forName(s))
+                                .orElse(StandardCharsets.ISO_8859_1);
+                }
+                return new String(buf, cs);
+            }
+
+            @Override
+            public void onResponseError(Throwable t) {
+                brp.onError(t);
+            }
+
+        };
+    }
+
+    /**
+     * Returns a response processor which ignores the response body.
+     *
+     * @return a {@code BodyProcessor}
+     */
+    public static BodyProcessor<Void> ignoreBody() {
+        return asByteArrayConsumer((byte[] buf) -> { /* ignore */ });
+    }
+
+    /**
+     * A processor for response bodies, which determines the type of the
+     * response body returned from {@link HttpResponse}. Response processors can
+     * either return an object that represents the body itself (after it has
+     * been read) or else an object that is used to read the body (such as an
+     * {@code InputStream}). The parameterized type {@code <T>} is the type of
+     * the returned body object from
+     * {@link HttpResponse#body(BodyProcessor) HttpResponse.body} and
+     * (indirectly) from {@link HttpResponse#bodyAsync(BodyProcessor)
+     * HttpResponse.bodyAsync}.
+     *
+     * <p> Implementations of this interface are provided in {@link HttpResponse}
+     * which write responses to {@code String, byte[], File, Consumer<byte[]>}.
+     * Custom implementations can also be used.
+     *
+     * <p> The methods of this interface may be called from multiple threads,
+     * but only one method is invoked at a time, and behaves as if called from
+     * one thread.
+     *
+     * @param <T> the type of the response body
+     *
+     * @since 9
+     */
+    public interface BodyProcessor<T> {
+
+        /**
+         * Called immediately before the response body is read. If {@code <T>}
+         * is an object used to read or accept the response body, such as a
+         * {@code Consumer} or {@code InputStream} then it should be returned
+         * from this method, and the body object will be returned before any
+         * data is read. If {@code <T>} represents the body itself after being
+         * read, then this method must return {@code null} and the body will be
+         * returned from {@link #onResponseComplete()}. In both cases, the
+         * actual body data is provided by the
+         * {@link #onResponseBodyChunk(ByteBuffer) onResponseBodyChunk} method
+         * in exactly the same way.
+         *
+         * <p> flowController is a consumer of long values and is used for
+         * updating a flow control window as follows. The window represents the
+         * number of times
+         * {@link #onResponseBodyChunk(java.nio.ByteBuffer) onResponseBodyChunk}
+         * may be called before receiving further updates to the window. Each
+         * time it is called, the window is reduced by {@code 1}. When the
+         * window reaches zero {@code onResponseBodyChunk()} will not be called
+         * again until the window has opened again with further calls to
+         * flowController.accept().
+         * {@link java.util.function.LongConsumer#accept(long) flowcontroller.accept()}
+         * must be called to open (increase) the window by the specified amount.
+         * The initial value is zero. This implies that if {@code
+         * onResponseBodyStart()} does not call {@code flowController.accept()}
+         * with a positive value no data will ever be delivered.
+         *
+         * @param contentLength {@code -1} signifies unknown content length.
+         *                      Otherwise, a positive integer, or zero.
+         * @param responseHeaders the response headers
+         * @param flowController a LongConsumer used to update the flow control
+         *                       window
+         * @return {@code null} or an object that can be used to read the
+         *         response body.
+         * @throws IOException if an exception occurs starting the response
+         *                     body receive
+         */
+        T onResponseBodyStart(long contentLength,
+                              HttpHeaders responseHeaders,
+                              LongConsumer flowController)
+            throws IOException;
+
+        /**
+         * Called if an error occurs while reading the response body. This
+         * terminates the operation and no further calls will occur after this.
+         *
+         * @param t the Throwable
+         */
+        void onResponseError(Throwable t);
+
+        /**
+         * Called for each buffer of data received for this response.
+         * ByteBuffers can be reused as soon as this method returns.
+         *
+         * @param b a ByteBuffer whose position is at the first byte that can be
+         *          read, and whose limit is after the last byte that can be read
+         * @throws IOException in case of I/O error
+         */
+        void onResponseBodyChunk(ByteBuffer b) throws IOException;
+
+        /**
+         * Called after the last time
+         * {@link #onResponseBodyChunk(java.nio.ByteBuffer)} has been called and
+         * returned indicating that the entire content has been read. This
+         * method must return an object that represents or contains the response
+         * body just received, but only if an object was not returned from
+         * {@link #onResponseBodyStart(long, HttpHeaders, LongConsumer)
+         * onResponseBodyStart}.
+         *
+         * @return a T, or {@code null} if an object was already returned
+         * @throws IOException in case of I/O error
+         */
+        T onResponseComplete() throws IOException;
+    }
+
+    /**
+     * A response processor for a HTTP/2 multi response. A multi response
+     * comprises a main response, and zero or more additional responses. Each
+     * additional response is sent by the server in response to requests that
+     * the server also generates. Additional responses are typically resources
+     * that the server guesses the client will need which are related to the
+     * initial request.
+     *
+     * <p>The server generated requests are also known as <i>push promises</i>.
+     * The server is permitted to send any number of these requests up to the
+     * point where the main response is fully received. Therefore, after
+     * completion of the main response body, the final number of additional
+     * responses is known. Additional responses may be cancelled, but given that
+     * the server does not wait for any acknowledgment before sending the
+     * response, this must be done quickly to avoid unnecessary data transmission.
+     *
+     * <p> {@code MultiProcessor}s are parameterised with a type {@code T} which
+     * represents some meaningful aggregate of the responses received. This
+     * would typically be a Collection of response or response body objects. One
+     * example implementation can be found at {@link
+     * HttpResponse#multiFile(java.nio.file.Path)}.
+     *
+     * @param <T> a type representing the aggregated results
+     *
+     * @since 9
+     */
+    public interface MultiProcessor<T> {
+
+        /**
+         * Called before or soon after a multi request is sent. The request that
+         * initiated the multi response is supplied, as well as a
+         * CompletableFuture for the main response. The implementation of this
+         * method must return a BiFunction which is called once for each push
+         * promise received.
+         *
+         * <p> The parameters to the {@code BiFunction} are the {@code HttpRequest}
+         * for the push promise and a {@code CompletableFuture} for its
+         * response. The function must return a Boolean indicating whether the
+         * push promise has been accepted (true) or should be canceled (false).
+         * The CompletableFutures for any canceled pushes are themselves
+         * completed exceptionally soon after the function returns.
+         *
+         * @param mainRequest the main request
+         * @param response a CompletableFuture for the main response
+         * @return a BiFunction that is called for each push promise
+         */
+        BiFunction<HttpRequest, CompletableFuture<HttpResponse>, Boolean>
+        onStart(HttpRequest mainRequest,
+                CompletableFuture<HttpResponse> response);
+
+        /**
+         * Called after all responses associated with the multi response have
+         * been fully processed, including response bodies.
+         *
+         * <p> Example types for {@code T} could be Collections of response body
+         * types or {@code Map}s from request {@code URI} to a response body
+         * type.
+         *
+         * @return the aggregate response object
+         */
+        T onComplete();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpResponseImpl.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.LongConsumer;
+import javax.net.ssl.SSLParameters;
+
+/**
+ * The implementation class for HttpResponse
+ */
+class HttpResponseImpl extends HttpResponse {
+
+    int responseCode;
+    Exchange exchange;
+    HttpRequestImpl request;
+    HttpHeaders1 headers;
+    HttpHeaders1 trailers;
+    SSLParameters sslParameters;
+    URI uri;
+    HttpClient.Version version;
+    AccessControlContext acc;
+    RawChannel rawchan;
+    HttpConnection connection;
+
+    public HttpResponseImpl(int responseCode, Exchange exch, HttpHeaders1 headers,
+            HttpHeaders1 trailers, SSLParameters sslParameters,
+            HttpClient.Version version, HttpConnection connection) {
+        this.responseCode = responseCode;
+        this.exchange = exch;
+        this.request = exchange.request();
+        this.headers = headers;
+        this.trailers = trailers;
+        this.sslParameters = sslParameters;
+        this.uri = request.uri();
+        this.version = version;
+        this.connection = connection;
+    }
+
+    @Override
+    public int statusCode() {
+        return responseCode;
+    }
+
+    @Override
+    public HttpRequestImpl request() {
+        return request;
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        headers.makeUnmodifiable();
+        return headers;
+    }
+
+    @Override
+    public HttpHeaders trailers() {
+        trailers.makeUnmodifiable();
+        return trailers;
+    }
+
+
+    @Override
+    public <T> T body(java.net.http.HttpResponse.BodyProcessor<T> processor) {
+        return exchange.responseBody(processor);
+    }
+
+    @Override
+    public <T> CompletableFuture<T> bodyAsync(java.net.http.HttpResponse.BodyProcessor<T> processor) {
+        acc = AccessController.getContext();
+        return exchange.responseBodyAsync(processor);
+    }
+
+    @Override
+    public SSLParameters sslParameters() {
+        return sslParameters;
+    }
+
+    public AccessControlContext getAccessControlContext() {
+        return acc;
+    }
+
+    @Override
+    public URI uri() {
+        return uri;
+    }
+
+    @Override
+    public HttpClient.Version version() {
+        return version;
+    }
+    // keepalive flag determines whether connection is closed or kept alive
+    // by reading/skipping data
+
+    public static java.net.http.HttpResponse.BodyProcessor<Void> ignoreBody(boolean keepalive) {
+        return new java.net.http.HttpResponse.BodyProcessor<Void>() {
+
+            @Override
+            public Void onResponseBodyStart(long clen, HttpHeaders h,
+                    LongConsumer flowController) throws IOException {
+                return null;
+            }
+
+            @Override
+            public void onResponseBodyChunk(ByteBuffer b) throws IOException {
+            }
+
+            @Override
+            public Void onResponseComplete() throws IOException {
+                return null;
+            }
+
+            @Override
+            public void onResponseError(Throwable t) {
+            }
+        };
+    }
+
+    /**
+     *
+     * @return
+     */
+    RawChannel rawChannel() {
+        if (rawchan == null) {
+            rawchan = new RawChannel(request.client(), connection);
+        }
+        return rawchan;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/HttpTimeoutException.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.io.IOException;
+
+/**
+ * Thrown when a response is not received within a specified time period.
+ */
+public class HttpTimeoutException extends IOException {
+
+    private static final long serialVersionUID = 981344271622632951L;
+
+    public HttpTimeoutException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Log.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.util.Locale;
+import sun.util.logging.PlatformLogger;
+
+/**
+ * -Djava.net.HttpClient.log=errors,requests,headers,frames[:type:type2:..],content
+ *
+ * Any of errors, requests, headers or content are optional.
+ *
+ * Other handlers may be added. All logging is at level INFO
+ *
+ * Logger name is "java.net.http.HttpClient"
+ */
+class Log {
+
+    final static String logProp = "java.net.http.HttpClient.log";
+
+    public static final int OFF = 0;
+    public static final int ERRORS = 0x1;
+    public static final int REQUESTS = 0x2;
+    public static final int HEADERS = 0x4;
+    public static final int CONTENT = 0x8;
+    public static final int FRAMES = 0x10;
+    public static final int SSL = 0x20;
+    static int logging;
+
+    // Frame types: "control", "data", "window", "all"
+    public static final int CONTROL = 1; // all except DATA and WINDOW_UPDATES
+    public static final int DATA = 2;
+    public static final int WINDOW_UPDATES = 4;
+    public static final int ALL = CONTROL| DATA | WINDOW_UPDATES;
+    static int frametypes;
+
+    static sun.util.logging.PlatformLogger logger;
+
+    static {
+        String s = Utils.getNetProperty(logProp);
+        if (s == null) {
+            logging = OFF;
+        } else {
+            String[] vals = s.split(",");
+            for (String val : vals) {
+                switch (val.toLowerCase(Locale.US)) {
+                    case "errors":
+                        logging |= ERRORS;
+                        break;
+                    case "requests":
+                        logging |= REQUESTS;
+                        break;
+                    case "headers":
+                        logging |= HEADERS;
+                        break;
+                    case "content":
+                        logging |= CONTENT;
+                        break;
+                    case "ssl":
+                        logging |= SSL;
+                        break;
+                    case "all":
+                        logging |= CONTENT|HEADERS|REQUESTS|FRAMES|ERRORS;
+                        break;
+                }
+                if (val.startsWith("frames")) {
+                    logging |= FRAMES;
+                    String[] types = val.split(":");
+                    if (types.length == 1) {
+                        frametypes = CONTROL | DATA | WINDOW_UPDATES;
+                    } else {
+                        for (String type : types) {
+                            switch (type.toLowerCase()) {
+                                case "control":
+                                    frametypes |= CONTROL;
+                                    break;
+                                case "data":
+                                    frametypes |= DATA;
+                                    break;
+                                case "window":
+                                    frametypes |= WINDOW_UPDATES;
+                                    break;
+                                case "all":
+                                    frametypes = ALL;
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (logging != OFF) {
+            logger = PlatformLogger.getLogger("java.net.http.HttpClient");
+        }
+    }
+
+    static boolean errors() {
+        return (logging & ERRORS) != 0;
+    }
+
+    static boolean requests() {
+        return (logging & REQUESTS) != 0;
+    }
+
+    static boolean headers() {
+        return (logging & HEADERS) != 0;
+    }
+
+    static boolean ssl() {
+        return (logging & SSL) != 0;
+    }
+
+    static boolean frames() {
+        return (logging & FRAMES) != 0;
+    }
+
+    static void logError(String s) {
+        if (errors())
+            logger.info("ERROR: " + s);
+    }
+
+    static void logError(Throwable t) {
+        if (errors()) {
+            String s = Utils.stackTrace(t);
+            logger.info("ERROR: " + s);
+        }
+    }
+
+    static void logSSL(String s) {
+        if (ssl())
+            logger.info("SSL: " + s);
+    }
+
+    static void logRequest(String s) {
+        if (requests())
+            logger.info("REQUEST: " + s);
+    }
+
+    static void logResponse(String s) {
+        if (requests())
+            logger.info("RESPONSE: " + s);
+    }
+
+    static void logHeaders(String s) {
+        if (headers())
+            logger.info("HEADERS: " + s);
+    }
+// END HTTP2
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/MultiExchange.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
+
+import static java.net.http.Pair.pair;
+
+/**
+ * Encapsulates multiple Exchanges belonging to one HttpRequestImpl.
+ * - manages filters
+ * - retries due to filters.
+ * - I/O errors and most other exceptions get returned directly to user
+ *
+ * Creates a new Exchange for each request/response interaction
+ */
+class MultiExchange {
+
+    final HttpRequestImpl request; // the user request
+    final HttpClientImpl client;
+    HttpRequestImpl currentreq; // used for async only
+    Exchange exchange; // the current exchange
+    Exchange previous;
+    int attempts;
+    // Maximum number of times a request will be retried/redirected
+    // for any reason
+
+    final static int DEFAULT_MAX_ATTEMPTS = 5;
+    final static int max_attempts = Utils.getIntegerNetProperty(
+            "sun.net.httpclient.redirects.retrylimit", DEFAULT_MAX_ATTEMPTS
+    );
+
+    private final List<HeaderFilter> filters;
+    TimedEvent td;
+    boolean cancelled = false;
+
+    /**
+     * Filter fields. These are attached as required by filters
+     * and only used by the filter implementations. This could be
+     * generalised into Objects that are passed explicitly to the filters
+     * (one per MultiExchange object, and one per Exchange object possibly)
+     */
+    volatile AuthenticationFilter.AuthInfo serverauth, proxyauth;
+    // RedirectHandler
+    volatile int numberOfRedirects = 0;
+
+    /**
+     */
+    MultiExchange(HttpRequestImpl request) {
+        this.exchange = new Exchange(request);
+        this.previous = null;
+        this.request = request;
+        this.currentreq = request;
+        this.attempts = 0;
+        this.client = request.client();
+        this.filters = client.filterChain();
+    }
+
+    public HttpResponseImpl response() throws IOException, InterruptedException {
+        HttpRequestImpl r = request;
+        if (r.timeval() != 0) {
+            // set timer
+            td = new TimedEvent(r.timeval());
+            client.registerTimer(td);
+        }
+        while (attempts < max_attempts) {
+            try {
+                attempts++;
+                Exchange currExchange = getExchange();
+                requestFilters(r);
+                HttpResponseImpl response = currExchange.response();
+                Pair<HttpResponse, HttpRequestImpl> filterResult = responseFilters(response);
+                HttpRequestImpl newreq = filterResult.second;
+                if (newreq == null) {
+                    if (attempts > 1) {
+                        Log.logError("Succeeded on attempt: " + attempts);
+                    }
+                    cancelTimer();
+                    return response;
+                }
+                response.body(HttpResponse.ignoreBody());
+                setExchange(new Exchange(newreq, currExchange.getAccessControlContext() ));
+                r = newreq;
+            } catch (IOException e) {
+                if (cancelled) {
+                    throw new HttpTimeoutException("Request timed out");
+                }
+                throw e;
+            }
+        }
+        cancelTimer();
+        throw new IOException("Retry limit exceeded");
+    }
+
+    private synchronized Exchange getExchange() {
+        return exchange;
+    }
+
+    private synchronized void setExchange(Exchange exchange) {
+        this.exchange = exchange;
+    }
+
+    private void cancelTimer() {
+        if (td != null) {
+            client.cancelTimer(td);
+        }
+    }
+
+    private void requestFilters(HttpRequestImpl r) throws IOException {
+        for (HeaderFilter filter : filters) {
+            filter.request(r);
+        }
+    }
+
+    // Filters are assumed to be non-blocking so the async
+    // versions of these methods just call the blocking ones
+
+    private CompletableFuture<Void> requestFiltersAsync(HttpRequestImpl r) {
+        CompletableFuture<Void> cf = new CompletableFuture<>();
+        try {
+            requestFilters(r);
+            cf.complete(null);
+        } catch(Throwable e) {
+            cf.completeExceptionally(e);
+        }
+        return cf;
+    }
+
+
+    private Pair<HttpResponse,HttpRequestImpl>
+    responseFilters(HttpResponse response) throws IOException
+    {
+        for (HeaderFilter filter : filters) {
+            HttpRequestImpl newreq = filter.response((HttpResponseImpl)response);
+            if (newreq != null) {
+                return pair(null, newreq);
+            }
+        }
+        return pair(response, null);
+    }
+
+    private CompletableFuture<Pair<HttpResponse,HttpRequestImpl>>
+    responseFiltersAsync(HttpResponse response)
+    {
+        CompletableFuture<Pair<HttpResponse,HttpRequestImpl>> cf = new CompletableFuture<>();
+        try {
+            Pair<HttpResponse,HttpRequestImpl> n = responseFilters(response); // assumed to be fast
+            cf.complete(n);
+        } catch (Throwable e) {
+            cf.completeExceptionally(e);
+        }
+        return cf;
+    }
+
+    public void cancel() {
+        cancelled = true;
+        getExchange().cancel();
+    }
+
+    public CompletableFuture<HttpResponseImpl> responseAsync(Void v) {
+        CompletableFuture<HttpResponseImpl> cf;
+        if (++attempts > max_attempts) {
+            cf = new CompletableFuture<>();
+            cf.completeExceptionally(new IOException("Too many retries"));
+        } else {
+            if (currentreq.timeval() != 0) {
+                // set timer
+                td = new TimedEvent(currentreq.timeval());
+                client.registerTimer(td);
+            }
+            Exchange exch = getExchange();
+            cf = requestFiltersAsync(currentreq)
+                .thenCompose(exch::responseAsync)
+                .thenCompose(this::responseFiltersAsync)
+                .thenCompose((Pair<HttpResponse,HttpRequestImpl> pair) -> {
+                    HttpResponseImpl resp = (HttpResponseImpl)pair.first;
+                    if (resp != null) {
+                        if (attempts > 1) {
+                            Log.logError("Succeeded on attempt: " + attempts);
+                        }
+                        return CompletableFuture.completedFuture(resp);
+                    } else {
+                        currentreq = pair.second;
+                        Exchange previous = exch;
+                        setExchange(new Exchange(currentreq,
+                                                 currentreq.getAccessControlContext()));
+                        //reads body off previous, and then waits for next response
+                        return previous
+                                .responseBodyAsync(HttpResponse.ignoreBody())
+                                .thenCompose(this::responseAsync);
+                    }
+                })
+            .handle((BiFunction<HttpResponse, Throwable, Pair<HttpResponse, Throwable>>) Pair::new)
+            .thenCompose((Pair<HttpResponse,Throwable> obj) -> {
+                HttpResponseImpl response = (HttpResponseImpl)obj.first;
+                if (response != null) {
+                    return CompletableFuture.completedFuture(response);
+                }
+                // all exceptions thrown are handled here
+                CompletableFuture<HttpResponseImpl> error = getExceptionalCF(obj.second);
+                if (error == null) {
+                    cancelTimer();
+                    return responseAsync(null);
+                } else {
+                    return error;
+                }
+            });
+        }
+        return cf;
+    }
+
+    /**
+     * Take a Throwable and return a suitable CompletableFuture that is
+     * completed exceptionally.
+     */
+    private CompletableFuture<HttpResponseImpl> getExceptionalCF(Throwable t) {
+        CompletableFuture<HttpResponseImpl> error = new CompletableFuture<>();
+        if ((t instanceof CompletionException) || (t instanceof ExecutionException)) {
+            if (t.getCause() != null) {
+                t = t.getCause();
+            }
+        }
+        if (cancelled && t instanceof IOException) {
+            t = new HttpTimeoutException("request timed out");
+        }
+        error.completeExceptionally(t);
+        return error;
+    }
+
+    <T> T responseBody(HttpResponse.BodyProcessor<T> processor) {
+        return getExchange().responseBody(processor);
+    }
+
+    <T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
+        return getExchange().responseBodyAsync(processor);
+    }
+
+    class TimedEvent extends TimeoutEvent {
+        TimedEvent(long timeval) {
+            super(timeval);
+        }
+        @Override
+        public void handle() {
+            cancel();
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Pair.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2016 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  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  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  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 java.net.http;
+
+/**
+ * A simple paired value class
+ */
+final class Pair<T, U> {
+
+    Pair(T first, U second) {
+        this.second = second;
+        this.first = first;
+    }
+
+    final T first;
+    final U second;
+
+    // Because 'pair()' is shorter than 'new Pair<>()'.
+    // Sometimes this difference might be very significant (especially in a
+    // 80-ish characters boundary). Sorry diamond operator.
+    static <T, U> Pair<T, U> pair(T first, U second) {
+        return new Pair<>(first, second);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/PlainHttpConnection.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.StandardSocketOptions;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Plain raw TCP connection direct to destination
+ */
+class PlainHttpConnection extends HttpConnection {
+
+    protected SocketChannel chan;
+    private volatile boolean connected;
+    private boolean closed;
+
+    class ConnectEvent extends AsyncEvent implements AsyncEvent.Blocking {
+        CompletableFuture<Void> cf;
+
+        ConnectEvent(CompletableFuture<Void> cf) {
+            this.cf = cf;
+        }
+
+        @Override
+        public SelectableChannel channel() {
+            return chan;
+        }
+
+        @Override
+        public int interestOps() {
+            return SelectionKey.OP_CONNECT;
+        }
+
+        @Override
+        public void handle() {
+            try {
+                chan.finishConnect();
+            } catch (IOException e) {
+                cf.completeExceptionally(e);
+            }
+            connected = true;
+            cf.complete(null);
+        }
+
+        @Override
+        public void abort() {
+            close();
+        }
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        CompletableFuture<Void> plainFuture = new CompletableFuture<>();
+        try {
+            chan.configureBlocking(false);
+            chan.connect(address);
+            client.registerEvent(new ConnectEvent(plainFuture));
+        } catch (IOException e) {
+            plainFuture.completeExceptionally(e);
+        }
+        return plainFuture;
+    }
+
+    @Override
+    public void connect() throws IOException {
+        chan.connect(address);
+        connected = true;
+    }
+
+    @Override
+    SocketChannel channel() {
+        return chan;
+    }
+
+    PlainHttpConnection(InetSocketAddress addr, HttpClientImpl client) {
+        super(addr, client);
+        try {
+            this.chan = SocketChannel.open();
+            int bufsize = client.getReceiveBufferSize();
+            chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
+        } catch (IOException e) {
+            throw new InternalError(e);
+        }
+    }
+
+    @Override
+    long write(ByteBuffer[] buffers, int start, int number) throws IOException {
+        //debugPrint("Send", buffers, start, number);
+        return chan.write(buffers, start, number);
+    }
+
+    @Override
+    long write(ByteBuffer buffer) throws IOException {
+        //debugPrint("Send", buffer);
+        return chan.write(buffer);
+    }
+
+    @Override
+    public String toString() {
+        return "PlainHttpConnection: " + super.toString();
+    }
+
+    /**
+     * Close this connection
+     */
+    @Override
+    synchronized void close() {
+        if (closed)
+            return;
+        closed = true;
+        try {
+            Log.logError("Closing: " + toString());
+            //System.out.println("Closing: " + this);
+            chan.close();
+        } catch (IOException e) {}
+    }
+
+    @Override
+    protected ByteBuffer readImpl(int length) throws IOException {
+        ByteBuffer buf = getBuffer(); // TODO not using length
+        int n = chan.read(buf);
+        if (n == -1) {
+            return null;
+        }
+        buf.flip();
+        String s = "Receive (" + n + " bytes) ";
+        //debugPrint(s, buf);
+        return buf;
+    }
+
+    @Override
+    protected int readImpl(ByteBuffer buf) throws IOException {
+        int mark = buf.position();
+        int n = chan.read(buf);
+        if (n == -1) {
+            return -1;
+        }
+        Utils.flipToMark(buffer, mark);
+        String s = "Receive (" + n + " bytes) ";
+        //debugPrint(s, buf);
+        return n;
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return new ConnectionPool.CacheKey(address, null);
+    }
+
+    @Override
+    synchronized boolean connected() {
+        return connected;
+    }
+
+    class ReceiveResponseEvent extends AsyncEvent implements AsyncEvent.Blocking {
+        CompletableFuture<Void> cf;
+
+        ReceiveResponseEvent(CompletableFuture<Void> cf) {
+            this.cf = cf;
+        }
+        @Override
+        public SelectableChannel channel() {
+            return chan;
+        }
+
+        @Override
+        public void handle() {
+            cf.complete(null);
+        }
+
+        @Override
+        public int interestOps() {
+            return SelectionKey.OP_READ;
+        }
+
+        @Override
+        public void abort() {
+            close();
+        }
+    }
+
+    @Override
+    boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    boolean isProxied() {
+        return false;
+    }
+
+    @Override
+    CompletableFuture<Void> whenReceivingResponse() {
+        CompletableFuture<Void> cf = new CompletableFuture<>();
+        try {
+            client.registerEvent(new ReceiveResponseEvent(cf));
+        } catch (IOException e) {
+            cf.completeExceptionally(e);
+        }
+        return cf;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/PlainProxyConnection.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.net.InetSocketAddress;
+
+class PlainProxyConnection extends PlainHttpConnection {
+
+    PlainProxyConnection(InetSocketAddress proxy, HttpClientImpl client) {
+        super(proxy, client);
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return new ConnectionPool.CacheKey(null, address);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/PlainTunnelingConnection.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.net.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.security.AccessControlContext;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
+ * encrypt. Used by WebSockets. Subclassed in SSLTunnelConnection for encryption.
+ */
+class PlainTunnelingConnection extends HttpConnection {
+
+    final PlainHttpConnection delegate;
+    protected final InetSocketAddress proxyAddr;
+    private volatile boolean connected;
+    private final AccessControlContext acc;
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        return delegate.connectAsync()
+            .thenCompose((Void v) -> {
+                HttpRequestImpl req = new HttpRequestImpl(client, "CONNECT", address);
+                Exchange connectExchange = new Exchange(req, acc);
+                return connectExchange
+                    .responseAsyncImpl(delegate)
+                    .thenCompose((HttpResponse r) -> {
+                        CompletableFuture<Void> cf = new CompletableFuture<>();
+                        if (r.statusCode() != 200) {
+                            cf.completeExceptionally(new IOException("Tunnel failed"));
+                        } else {
+                            connected = true;
+                            cf.complete(null);
+                        }
+                        return cf;
+                    });
+            });
+    }
+
+    @Override
+    public void connect() throws IOException, InterruptedException {
+        delegate.connect();
+        HttpRequestImpl req = new HttpRequestImpl(client, "CONNECT", address);
+        Exchange connectExchange = new Exchange(req, acc);
+        HttpResponse r = connectExchange.responseImpl(delegate);
+        if (r.statusCode() != 200) {
+            throw new IOException("Tunnel failed");
+        }
+        connected = true;
+    }
+
+    @Override
+    boolean connected() {
+        return connected;
+    }
+
+    protected PlainTunnelingConnection(InetSocketAddress addr,
+                                       InetSocketAddress proxy,
+                                       HttpClientImpl client,
+                                       AccessControlContext acc) {
+        super(addr, client);
+        this.proxyAddr = proxy;
+        this.acc = acc;
+        delegate = new PlainHttpConnection(proxy, client);
+    }
+
+    @Override
+    SocketChannel channel() {
+        return delegate.channel();
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return new ConnectionPool.CacheKey(null, proxyAddr);
+    }
+
+    @Override
+    long write(ByteBuffer[] buffers, int start, int number) throws IOException {
+        return delegate.write(buffers, start, number);
+    }
+
+    @Override
+    long write(ByteBuffer buffer) throws IOException {
+        return delegate.write(buffer);
+    }
+
+    @Override
+    void close() {
+        delegate.close();
+        connected = false;
+    }
+
+    @Override
+    protected ByteBuffer readImpl(int length) throws IOException {
+        return delegate.readImpl(length);
+    }
+
+    @Override
+    CompletableFuture<Void> whenReceivingResponse() {
+        return delegate.whenReceivingResponse();
+    }
+
+    @Override
+    protected int readImpl(ByteBuffer buffer) throws IOException {
+        return delegate.readImpl(buffer);
+    }
+
+    @Override
+    boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    boolean isProxied() {
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/RawChannel.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SelectableChannel;
+
+/**
+ * Used to implement WebSocket. Each RawChannel corresponds to
+ * a TCP connection (SocketChannel) but is connected to a Selector
+ * and an ExecutorService for invoking the send and receive callbacks
+ * Also includes SSL processing.
+ */
+class RawChannel implements ByteChannel, GatheringByteChannel {
+
+    private final HttpClientImpl client;
+    private final HttpConnection connection;
+    private boolean closed;
+
+    private interface RawEvent {
+
+        /** must return the selector interest op flags OR'd. */
+        int interestOps();
+
+        /** called when event occurs. */
+        void handle();
+    }
+
+    interface BlockingEvent extends RawEvent { }
+
+    interface NonBlockingEvent extends RawEvent { }
+
+    RawChannel(HttpClientImpl client, HttpConnection connection) {
+        this.client = client;
+        this.connection = connection;
+    }
+
+    private class RawAsyncEvent extends AsyncEvent {
+
+        private final RawEvent re;
+
+        RawAsyncEvent(RawEvent re) {
+            this.re = re;
+        }
+
+        public SelectableChannel channel() {
+            return connection.channel();
+        }
+
+        // must return the selector interest op flags OR'd
+        public int interestOps() {
+            return re.interestOps();
+        }
+
+        // called when event occurs
+        public void handle() {
+            re.handle();
+        }
+
+        public void abort() {}
+    }
+
+    private class BlockingRawAsyncEvent extends RawAsyncEvent
+            implements AsyncEvent.Blocking {
+
+        BlockingRawAsyncEvent(RawEvent re) {
+            super(re);
+        }
+    }
+
+    private class NonBlockingRawAsyncEvent extends RawAsyncEvent
+            implements AsyncEvent.NonBlocking {
+
+        NonBlockingRawAsyncEvent(RawEvent re) {
+            super(re);
+        }
+    }
+
+    /*
+     * Register given event whose callback will be called once only.
+     * (i.e. register new event for each callback)
+     */
+    public void registerEvent(RawEvent event) throws IOException {
+        if (event instanceof BlockingEvent) {
+            client.registerEvent(new BlockingRawAsyncEvent(event));
+        } else if (event instanceof NonBlockingEvent) {
+            client.registerEvent(new NonBlockingRawAsyncEvent(event));
+        } else {
+            throw new InternalError();
+        }
+    }
+
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+        return connection.read(dst);
+    }
+
+    @Override
+    public boolean isOpen() {
+        return !closed;
+    }
+
+    @Override
+    public void close() throws IOException {
+        closed = true;
+        connection.close();
+    }
+
+    @Override
+    public long write(ByteBuffer[] src) throws IOException {
+        return connection.write(src, 0, src.length);
+    }
+
+    @Override
+    public long write(ByteBuffer[] src, int offset, int len)
+            throws IOException {
+        return connection.write(src, offset, len);
+    }
+
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+        return (int) connection.write(src);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/RedirectFilter.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+
+class RedirectFilter implements HeaderFilter {
+
+    HttpRequestImpl requestImpl;
+    HttpRequest request;
+    HttpClientImpl client;
+    String method;
+    final static int DEFAULT_MAX_REDIRECTS = 5;
+    URI uri;
+
+    final static int max_redirects = Utils.getIntegerNetProperty(
+            "sun.net.httpclient.redirects.retrylimit", DEFAULT_MAX_REDIRECTS
+    );
+
+    @Override
+    public void request(HttpRequestImpl r) throws IOException {
+        this.request = r;
+        this.client = r.getClient();
+        this.method = r.method();
+        this.requestImpl = r;
+        this.uri = r.uri();
+    }
+
+    @Override
+    public HttpRequestImpl response(HttpResponseImpl r) throws IOException {
+        return handleResponse(r);
+    }
+
+    /**
+     * checks to see if new request needed and returns it.
+     * Null means response is ok to return to user.
+     */
+    private HttpRequestImpl handleResponse(HttpResponseImpl r) {
+        int rcode = r.statusCode();
+        if (rcode == 200) {
+            return null;
+        }
+        if (rcode >= 300 && rcode <= 399) {
+            URI redir = getRedirectedURI(r.headers());
+            if (canRedirect(r) && ++r.request.exchange.numberOfRedirects < max_redirects) {
+                //System.out.println("Redirecting to: " + redir);
+                return new HttpRequestImpl(redir, request, client, method, requestImpl);
+            } else {
+                //System.out.println("Redirect: giving up");
+                return null;
+            }
+        }
+        return null;
+    }
+
+    private URI getRedirectedURI(HttpHeaders headers) {
+        URI redirectedURI;
+        redirectedURI = headers.firstValue("Location")
+                .map((s) -> URI.create(s))
+                .orElseThrow(() -> new UncheckedIOException(
+                        new IOException("Invalid redirection")));
+
+        // redirect could be relative to original URL, but if not
+        // then redirect is used.
+        redirectedURI = uri.resolve(redirectedURI);
+        return redirectedURI;
+    }
+
+    private boolean canRedirect(HttpResponse r) {
+        return requestImpl.followRedirectsImpl().redirect(r);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/ResponseContent.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Implements chunked/fixed transfer encodings of HTTP/1.1 responses.
+ */
+class ResponseContent {
+
+    final HttpResponse.BodyProcessor<?> userProcessor;
+    final HttpResponse.BodyProcessor<?> pusher;
+    final HttpConnection connection;
+    final int contentLength;
+    ByteBuffer buffer;
+    ByteBuffer lastBufferUsed;
+    final ResponseHeaders headers;
+    final Http1Response.FlowController flowController;
+
+    ResponseContent(HttpConnection connection,
+                    int contentLength,
+                    ResponseHeaders h,
+                    HttpResponse.BodyProcessor<?> userProcessor,
+                    Http1Response.FlowController flowController) {
+        this.userProcessor = userProcessor;
+        this.pusher = (HttpResponse.BodyProcessor)userProcessor;
+        this.connection = connection;
+        this.contentLength = contentLength;
+        this.headers = h;
+        this.flowController = flowController;
+    }
+
+    static final int LF = 10;
+    static final int CR = 13;
+    static final int SP = 0x20;
+    static final int BUF_SIZE = 1024;
+
+    boolean chunkedContent, chunkedContentInitialized;
+
+    private boolean contentChunked() throws IOException {
+        if (chunkedContentInitialized) {
+            return chunkedContent;
+        }
+        if (contentLength == -1) {
+            String tc = headers.firstValue("Transfer-Encoding")
+                               .orElse("");
+            if (!tc.equals("")) {
+                if (tc.equalsIgnoreCase("chunked")) {
+                    chunkedContent = true;
+                } else {
+                    throw new IOException("invalid content");
+                }
+            } else {
+                chunkedContent = false;
+            }
+        }
+        chunkedContentInitialized = true;
+        return chunkedContent;
+    }
+
+    /**
+     * Entry point for pusher. b is an initial ByteBuffer that may
+     * have some data in it. When this method returns, the body
+     * has been fully processed.
+     */
+    void pushBody(ByteBuffer b) throws IOException {
+        // TODO: check status
+        if (contentChunked()) {
+            pushBodyChunked(b);
+        } else {
+            pushBodyFixed(b);
+        }
+    }
+
+    // reads and returns chunklen. Position of chunkbuf is first byte
+    // of chunk on return. chunklen includes the CR LF at end of chunk
+    int readChunkLen() throws IOException {
+        chunklen = 0;
+        boolean cr = false;
+        while (true) {
+            getHunk();
+            int c = chunkbuf.get();
+            if (cr) {
+                if (c == LF) {
+                    return chunklen + 2;
+                } else {
+                    throw new IOException("invalid chunk header");
+                }
+            }
+            if (c == CR) {
+                cr = true;
+            } else {
+                int digit = toDigit(c);
+                chunklen = chunklen * 16 + digit;
+            }
+        }
+    }
+
+    int chunklen = -1;      // number of bytes in chunk (fixed)
+    int bytesremaining;     // number of bytes in chunk left to be read incl CRLF
+    int bytesread;
+    ByteBuffer chunkbuf;    // initialise
+
+    // make sure we have at least 1 byte to look at
+    private void getHunk() throws IOException {
+        while (chunkbuf == null || !chunkbuf.hasRemaining()) {
+
+            if (chunkbuf != null) {
+                connection.returnBuffer(chunkbuf);
+            }
+            chunkbuf = connection.read();
+        }
+    }
+
+    private void consumeBytes(int n) throws IOException {
+        getHunk();
+        while (n > 0) {
+            int e = Math.min(chunkbuf.remaining(), n);
+            chunkbuf.position(chunkbuf.position() + e);
+            n -= e;
+            if (n > 0)
+                getHunk();
+        }
+    }
+
+    /**
+     * Returns a ByteBuffer containing a chunk of data or a "hunk" of data
+     * (a chunk of a chunk if the chunk size is larger than our ByteBuffers).
+     */
+    ByteBuffer readChunkedBuffer() throws IOException {
+        if (chunklen == -1) {
+            // new chunk
+            bytesremaining = readChunkLen();
+            chunklen = bytesremaining - 2;
+            if (chunklen == 0) {
+                consumeBytes(2);
+                return null;
+            }
+        }
+
+        getHunk();
+        bytesread = chunkbuf.remaining();
+        ByteBuffer returnBuffer;
+
+        /**
+         * Cases. Always at least one byte is read by getHunk()
+         *
+         * 1) one read contains exactly 1 chunk. Strip off CRLF and pass buffer on
+         * 2) one read contains a hunk. If at end of chunk, consume CRLF.Pass buffer up.
+         * 3) read contains rest of chunk and more data. Copy buffer.
+         */
+        if (bytesread == bytesremaining) {
+            // common case: 1 read = 1 chunk (or final hunk of chunk)
+            chunkbuf.limit(chunkbuf.limit() - 2); // remove trailing CRLF
+            bytesremaining = 0;
+            returnBuffer = chunkbuf;
+            chunkbuf = null;
+            chunklen = -1;
+        } else if (bytesread < bytesremaining) {
+            // read a hunk, maybe including CR or LF or both
+            bytesremaining -= bytesread;
+            if (bytesremaining <= 2) {
+                // remove any trailing CR LF already read, and then read the rest
+                chunkbuf.limit(chunkbuf.limit() - (2 - bytesremaining));
+                consumeBytes(bytesremaining);
+                chunklen = -1;
+            }
+            returnBuffer = chunkbuf;
+            chunkbuf = null;
+        } else {
+            // bytesread > bytesremaining
+            returnBuffer = splitChunkedBuffer(bytesremaining-2);
+            bytesremaining = 0;
+            chunklen = -1;
+            consumeBytes(2);
+        }
+        return returnBuffer;
+    }
+
+    ByteBuffer initialBuffer;
+    int fixedBytesReturned;
+
+    ByteBuffer getResidue() {
+        return lastBufferUsed;
+    }
+
+    private void compactBuffer(ByteBuffer buf) {
+        buf.compact()
+           .flip();
+    }
+
+    /**
+     * Copies inbuf (numBytes from its position) to new buffer. The returned
+     * buffer's position is zero and limit is at end (numBytes)
+     */
+    private ByteBuffer copyBuffer(ByteBuffer inbuf, int numBytes) {
+        ByteBuffer b1 = connection.getBuffer();
+        assert b1.remaining() >= numBytes;
+        byte[] b = b1.array();
+        inbuf.get(b, 0, numBytes);
+        b1.limit(numBytes);
+        return b1;
+    }
+
+    /**
+     * Split numBytes of data out of chunkbuf from the remainder,
+     * copying whichever part is smaller. chunkbuf points to second part
+     * of buffer on return. The returned buffer is the data from position
+     * to position + numBytes. Both buffers positions are reset so same
+     * data can be re-read.
+     */
+    private ByteBuffer splitChunkedBuffer(int numBytes) {
+        ByteBuffer newbuf = connection.getBuffer();
+        byte[] b = newbuf.array();
+        int midpoint = chunkbuf.position() + numBytes;
+        int remainder = chunkbuf.limit() - midpoint;
+
+        if (numBytes < remainder) {
+            // copy first part of chunkbuf to new buf
+            chunkbuf.get(b, 0, numBytes);
+            newbuf.limit(numBytes);
+            return newbuf;
+        } else {
+            // copy remainder of chunkbuf to newbuf and make newbuf chunkbuf
+            chunkbuf.mark();
+            chunkbuf.position(midpoint);
+            chunkbuf.get(b, 0, remainder);
+            chunkbuf.reset();
+            chunkbuf.limit(midpoint);
+            newbuf.limit(remainder);
+            newbuf.position(0);
+            ByteBuffer tmp = chunkbuf;
+            chunkbuf = newbuf;
+            return tmp;
+        }
+    }
+
+    private void pushBodyChunked(ByteBuffer b) throws IOException {
+        chunkbuf = b;
+        while (true) {
+            ByteBuffer b1 = readChunkedBuffer();
+            if (b1 != null) {
+                if (b1.hasRemaining()) {
+                    request(1); // wait till we can send
+                    pusher.onResponseBodyChunk(b1);
+                    lastBufferUsed = b1;
+                }
+            } else {
+                return;
+            }
+        }
+    }
+
+    private int toDigit(int b) throws IOException {
+        if (b >= 0x30 && b <= 0x39) {
+            return b - 0x30;
+        }
+        if (b >= 0x41 && b <= 0x46) {
+            return b - 0x41 + 10;
+        }
+        if (b >= 0x61 && b <= 0x66) {
+            return b - 0x61 + 10;
+        }
+        throw new IOException("Invalid chunk header byte " + b);
+    }
+
+    private void request(long value) throws IOException {
+        try {
+            flowController.request(value);
+        } catch (InterruptedException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void pushBodyFixed(ByteBuffer b) throws IOException {
+        lastBufferUsed = b;
+        for (int remaining = contentLength; remaining > 0;) {
+            int bufsize = b.remaining();
+            if (bufsize > remaining) {
+                // more data available than required, must copy
+                lastBufferUsed = b;
+                b = copyBuffer(b, remaining);
+                remaining = 0;
+            } else {
+                // pass entire buffer up to user
+                remaining -= bufsize;
+                compactBuffer(b);
+            }
+            request(1); // wait till we can send
+            pusher.onResponseBodyChunk(b);
+            if (remaining > 0) {
+                b = connection.read();
+                if (b == null) {
+                    throw new IOException("Error reading response");
+                }
+                lastBufferUsed = b;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/ResponseHeaders.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Reads response headers off channel, in blocking mode. Entire header
+ * block is collected in a byte[]. The offset location of the start of
+ * each header name is recorded in an array to facilitate later searching.
+ *
+ * The location of "Content-length" is recorded explicitly. Similar approach
+ * could be taken for other common headers.
+ *
+ * This class is not thread-safe
+ */
+class ResponseHeaders implements HttpHeaders1 {
+
+    static final int DATA_SIZE = 16 * 1024;  // initial space for headers
+    static final int NUM_HEADERS = 50; // initial expected max number of headers
+
+    final HttpConnection connection;
+    byte[] data;
+    int contentlen = -2; // means not initialized
+    ByteBuffer buffer;
+
+    /**
+     * Following used for scanning the array looking for:
+     *      - well known headers
+     *      - end of header block
+     */
+    int[] headerOffsets; // index into data
+    int numHeaders;
+    int count;
+
+    ByteBuffer residue; // after headers processed, data may be here
+
+    ResponseHeaders(HttpConnection connection, ByteBuffer buffer) {
+        this.connection = connection;
+        initOffsets();
+        this.buffer = buffer;
+        data = new byte[DATA_SIZE];
+    }
+
+    int getContentLength() throws IOException {
+        if (contentlen != -2) {
+            return contentlen;
+        }
+        int[] search = findHeaderValue("Content-length");
+        if (search[0] == -1) {
+            contentlen = -1;
+            return -1;
+        }
+
+        int i = search[0];
+
+        while (data[i] == ' ' || data[i] == '\t') {
+            i++;
+            if (i == data.length || data[i] == CR || data[i] == LF) {
+                throw new IOException("Bad header");
+            }
+        }
+        contentlen = 0;
+        int digit = data[i++] - 0x30;
+        while (digit >= 0 && digit <= 9) {
+            contentlen = contentlen * 10 + digit;
+            digit = data[i++] - 0x30;
+        }
+        return contentlen;
+    }
+
+    void log() {
+        populateMap(false);
+    }
+
+    void  populateMap(boolean clearOffsets) {
+        StringBuilder sb;
+
+        for (int i = 0; i < numHeaders; i++) {
+            sb = new StringBuilder(32);
+            int offset = headerOffsets[i];
+            if (offset == -1) {
+                continue;
+            }
+            int j;
+            for (j=0; data[offset+j] != ':'; j++) {
+                // byte to char promotion ok for US-ASCII
+                sb.append((char)data[offset+j]);
+            }
+            String name = sb.toString();
+            List<String> l = getOrCreate(name);
+            addEntry(l, name, offset + j + 1);
+            // clear the offset
+            if (clearOffsets)
+                headerOffsets[i] = -1;
+        }
+    }
+
+    void addEntry(List<String> l, String name, int j) {
+
+        while (data[j] == ' ' || data[j] == '\t') {
+            j++;
+        }
+
+        int vstart = j;
+            // TODO: back slash ??
+
+        while (data[j] != CR) {
+            j++;
+        }
+        try {
+            String value = new String(data, vstart, j - vstart, "US-ASCII");
+            l.add(value);
+        } catch (UnsupportedEncodingException e) {
+            // can't happen
+            throw new InternalError(e);
+        }
+    }
+
+    // returns an int[2]: [0] = offset of value in data[]
+    // [1] = offset in headerOffsets. Both are -1 in error
+
+    private int[] findHeaderValue(String name) {
+        int[] result = new int[2];
+        byte[] namebytes = getBytes(name);
+
+ outer: for (int i = 0; i < numHeaders; i++) {
+            int offset = headerOffsets[i];
+            if (offset == -1) {
+                continue;
+            }
+
+            for (int j=0; j<namebytes.length; j++) {
+                if (namebytes[j] != lowerCase(data[offset+j])) {
+                    continue outer;
+                }
+            }
+            // next char must be ':'
+            if (data[offset+namebytes.length] != ':') {
+                continue;
+            }
+            result[0] = offset+namebytes.length + 1;
+            result[1] = i;
+            return result;
+        }
+        result[0] = -1;
+        result[1] = -1;
+        return result;
+    }
+
+    /**
+     * Populates the map for header values with the given name.
+     * The offsets are cleared for any that are found, so they don't
+     * get repeatedly searched.
+     */
+    List<String> populateMapEntry(String name) {
+        List<String> l = getOrCreate(name);
+        int[] search = findHeaderValue(name);
+        if (search[0] != -1) {
+            addEntry(l, name, search[0]);
+            // clear the offset
+            headerOffsets[search[1]] = -1;
+        }
+        return l;
+    }
+
+    static final Locale usLocale = Locale.US;
+    static final Charset ascii = StandardCharsets.US_ASCII;
+
+    private byte[] getBytes(String name) {
+        return name.toLowerCase(usLocale).getBytes(ascii);
+    }
+
+    /*
+     * We read buffers in a loop until we detect end of headers
+     * CRLFCRLF. Each byte received is copied into the byte[] data
+     * The position of the first byte of each header (after a CRLF)
+     * is recorded in a separate array showing the location of
+     * each header name.
+     */
+    void initHeaders() throws IOException {
+
+        inHeaderName = true;
+        endOfHeader = true;
+
+        for (int numBuffers = 0; true; numBuffers++) {
+
+            if (numBuffers > 0) {
+                buffer = connection.read();
+            }
+
+            if (buffer == null) {
+                throw new IOException("Error reading headers");
+            }
+
+            if (!buffer.hasRemaining()) {
+                continue;
+            }
+
+            // Position set to first byte
+            int start = buffer.position();
+            byte[] backing = buffer.array();
+            int len = buffer.limit() - start;
+
+            for (int i = 0; i < len; i++) {
+                byte b = backing[i + start];
+                if (inHeaderName) {
+                    b = lowerCase(b);
+                }
+                if (b == ':') {
+                    inHeaderName = false;
+                }
+                data[count++] = b;
+                checkByte(b);
+                if (firstChar) {
+                    recordHeaderOffset(count-1);
+                    firstChar = false;
+                }
+                if (endOfHeader && numHeaders == 0) {
+                    // empty headers
+                    endOfAllHeaders = true;
+                }
+                if (endOfAllHeaders) {
+                    int newposition = i + 1 + start;
+                    if (newposition <= buffer.limit()) {
+                        buffer.position(newposition);
+                        residue = buffer;
+                    } else {
+                        residue = null;
+                    }
+                    return;
+                }
+
+                if (count == data.length) {
+                    resizeData();
+                }
+            }
+        }
+    }
+
+    static final int CR = 13;
+    static final int LF = 10;
+    int crlfCount = 0;
+
+    // results of checkByte()
+    boolean endOfHeader; // just seen LF after CR before
+    boolean endOfAllHeaders; // just seen LF after CRLFCR before
+    boolean firstChar; //
+    boolean inHeaderName; // examining header name
+
+    void checkByte(byte b) throws IOException {
+        if (endOfHeader &&  b != CR && b != LF)
+            firstChar = true;
+        endOfHeader = false;
+        endOfAllHeaders = false;
+        switch (crlfCount) {
+            case 0:
+                crlfCount = b == CR ? 1 : 0;
+                break;
+            case 1:
+                crlfCount = b == LF ? 2 : 0;
+                endOfHeader = true;
+                inHeaderName = true;
+                break;
+            case 2:
+                crlfCount = b == CR ? 3 : 0;
+                break;
+            case 3:
+                if (b != LF) {
+                    throw new IOException("Bad header block termination");
+                }
+                endOfAllHeaders = true;
+                break;
+        }
+    }
+
+    byte lowerCase(byte b) {
+        if (b >= 0x41 && b <= 0x5A)
+            b = (byte)(b + 32);
+        return b;
+    }
+
+    void resizeData() {
+        int oldlen = data.length;
+        int newlen = oldlen * 2;
+        byte[] newdata = new byte[newlen];
+        System.arraycopy(data, 0, newdata, 0, oldlen);
+        data = newdata;
+    }
+
+    final void initOffsets() {
+        headerOffsets = new int[NUM_HEADERS];
+        numHeaders = 0;
+    }
+
+    ByteBuffer getResidue() {
+        return residue;
+    }
+
+    void recordHeaderOffset(int index) {
+        if (numHeaders >= headerOffsets.length) {
+            int oldlen = headerOffsets.length;
+            int newlen = oldlen * 2;
+            int[] new1 = new int[newlen];
+            System.arraycopy(headerOffsets, 0, new1, 0, oldlen);
+            headerOffsets = new1;
+        }
+        headerOffsets[numHeaders++] = index;
+    }
+
+    /**
+     * As entries are read from the byte[] they are placed in here
+     * So we always check this map first
+     */
+    Map<String,List<String>> headers = new HashMap<>();
+
+    @Override
+    public Optional<String> firstValue(String name) {
+        List<String> l =  allValues(name);
+        if (l == null || l.isEmpty()) {
+            return Optional.ofNullable(null);
+        } else {
+            return Optional.of(l.get(0));
+        }
+    }
+
+    @Override
+    public List<String> allValues(String name) {
+        name = name.toLowerCase(usLocale);
+        List<String> l = headers.get(name);
+        if (l == null) {
+            l = populateMapEntry(name);
+        }
+        return Collections.unmodifiableList(l);
+    }
+
+    @Override
+    public void makeUnmodifiable() {
+    }
+
+    // Delegates map to HashMap but converts keys to lower case
+
+    static class HeaderMap implements Map<String,List<String>> {
+        Map<String,List<String>> inner;
+
+        HeaderMap(Map<String,List<String>> inner) {
+            this.inner = inner;
+        }
+        @Override
+        public int size() {
+            return inner.size();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return inner.isEmpty();
+        }
+
+        @Override
+        public boolean containsKey(Object key) {
+            if (!(key instanceof String)) {
+                return false;
+            }
+            String s = ((String)key).toLowerCase(usLocale);
+            return inner.containsKey(s);
+        }
+
+        @Override
+        public boolean containsValue(Object value) {
+            return inner.containsValue(value);
+        }
+
+        @Override
+        public List<String> get(Object key) {
+            String s = ((String)key).toLowerCase(usLocale);
+            return inner.get(s);
+        }
+
+        @Override
+        public List<String> put(String key, List<String> value) {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
+        public List<String> remove(Object key) {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
+        public void putAll(Map<? extends String, ? extends List<String>> m) {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
+        public void clear() {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
+        public Set<String> keySet() {
+            return inner.keySet();
+        }
+
+        @Override
+        public Collection<List<String>> values() {
+            return inner.values();
+        }
+
+        @Override
+        public Set<Entry<String, List<String>>> entrySet() {
+            return inner.entrySet();
+        }
+    }
+
+    @Override
+    public Map<String, List<String>> map() {
+        populateMap(true);
+        return new HeaderMap(headers);
+    }
+
+    Map<String, List<String>> mapInternal() {
+        populateMap(false);
+        return new HeaderMap(headers);
+    }
+
+    private List<String> getOrCreate(String name) {
+        List<String> l = headers.get(name);
+        if (l == null) {
+            l = new LinkedList<>();
+            headers.put(name, l);
+        }
+        return l;
+    }
+
+    @Override
+    public Optional<Long> firstValueAsLong(String name) {
+        List<String> l =  allValues(name);
+        if (l == null) {
+            return Optional.ofNullable(null);
+        } else {
+            String v = l.get(0);
+            Long lv = Long.parseLong(v);
+            return Optional.of(lv);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/SSLConnection.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CompletableFuture;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLParameters;
+import java.net.http.SSLDelegate.BufType;
+import java.net.http.SSLDelegate.WrapperResult;
+
+/**
+ * An SSL connection built on a Plain TCP connection.
+ */
+class SSLConnection extends HttpConnection {
+
+    PlainHttpConnection delegate;
+    SSLDelegate sslDelegate;
+    final String[] alpn;
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        return delegate.connectAsync()
+                .thenCompose((Void v) -> {
+                    CompletableFuture<Void> cf = new CompletableFuture<>();
+                    try {
+                        this.sslDelegate = new SSLDelegate(delegate.channel(),
+                                                           client,
+                                                           alpn);
+                        cf.complete(null);
+                    } catch (IOException e) {
+                        cf.completeExceptionally(e);
+                    }
+                    return cf;
+                });
+    }
+
+    @Override
+    public void connect() throws IOException {
+        delegate.connect();
+        this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn);
+    }
+
+    SSLConnection(InetSocketAddress addr, HttpClientImpl client, String[] ap) {
+        super(addr, client);
+        this.alpn = ap;
+        delegate = new PlainHttpConnection(addr, client);
+    }
+
+    @Override
+    SSLParameters sslParameters() {
+        return sslDelegate.getSSLParameters();
+    }
+
+    @Override
+    public String toString() {
+        return "SSLConnection: " + super.toString();
+    }
+
+    private static long countBytes(ByteBuffer[] buffers, int start, int length) {
+        long c = 0;
+        for (int i=0; i<length; i++) {
+            c+= buffers[start+i].remaining();
+        }
+        return c;
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return ConnectionPool.cacheKey(address, null);
+    }
+
+    @Override
+    long write(ByteBuffer[] buffers, int start, int number) throws IOException {
+        //debugPrint("Send", buffers, start, number);
+        long l = countBytes(buffers, start, number);
+        WrapperResult r = sslDelegate.sendData(buffers, start, number);
+        if (r.result.getStatus() == Status.CLOSED) {
+            if (l > 0) {
+                throw new IOException("SSLHttpConnection closed");
+            }
+        }
+        return l;
+    }
+
+    @Override
+    long write(ByteBuffer buffer) throws IOException {
+        //debugPrint("Send", buffer);
+        long l = buffer.remaining();
+        WrapperResult r = sslDelegate.sendData(buffer);
+        if (r.result.getStatus() == Status.CLOSED) {
+            if (l > 0) {
+                throw new IOException("SSLHttpConnection closed");
+            }
+        }
+        return l;
+    }
+
+    @Override
+    void close() {
+        try {
+            //System.err.println ("Closing: " + this);
+            delegate.channel().close(); // TODO: proper close
+        } catch (IOException ex) {
+            Log.logError(ex.toString());
+        }
+    }
+
+    @Override
+    protected ByteBuffer readImpl(int length) throws IOException {
+        ByteBuffer buf = sslDelegate.allocate(BufType.PACKET, length);
+        WrapperResult r = sslDelegate.recvData(buf);
+        // TODO: check for closure
+        String s = "Receive) ";
+        //debugPrint(s, r.buf);
+        return r.buf;
+    }
+
+    @Override
+    protected int readImpl(ByteBuffer buf) throws IOException {
+        // TODO: need to ensure that buf is big enough for application data
+        WrapperResult r = sslDelegate.recvData(buf);
+        // TODO: check for closure
+        String s = "Receive) ";
+        //debugPrint(s, r.buf);
+        return r.result.bytesProduced();
+    }
+
+    @Override
+    boolean connected() {
+        return delegate.connected();
+    }
+
+    @Override
+    SocketChannel channel() {
+        return delegate.channel();
+    }
+
+    @Override
+    CompletableFuture<Void> whenReceivingResponse() {
+        return delegate.whenReceivingResponse();
+    }
+
+    @Override
+    boolean isSecure() {
+        return true;
+    }
+
+    @Override
+    boolean isProxied() {
+        return false;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/SSLDelegate.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
+
+/**
+ * Implements the mechanics of SSL by managing an SSLEngine object.
+ * One of these is associated with each SSLConnection.
+ */
+class SSLDelegate {
+
+    final SSLEngine engine;
+    final EngineWrapper wrapper;
+    final Lock handshaking = new ReentrantLock();
+    final SSLParameters sslParameters;
+    final SocketChannel chan;
+    final HttpClientImpl client;
+
+    // alpn[] may be null
+    SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn)
+        throws IOException
+    {
+        SSLContext context = client.sslContext();
+        engine = context.createSSLEngine();
+        engine.setUseClientMode(true);
+        SSLParameters sslp = client.sslParameters().orElse(null);
+        if (sslp == null) {
+            sslp = context.getDefaultSSLParameters();
+        }
+        sslParameters = Utils.copySSLParameters(sslp);
+        if (alpn != null) {
+            sslParameters.setApplicationProtocols(alpn);
+            Log.logSSL("Setting application protocols: " + Arrays.toString(alpn));
+        } else {
+            Log.logSSL("Warning no application protocols proposed!");
+        }
+        engine.setSSLParameters(sslParameters);
+        wrapper = new EngineWrapper(chan, engine);
+        this.chan = chan;
+        this.client = client;
+    }
+
+    SSLParameters getSSLParameters() {
+        return sslParameters;
+    }
+
+    private static long countBytes(ByteBuffer[] buffers, int start, int number) {
+        long c = 0;
+        for (int i=0; i<number; i++) {
+            c+= buffers[start+i].remaining();
+        }
+        return c;
+    }
+
+
+    static class WrapperResult {
+        static WrapperResult createOK() {
+            WrapperResult r = new WrapperResult();
+            r.buf = null;
+            r.result = new SSLEngineResult(Status.OK, NOT_HANDSHAKING, 0, 0);
+            return r;
+        }
+        SSLEngineResult result;
+
+        /* if passed in buffer was not big enough then the a reallocated buffer
+         * is returned here  */
+        ByteBuffer buf;
+    }
+
+    int app_buf_size;
+    int packet_buf_size;
+
+    enum BufType {
+        PACKET,
+        APPLICATION
+    };
+
+    ByteBuffer allocate (BufType type) {
+        return allocate (type, -1);
+    }
+
+    // TODO: Use buffer pool for this
+    ByteBuffer allocate (BufType type, int len) {
+        assert engine != null;
+        synchronized (this) {
+            int size;
+            if (type == BufType.PACKET) {
+                if (packet_buf_size == 0) {
+                    SSLSession sess = engine.getSession();
+                    packet_buf_size = sess.getPacketBufferSize();
+                }
+                if (len > packet_buf_size) {
+                    packet_buf_size = len;
+                }
+                size = packet_buf_size;
+            } else {
+                if (app_buf_size == 0) {
+                    SSLSession sess = engine.getSession();
+                    app_buf_size = sess.getApplicationBufferSize();
+                }
+                if (len > app_buf_size) {
+                    app_buf_size = len;
+                }
+                size = app_buf_size;
+            }
+            return ByteBuffer.allocate (size);
+        }
+    }
+
+    /* reallocates the buffer by :-
+     * 1. creating a new buffer double the size of the old one
+     * 2. putting the contents of the old buffer into the new one
+     * 3. set xx_buf_size to the new size if it was smaller than new size
+     *
+     * flip is set to true if the old buffer needs to be flipped
+     * before it is copied.
+     */
+    private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {
+        synchronized (this) {
+            int nsize = 2 * b.capacity();
+            ByteBuffer n = allocate (type, nsize);
+            if (flip) {
+                b.flip();
+            }
+            n.put(b);
+            b = n;
+        }
+        return b;
+    }
+
+    /**
+     * This is a thin wrapper over SSLEngine and the SocketChannel, which
+     * guarantees the ordering of wraps/unwraps with respect to the underlying
+     * channel read/writes. It handles the UNDER/OVERFLOW status codes
+     * It does not handle the handshaking status codes, or the CLOSED status code
+     * though once the engine is closed, any attempt to read/write to it
+     * will get an exception.  The overall result is returned.
+     * It functions synchronously/blocking
+     */
+    class EngineWrapper {
+
+        SocketChannel chan;
+        SSLEngine engine;
+        Object wrapLock, unwrapLock;
+        ByteBuffer unwrap_src, wrap_dst;
+        boolean closed = false;
+        int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
+
+        EngineWrapper (SocketChannel chan, SSLEngine engine) throws IOException {
+            this.chan = chan;
+            this.engine = engine;
+            wrapLock = new Object();
+            unwrapLock = new Object();
+            unwrap_src = allocate(BufType.PACKET);
+            wrap_dst = allocate(BufType.PACKET);
+        }
+
+        void close () throws IOException {
+        }
+
+        WrapperResult wrapAndSend(ByteBuffer src, boolean ignoreClose)
+            throws IOException
+        {
+            ByteBuffer[] buffers = new ByteBuffer[1];
+            buffers[0] = src;
+            return wrapAndSend(buffers, 0, 1, ignoreClose);
+        }
+
+        /* try to wrap and send the data in src. Handles OVERFLOW.
+         * Might block if there is an outbound blockage or if another
+         * thread is calling wrap(). Also, might not send any data
+         * if an unwrap is needed.
+         */
+        WrapperResult wrapAndSend(ByteBuffer[] src,
+                                  int offset,
+                                  int len,
+                                  boolean ignoreClose)
+            throws IOException
+        {
+            if (closed && !ignoreClose) {
+                throw new IOException ("Engine is closed");
+            }
+            Status status;
+            WrapperResult r = new WrapperResult();
+            synchronized (wrapLock) {
+                wrap_dst.clear();
+                do {
+                    r.result = engine.wrap (src, offset, len, wrap_dst);
+                    status = r.result.getStatus();
+                    if (status == Status.BUFFER_OVERFLOW) {
+                        wrap_dst = realloc (wrap_dst, true, BufType.PACKET);
+                    }
+                } while (status == Status.BUFFER_OVERFLOW);
+                if (status == Status.CLOSED && !ignoreClose) {
+                    closed = true;
+                    return r;
+                }
+                if (r.result.bytesProduced() > 0) {
+                    wrap_dst.flip();
+                    int l = wrap_dst.remaining();
+                    assert l == r.result.bytesProduced();
+                    while (l>0) {
+                        l -= chan.write (wrap_dst);
+                    }
+                }
+            }
+            return r;
+        }
+
+        /* block until a complete message is available and return it
+         * in dst, together with the Result. dst may have been re-allocated
+         * so caller should check the returned value in Result
+         * If handshaking is in progress then, possibly no data is returned
+         */
+        WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
+            Status status;
+            WrapperResult r = new WrapperResult();
+            r.buf = dst;
+            if (closed) {
+                throw new IOException ("Engine is closed");
+            }
+            boolean needData;
+            if (u_remaining > 0) {
+                unwrap_src.compact();
+                unwrap_src.flip();
+                needData = false;
+            } else {
+                unwrap_src.clear();
+                needData = true;
+            }
+            synchronized (unwrapLock) {
+                int x;
+                do {
+                    if (needData) {
+                        do {
+                        x = chan.read (unwrap_src);
+                        } while (x == 0);
+                        if (x == -1) {
+                            throw new IOException ("connection closed for reading");
+                        }
+                        unwrap_src.flip();
+                    }
+                    r.result = engine.unwrap (unwrap_src, r.buf);
+                    status = r.result.getStatus();
+                    if (status == Status.BUFFER_UNDERFLOW) {
+                        if (unwrap_src.limit() == unwrap_src.capacity()) {
+                            /* buffer not big enough */
+                            unwrap_src = realloc (
+                                unwrap_src, false, BufType.PACKET
+                            );
+                        } else {
+                            /* Buffer not full, just need to read more
+                             * data off the channel. Reset pointers
+                             * for reading off SocketChannel
+                             */
+                            unwrap_src.position (unwrap_src.limit());
+                            unwrap_src.limit (unwrap_src.capacity());
+                        }
+                        needData = true;
+                    } else if (status == Status.BUFFER_OVERFLOW) {
+                        r.buf = realloc (r.buf, true, BufType.APPLICATION);
+                        needData = false;
+                    } else if (status == Status.CLOSED) {
+                        closed = true;
+                        r.buf.flip();
+                        return r;
+                    }
+                } while (status != Status.OK);
+            }
+            u_remaining = unwrap_src.remaining();
+            return r;
+        }
+    }
+
+    WrapperResult sendData (ByteBuffer src) throws IOException {
+        ByteBuffer[] buffers = new ByteBuffer[1];
+        buffers[0] = src;
+        return sendData(buffers, 0, 1);
+    }
+
+    /**
+     * send the data in the given ByteBuffer. If a handshake is needed
+     * then this is handled within this method. When this call returns,
+     * all of the given user data has been sent and any handshake has been
+     * completed. Caller should check if engine has been closed.
+     */
+    WrapperResult sendData (ByteBuffer[] src, int offset, int len) throws IOException {
+        WrapperResult r = WrapperResult.createOK();
+        while (countBytes(src, offset, len) > 0) {
+            r = wrapper.wrapAndSend(src, offset, len, false);
+            Status status = r.result.getStatus();
+            if (status == Status.CLOSED) {
+                doClosure ();
+                return r;
+            }
+            HandshakeStatus hs_status = r.result.getHandshakeStatus();
+            if (hs_status != HandshakeStatus.FINISHED &&
+                hs_status != HandshakeStatus.NOT_HANDSHAKING)
+            {
+                doHandshake(hs_status);
+            }
+        }
+        return r;
+    }
+
+    /**
+     * read data thru the engine into the given ByteBuffer. If the
+     * given buffer was not large enough, a new one is allocated
+     * and returned. This call handles handshaking automatically.
+     * Caller should check if engine has been closed.
+     */
+    WrapperResult recvData (ByteBuffer dst) throws IOException {
+        /* we wait until some user data arrives */
+        int mark = dst.position();
+        WrapperResult r = null;
+        assert dst.position() == 0;
+        while (dst.position() == 0) {
+            r = wrapper.recvAndUnwrap (dst);
+            dst = (r.buf != dst) ? r.buf: dst;
+            Status status = r.result.getStatus();
+            if (status == Status.CLOSED) {
+                doClosure ();
+                return r;
+            }
+
+            HandshakeStatus hs_status = r.result.getHandshakeStatus();
+            if (hs_status != HandshakeStatus.FINISHED &&
+                hs_status != HandshakeStatus.NOT_HANDSHAKING)
+            {
+                doHandshake (hs_status);
+            }
+        }
+        Utils.flipToMark(dst, mark);
+        return r;
+    }
+
+    /* we've received a close notify. Need to call wrap to send
+     * the response
+     */
+    void doClosure () throws IOException {
+        try {
+            handshaking.lock();
+            ByteBuffer tmp = allocate(BufType.APPLICATION);
+            WrapperResult r;
+            do {
+                tmp.clear();
+                tmp.flip ();
+                r = wrapper.wrapAndSend(tmp, true);
+            } while (r.result.getStatus() != Status.CLOSED);
+        } finally {
+            handshaking.unlock();
+        }
+    }
+
+    /* do the (complete) handshake after acquiring the handshake lock.
+     * If two threads call this at the same time, then we depend
+     * on the wrapper methods being idempotent. eg. if wrapAndSend()
+     * is called with no data to send then there must be no problem
+     */
+    @SuppressWarnings("fallthrough")
+    void doHandshake (HandshakeStatus hs_status) throws IOException {
+        boolean wasBlocking = false;
+        try {
+            wasBlocking = chan.isBlocking();
+            handshaking.lock();
+            chan.configureBlocking(true);
+            ByteBuffer tmp = allocate(BufType.APPLICATION);
+            while (hs_status != HandshakeStatus.FINISHED &&
+                   hs_status != HandshakeStatus.NOT_HANDSHAKING)
+            {
+                WrapperResult r = null;
+                switch (hs_status) {
+                    case NEED_TASK:
+                        Runnable task;
+                        while ((task = engine.getDelegatedTask()) != null) {
+                            /* run in current thread, because we are already
+                             * running an external Executor
+                             */
+                            task.run();
+                        }
+                        /* fall thru - call wrap again */
+                    case NEED_WRAP:
+                        tmp.clear();
+                        tmp.flip();
+                        r = wrapper.wrapAndSend(tmp, false);
+                        break;
+
+                    case NEED_UNWRAP:
+                        tmp.clear();
+                        r = wrapper.recvAndUnwrap (tmp);
+                        if (r.buf != tmp) {
+                            tmp = r.buf;
+                        }
+                        assert tmp.position() == 0;
+                        break;
+                }
+                hs_status = r.result.getHandshakeStatus();
+            }
+            Log.logSSL(getSessionInfo());
+            if (!wasBlocking) {
+                chan.configureBlocking(false);
+            }
+        } finally {
+            handshaking.unlock();
+        }
+    }
+
+    String getSessionInfo() {
+        StringBuilder sb = new StringBuilder();
+        String application = engine.getApplicationProtocol();
+        SSLSession sess = engine.getSession();
+        String cipher = sess.getCipherSuite();
+        String protocol = sess.getProtocol();
+        sb.append("Handshake complete alpn: ")
+                .append(application)
+                .append(", Cipher: ")
+                .append(cipher)
+                .append(", Protocol: ")
+                .append(protocol);
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/SSLTunnelConnection.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+package java.net.http;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.security.AccessControlContext;
+import java.util.concurrent.CompletableFuture;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLParameters;
+import java.net.http.SSLDelegate.BufType;
+import java.net.http.SSLDelegate.WrapperResult;
+
+/**
+ * An SSL tunnel built on a Plain (CONNECT) TCP tunnel.
+ */
+class SSLTunnelConnection extends HttpConnection {
+
+    final PlainTunnelingConnection delegate;
+    protected SSLDelegate sslDelegate;
+    private volatile boolean connected;
+
+    @Override
+    public void connect() throws IOException, InterruptedException {
+        delegate.connect();
+        this.sslDelegate = new SSLDelegate(delegate.channel(), client, null);
+        connected = true;
+    }
+
+    @Override
+    boolean connected() {
+        return connected && delegate.connected();
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        return delegate.connectAsync()
+            .thenAccept((Void v) -> {
+                try {
+                    // can this block?
+                    this.sslDelegate = new SSLDelegate(delegate.channel(),
+                                                       client,
+                                                       null);
+                    connected = true;
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            });
+    }
+
+    SSLTunnelConnection(InetSocketAddress addr,
+                        HttpClientImpl client,
+                        InetSocketAddress proxy,
+                        AccessControlContext acc) {
+        super(addr, client);
+        delegate = new PlainTunnelingConnection(addr, proxy, client, acc);
+    }
+
+    @Override
+    SSLParameters sslParameters() {
+        return sslDelegate.getSSLParameters();
+    }
+
+    @Override
+    public String toString() {
+        return "SSLTunnelConnection: " + super.toString();
+    }
+
+    private static long countBytes(ByteBuffer[] buffers, int start, int number) {
+        long c = 0;
+        for (int i=0; i<number; i++) {
+            c+= buffers[start+i].remaining();
+        }
+        return c;
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return ConnectionPool.cacheKey(address, delegate.proxyAddr);
+    }
+
+    @Override
+    long write(ByteBuffer[] buffers, int start, int number) throws IOException {
+        //debugPrint("Send", buffers, start, number);
+        long l = countBytes(buffers, start, number);
+        WrapperResult r = sslDelegate.sendData(buffers, start, number);
+        if (r.result.getStatus() == Status.CLOSED) {
+            if (l > 0) {
+                throw new IOException("SSLHttpConnection closed");
+            }
+        }
+        return l;
+    }
+
+    @Override
+    long write(ByteBuffer buffer) throws IOException {
+        //debugPrint("Send", buffer);
+        long l = buffer.remaining();
+        WrapperResult r = sslDelegate.sendData(buffer);
+        if (r.result.getStatus() == Status.CLOSED) {
+            if (l > 0) {
+                throw new IOException("SSLHttpConnection closed");
+            }
+        }
+        return l;
+    }
+
+    @Override
+    void close() {
+        try {
+            //System.err.println ("Closing: " + this);
+            delegate.channel().close(); // TODO: proper close
+        } catch (IOException ex) {
+        }
+    }
+
+    @Override
+    protected ByteBuffer readImpl(int length) throws IOException {
+        ByteBuffer buf = sslDelegate.allocate(BufType.PACKET, length);
+        WrapperResult r = sslDelegate.recvData(buf);
+        // TODO: check for closure
+        String s = "Receive) ";
+        //debugPrint(s, r.buf);
+        return r.buf;
+    }
+
+    @Override
+    protected int readImpl(ByteBuffer buf) throws IOException {
+        WrapperResult r = sslDelegate.recvData(buf);
+        // TODO: check for closure
+        String s = "Receive) ";
+        //debugPrint(s, r.buf);
+        return r.result.bytesProduced();
+    }
+
+    @Override
+    SocketChannel channel() {
+        return delegate.channel();
+    }
+
+    @Override
+    CompletableFuture<Void> whenReceivingResponse() {
+        return delegate.whenReceivingResponse();
+    }
+
+    @Override
+    boolean isSecure() {
+        return true;
+    }
+
+    @Override
+    boolean isProxied() {
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Stream.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2015, 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
+ */
+
+package java.net.http;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.function.LongConsumer;
+
+/**
+ * Http/2 Stream
+ */
+class Stream extends ExchangeImpl {
+
+    void debugPrint() {
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    <T> CompletableFuture<T> responseBodyAsync(HttpResponse.BodyProcessor<T> processor) {
+            return null;
+    }
+
+    Stream(HttpClientImpl client, Http2Connection connection, Exchange e) {
+        super(e);
+    }
+
+    @Override
+    HttpResponseImpl getResponse() throws IOException {
+        return null;
+    }
+
+    @Override
+    void sendRequest() throws IOException, InterruptedException {
+    }
+
+    @Override
+    void sendHeadersOnly() throws IOException, InterruptedException {
+    }
+
+    @Override
+    void sendBody() throws IOException, InterruptedException {
+    }
+
+    @Override
+    CompletableFuture<Void> sendHeadersAsync() {
+        return null;
+    }
+
+    @Override
+    CompletableFuture<HttpResponseImpl> getResponseAsync(Void v) {
+        return null;
+    }
+
+    @Override
+    CompletableFuture<Void> sendBodyAsync() {
+        return null;
+    }
+
+    @Override
+    void cancel() {
+    }
+
+
+    @Override
+    CompletableFuture<Void> sendRequestAsync() {
+        return null;
+    }
+
+    @Override
+    <T> T responseBody(HttpResponse.BodyProcessor<T> processor) throws IOException {
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/TimeoutEvent.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+/**
+ * Timeout event delivered by selector thread. Executes given handler
+ * if the timer not cancelled first.
+ *
+ * Register with HttpClientImpl.registerTimer(TimeoutEvent)
+ *
+ * Cancel with HttpClientImpl.cancelTimer(TimeoutEvent)
+ */
+abstract class TimeoutEvent {
+
+    final long timeval;
+
+    long delta; // used when on queue
+
+    TimeoutEvent(long timeval) { this.timeval = timeval; }
+
+    public abstract void handle();
+
+    /**  Returns the timevalue in milli-seconds */
+    public long timevalMillis() { return timeval; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/Utils.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+package java.net.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.net.NetPermission;
+import java.net.URI;
+import java.net.URLPermission;
+import java.nio.ByteBuffer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.net.ssl.SSLParameters;
+import sun.net.NetProperties;
+
+/**
+ * Miscellaneous utilities
+ */
+class Utils {
+
+    /**
+     * Allocated buffer size. Must never be higher than 16K. But can be lower
+     * if smaller allocation units preferred. HTTP/2 mandates that all
+     * implementations support frame payloads of at least 16K.
+     */
+    public static final int BUFSIZE = 16 * 1024;
+
+    /** Validates a RFC7230 token */
+    static void validateToken(String token, String errormsg) {
+        int length = token.length();
+        for (int i = 0; i < length; i++) {
+            int c = token.codePointAt(i);
+            if (c >= 0x30 && c <= 0x39 // 0 - 9
+                    || (c >= 0x61 && c <= 0x7a) // a - z
+                    || (c >= 0x41 && c <= 0x5a) // A - Z
+                    || (c >= 0x21 && c <= 0x2e && c != 0x22 && c != 0x27 && c != 0x2c)
+                    || (c >= 0x5e && c <= 0x60)
+                    || (c == 0x7c) || (c == 0x7e)) {
+            } else {
+                throw new IllegalArgumentException(errormsg);
+            }
+        }
+    }
+
+    /**
+     * Return sthe security permission required for the given details.
+     * If method is CONNECT, then uri must be of form "scheme://host:port"
+     */
+    static URLPermission getPermission(URI uri,
+                                       String method,
+                                       Map<String, List<String>> headers) {
+        StringBuilder sb = new StringBuilder();
+
+        String urlstring, actionstring;
+
+        if (method.equals("CONNECT")) {
+            urlstring = uri.toString();
+            actionstring = "CONNECT";
+        } else {
+            sb.append(uri.getScheme())
+                    .append("://")
+                    .append(uri.getHost())
+                    .append(uri.getPath());
+            urlstring = sb.toString();
+
+            sb = new StringBuilder();
+            sb.append(method);
+            if (headers != null && !headers.isEmpty()) {
+                sb.append(':');
+                Set<String> keys = headers.keySet();
+                boolean first = true;
+                for (String key : keys) {
+                    if (!first) {
+                        sb.append(',');
+                    }
+                    sb.append(key);
+                    first = false;
+                }
+            }
+            actionstring = sb.toString();
+        }
+        return new URLPermission(urlstring, actionstring);
+    }
+
+    static void checkNetPermission(String target) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm == null)
+            return;
+        NetPermission np = new NetPermission(target);
+        sm.checkPermission(np);
+    }
+
+    static int getIntegerNetProperty(String name, int defaultValue) {
+        return AccessController.doPrivileged((PrivilegedAction<Integer>)() ->
+            NetProperties.getInteger(name, defaultValue) );
+    }
+
+    static String getNetProperty(String name) {
+        return AccessController.doPrivileged((PrivilegedAction<String>)() ->
+            NetProperties.get(name) );
+    }
+
+    static SSLParameters copySSLParameters(SSLParameters p) {
+        SSLParameters p1 = new SSLParameters();
+        p1.setAlgorithmConstraints(p.getAlgorithmConstraints());
+        p1.setCipherSuites(p.getCipherSuites());
+        p1.setEnableRetransmissions(p.getEnableRetransmissions());
+        p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm());
+        p1.setMaximumPacketSize(p.getMaximumPacketSize());
+        p1.setNeedClientAuth(p.getNeedClientAuth());
+        p1.setProtocols(p.getProtocols().clone());
+        p1.setSNIMatchers(p.getSNIMatchers());
+        p1.setServerNames(p.getServerNames());
+        p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
+        p1.setWantClientAuth(p.getWantClientAuth());
+        return p1;
+    }
+
+
+    /** Resumes reading into the given buffer. */
+    static void unflip(ByteBuffer buf) {
+        buf.position(buf.limit());
+        buf.limit(buf.capacity());
+    }
+
+    /**
+     * Set limit to position, and position to mark.
+     *
+     *
+     * @param buffer
+     * @param mark
+     */
+    static void flipToMark(ByteBuffer buffer, int mark) {
+        buffer.limit(buffer.position());
+        buffer.position(mark);
+    }
+
+    /** Compact and leave ready for reading. */
+    static void compact(List<ByteBuffer> buffers) {
+        for (ByteBuffer b : buffers) {
+            b.compact();
+            b.flip();
+        }
+    }
+
+    static String stackTrace(Throwable t) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        String s = null;
+        try {
+            PrintStream p = new PrintStream(bos, true, "US-ASCII");
+            t.printStackTrace(p);
+            s = bos.toString("US-ASCII");
+        } catch (UnsupportedEncodingException ex) {
+            // can't happen
+        }
+        return s;
+    }
+
+    /** Copies as much of src to dst as possible. */
+    static void copy (ByteBuffer src, ByteBuffer dst) {
+        int srcLen = src.remaining();
+        int dstLen = dst.remaining();
+        if (srcLen > dstLen) {
+            int diff = srcLen - dstLen;
+            int limit = src.limit();
+            src.limit(limit - diff);
+            dst.put(src);
+            src.limit(limit);
+        } else {
+            dst.put(src);
+        }
+    }
+
+    static ByteBuffer copy(ByteBuffer src) {
+        ByteBuffer dst = ByteBuffer.allocate(src.remaining());
+        dst.put(src);
+        dst.flip();
+        return dst;
+    }
+
+    static String combine(String[] s) {
+        StringBuilder sb = new StringBuilder();
+        sb.append('[');
+        boolean first = true;
+        for (String s1 : s) {
+            if (!first) {
+                sb.append(", ");
+                first = false;
+            }
+            sb.append(s1);
+        }
+        sb.append(']');
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/java/net/http/package-info.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ */
+
+/**
+ * <h2>High level HTTP API</h2>
+ * This provides a high-level client interface to HTTP (versions 1.1 and 2).
+ * Synchronous and asynchronous (via
+ * {@link java.util.concurrent.CompletableFuture}) modes are provided. The main
+ * classes defined are:
+ * <ul>
+ *    <li>{@link java.net.http.HttpClient}</li>
+ *    <li>{@link java.net.http.HttpRequest}</li>
+ *    <li>{@link java.net.http.HttpResponse}</li>
+ * </ul>
+ *
+ * @since 9
+ */
+package java.net.http;
--- a/jdk/test/com/sun/net/httpserver/FileServerHandler.java	Thu Feb 25 11:27:30 2016 -0800
+++ b/jdk/test/com/sun/net/httpserver/FileServerHandler.java	Thu Feb 25 23:14:22 2016 +0000
@@ -23,6 +23,7 @@
 
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.logging.*;
 import java.io.*;
 import java.net.*;
 import java.security.*;
@@ -36,6 +37,10 @@
  * Must be given an abs pathname to the document root.
  * Directory listings together with text + html files
  * can be served.
+ *
+ * File Server created on files sub-path
+ *
+ * Echo server created on echo sub-path
  */
 public class FileServerHandler implements HttpHandler {
 
@@ -44,14 +49,24 @@
                 System.out.println ("usage: java FileServerHandler rootDir port logfilename");
                 System.exit(1);
             }
+            Logger logger = Logger.getLogger("com.sun.net.httpserver");
+            ConsoleHandler ch = new ConsoleHandler();
+            logger.setLevel(Level.ALL);
+            ch.setLevel(Level.ALL);
+            logger.addHandler(ch);
+
             String rootDir = args[0];
             int port = Integer.parseInt (args[1]);
             String logfile = args[2];
-            HttpServer server = HttpServer.create (new InetSocketAddress (8000), 0);
+            HttpServer server = HttpServer.create (new InetSocketAddress (port), 0);
             HttpHandler h = new FileServerHandler (rootDir);
+            HttpHandler h1 = new EchoHandler ();
 
-            HttpContext c = server.createContext ("/", h);
+            HttpContext c = server.createContext ("/files", h);
             c.getFilters().add (new LogFilter (new File (logfile)));
+            HttpContext c1 = server.createContext ("/echo", h1);
+            c.getFilters().add (new LogFilter (new File (logfile)));
+            c1.getFilters().add (new LogFilter (new File (logfile)));
             server.setExecutor (Executors.newCachedThreadPool());
             server.start ();
         }
@@ -72,7 +87,8 @@
             URI uri = t.getRequestURI();
             String path = uri.getPath();
 
-            while (is.read () != -1) ;
+            int x = 0;
+            while (is.read () != -1) x++;
             is.close();
             File f = new File (docroot, path);
             if (!f.exists()) {
@@ -164,3 +180,61 @@
             t.close();
         }
     }
+
+class EchoHandler implements HttpHandler {
+
+    byte[] read(InputStream is) throws IOException {
+        byte[] buf = new byte[1024];
+        byte[] result = new byte[0];
+
+        while (true) {
+            int n = is.read(buf);
+            if (n > 0) {
+                byte[] b1 = new byte[result.length + n];
+                System.arraycopy(result, 0, b1, 0, result.length);
+                System.arraycopy(buf, 0, b1, result.length, n);
+                result = b1;
+            } else if (n == -1) {
+                return result;
+            }
+        }
+    }
+
+    public void handle (HttpExchange t)
+        throws IOException
+    {
+        InputStream is = t.getRequestBody();
+        Headers map = t.getRequestHeaders();
+        String fixedrequest = map.getFirst ("XFixed");
+
+        // return the number of bytes received (no echo)
+        String summary = map.getFirst ("XSummary");
+        if (fixedrequest != null && summary == null)  {
+            byte[] in = read(is);
+            t.sendResponseHeaders(200, in.length);
+            OutputStream os = t.getResponseBody();
+            os.write(in);
+            os.close();
+            is.close();
+        } else {
+            OutputStream os = t.getResponseBody();
+            byte[] buf = new byte[64 * 1024];
+            t.sendResponseHeaders(200, 0);
+            int n, count=0;;
+
+            while ((n = is.read(buf)) != -1) {
+                if (summary == null) {
+                    os.write(buf, 0, n);
+                }
+                count += n;
+            }
+            if (summary != null) {
+                String s = Integer.toString(count);
+                os.write(s.getBytes());
+            }
+            os.close();
+            is.close();
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/APIErrors.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @library /lib/testlibrary/
+ * @build jdk.testlibrary.SimpleSSLContext ProxyServer
+ * @compile ../../../com/sun/net/httpserver/LogFilter.java
+ * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
+ * @run main/othervm APIErrors
+ */
+//package javaapplication16;
+
+import com.sun.net.httpserver.*;
+import java.io.IOException;
+import java.net.*;
+import java.net.http.*;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.function.Supplier;
+
+/**
+ * Does stupid things with API, to check appropriate errors/exceptions thrown
+ */
+public class APIErrors {
+
+    static HttpServer s1 = null;
+    static ExecutorService executor = null;
+    static int port;
+    static HttpClient client;
+    static String httproot, fileuri, fileroot;
+    static List<HttpClient> clients = new LinkedList<>();
+
+    public static void main(String[] args) throws Exception {
+        initServer();
+        fileroot = System.getProperty("test.src") + "/docs";
+
+        client = HttpClient.create().build();
+
+        clients.add(HttpClient.getDefault());
+
+        try {
+            test1();
+            test2();
+            test3();
+        } finally {
+            s1.stop(0);
+            executor.shutdownNow();
+            for (HttpClient client : clients)
+                client.executorService().shutdownNow();
+        }
+    }
+
+    static void reject(Runnable r, Class<? extends Exception> extype) {
+        try {
+            r.run();
+            throw new RuntimeException("Expected: " + extype);
+        } catch (Throwable t) {
+            if (!extype.isAssignableFrom(t.getClass())) {
+                throw new RuntimeException("Wrong exception type: " + extype + " / "
+                    +t.getClass());
+            }
+        }
+    }
+
+    static void accept(Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable t) {
+            throw new RuntimeException("Unexpected exception: " + t);
+        }
+    }
+
+    static void checkNonNull(Supplier<?> r) {
+        if (r.get() == null)
+            throw new RuntimeException("Unexpected null return:");
+    }
+
+    static void assertTrue(Supplier<Boolean> r) {
+        if (r.get() == false)
+            throw new RuntimeException("Assertion failure:");
+    }
+
+    // HttpClient.Builder
+    static void test1() throws Exception {
+        System.out.println("Test 1");
+        HttpClient.Builder cb = HttpClient.create();
+        InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 5000);
+        reject(() -> { cb.priority(-1);}, IllegalArgumentException.class);
+        reject(() -> { cb.priority(500);}, IllegalArgumentException.class);
+        accept(() -> { cb.priority(1);});
+        accept(() -> { cb.priority(255);});
+
+        accept(() -> {clients.add(cb.build()); clients.add(cb.build());});
+    }
+
+    static void test2() throws Exception {
+        System.out.println("Test 2");
+        HttpClient.Builder cb = HttpClient.create();
+        InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 5000);
+        cb.proxy(ProxySelector.of(addr));
+        HttpClient c = cb.build();
+        clients.add(c);
+        checkNonNull(()-> {return c.executorService();});
+        assertTrue(()-> {return c.followRedirects() == HttpClient.Redirect.NEVER;});
+        assertTrue(()-> {return !c.authenticator().isPresent();});
+    }
+
+    static URI accessibleURI() {
+        return URI.create(fileuri);
+    }
+
+    static HttpRequest request() {
+        return HttpRequest.create(accessibleURI())
+                .GET();
+    }
+
+    static void test3() throws Exception {
+        System.out.println("Test 3");
+        reject(()-> {
+            try {
+                HttpRequest r1 = request();
+                HttpResponse resp = r1.response();
+                HttpResponse resp1 = r1.response();
+            } catch (IOException |InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }, IllegalStateException.class);
+
+        reject(()-> {
+            try {
+                HttpRequest r1 = request();
+                HttpResponse resp = r1.response();
+                HttpResponse resp1 = r1.responseAsync().get();
+            } catch (IOException |InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }, IllegalStateException.class);
+        reject(()-> {
+            try {
+                HttpRequest r1 = request();
+                HttpResponse resp1 = r1.responseAsync().get();
+                HttpResponse resp = r1.response();
+            } catch (IOException |InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }, IllegalStateException.class);
+    }
+
+    static class Auth extends java.net.Authenticator {
+        int count = 0;
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            if (count++ == 0) {
+                return new PasswordAuthentication("user", "passwd".toCharArray());
+            } else {
+                return new PasswordAuthentication("user", "goober".toCharArray());
+            }
+        }
+        int count() {
+            return count;
+        }
+    }
+
+    public static void initServer() throws Exception {
+        String root = System.getProperty ("test.src")+ "/docs";
+        InetSocketAddress addr = new InetSocketAddress (0);
+        s1 = HttpServer.create (addr, 0);
+        if (s1 instanceof HttpsServer) {
+            throw new RuntimeException ("should not be httpsserver");
+        }
+        HttpHandler h = new FileServerHandler(root);
+
+        HttpContext c1 = s1.createContext("/files", h);
+
+        executor = Executors.newCachedThreadPool();
+        s1.setExecutor (executor);
+        s1.start();
+
+        port = s1.getAddress().getPort();
+        System.out.println("HTTP server port = " + port);
+        httproot = "http://127.0.0.1:" + port + "/files/";
+        fileuri = httproot + "foo.txt";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/BasicAuthTest.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @run main/othervm BasicAuthTest
+ * @summary Basic Authentication Test
+ */
+
+import com.sun.net.httpserver.BasicAuthenticator;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.http.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+public class BasicAuthTest {
+
+    static volatile boolean ok;
+    static final String RESPONSE = "Hello world";
+    static final String POST_BODY = "This is the POST body 123909090909090";
+
+    public static void main(String[] args) throws Exception {
+        HttpServer server = HttpServer.create(new InetSocketAddress(0), 10);
+        ExecutorService e = Executors.newCachedThreadPool();
+        Handler h = new Handler();
+        HttpContext serverContext = server.createContext("/test", h);
+        int port = server.getAddress().getPort();
+        System.out.println("Server port = " + port);
+
+        ClientAuth ca = new ClientAuth();
+        ServerAuth sa = new ServerAuth("foo realm");
+        serverContext.setAuthenticator(sa);
+        server.setExecutor(e);
+        server.start();
+        HttpClient client = HttpClient.create()
+                                      .authenticator(ca)
+                                      .build();
+
+        try {
+            URI uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/test/foo");
+            HttpRequest req = client.request(uri).GET();
+
+            HttpResponse resp = req.response();
+            ok = resp.statusCode() == 200 &&
+                    resp.body(HttpResponse.asString()).equals(RESPONSE);
+
+            if (!ok || ca.count != 1)
+                throw new RuntimeException("Test failed");
+
+            // repeat same request, should succeed but no additional authenticator calls
+
+            req = client.request(uri).GET();
+            resp = req.response();
+            ok = resp.statusCode() == 200 &&
+                    resp.body(HttpResponse.asString()).equals(RESPONSE);
+
+            if (!ok || ca.count != 1)
+                throw new RuntimeException("Test failed");
+
+            // try a POST
+
+            req = client.request(uri)
+                    .body(HttpRequest.fromString(POST_BODY))
+                    .POST();
+            resp = req.response();
+            ok = resp.statusCode() == 200;
+
+            if (!ok || ca.count != 1)
+                throw new RuntimeException("Test failed");
+        } finally {
+            client.executorService().shutdownNow();
+            server.stop(0);
+            e.shutdownNow();
+        }
+        System.out.println("OK");
+    }
+
+    static class ServerAuth extends BasicAuthenticator {
+
+        ServerAuth(String realm) {
+            super(realm);
+        }
+
+        @Override
+        public boolean checkCredentials(String username, String password) {
+            if (!"user".equals(username) || !"passwd".equals(password)) {
+                return false;
+            }
+            return true;
+        }
+
+    }
+
+    static class ClientAuth extends java.net.Authenticator {
+        volatile int count = 0;
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            count++;
+            return new PasswordAuthentication("user", "passwd".toCharArray());
+        }
+    }
+
+   static class Handler implements HttpHandler {
+        static volatile boolean ok;
+
+        @Override
+        public void handle(HttpExchange he) throws IOException {
+            String method = he.getRequestMethod();
+            InputStream is = he.getRequestBody();
+            if (method.equalsIgnoreCase("POST")) {
+                String requestBody = new String(is.readAllBytes(), US_ASCII);
+                if (!requestBody.equals(POST_BODY)) {
+                    he.sendResponseHeaders(500, -1);
+                    ok = false;
+                } else {
+                    he.sendResponseHeaders(200, -1);
+                    ok = true;
+                }
+            } else { // GET
+                he.sendResponseHeaders(200, RESPONSE.length());
+                OutputStream os = he.getResponseBody();
+                os.write(RESPONSE.getBytes(US_ASCII));
+                os.close();
+                ok = true;
+            }
+        }
+
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/HeadersTest.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+import java.net.http.HttpRequest;
+import java.net.URI;
+
+/**
+ * @test
+ * @bug 8087112
+ * @summary Basic test for headers
+ */
+public class HeadersTest {
+
+    static final URI TEST_URI = URI.create("http://www.foo.com/");
+
+    static void bad(String name) {
+        HttpRequest.Builder builder = HttpRequest.create(TEST_URI);
+        try {
+            builder.header(name, "foo");
+            throw new RuntimeException("Expected IAE for header:" + name);
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    static void good(String name) {
+        HttpRequest.Builder builder = HttpRequest.create(TEST_URI);
+        try {
+            builder.header(name, "foo");
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException("Unexpected IAE for header:" + name);
+        }
+    }
+
+    public static void main(String[] args) {
+        bad("bad:header");
+        bad("Foo\n");
+        good("X-Foo!");
+        good("Bar~");
+        good("x");
+        bad(" ");
+        bad("Bar\r\n");
+        good("Hello#world");
+        good("Qwer#ert");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/HttpUtils.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import static java.net.http.HttpRequest.fromByteArray;
+import static java.net.http.HttpRequest.fromByteArrays;
+import static java.net.http.HttpRequest.fromFile;
+import static java.net.http.HttpRequest.fromInputStream;
+import static java.net.http.HttpRequest.fromString;
+import java.net.http.HttpResponse;
+import static java.net.http.HttpResponse.asByteArray;
+import static java.net.http.HttpResponse.asFile;
+import static java.net.http.HttpResponse.asInputStream;
+import static java.net.http.HttpResponse.asString;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public final class HttpUtils {
+
+    static final int DEFAULT_OFFSET = 10;
+    static final int DEFAULT_LENGTH = 1000;
+    static final String midSizedFilename = "/files/notsobigfile.txt";
+    static final String smallFilename = "/files/smallfile.txt";
+    static final Path midSizedFile;
+    static final Path smallFile;
+    static final String fileroot;
+
+    public enum RequestBody {
+        STRING, FILE, BYTE_ARRAY, BYTE_ARRAY_OFFSET, INPUTSTREAM, STRING_WITH_CHARSET,
+    }
+
+    static {
+        fileroot = System.getProperty("test.src") + "/docs";
+        midSizedFile = Paths.get(fileroot + midSizedFilename);
+        smallFile = Paths.get(fileroot + smallFilename);
+    }
+
+    static public String getFileContent(String path) throws IOException {
+        FileInputStream fis = new FileInputStream(path);
+        byte[] buf = new byte[2 * 1024];
+        StringBuilder sb = new StringBuilder();
+        int byteRead;
+        while ((byteRead = fis.read(buf)) != -1) {
+            sb.append(new String(buf, 0, byteRead, "US-ASCII"));
+        }
+        return sb.toString();
+    }
+
+    public static HttpRequest.Builder getHttpRequestBuilder(final HttpClient client,
+                                                            final String requestType,
+                                                            final URI uri)
+        throws IOException
+    {
+        HttpRequest.Builder builder;
+        String filename = smallFile.toFile().getAbsolutePath();
+        String fileContents = HttpUtils.getFileContent(filename);
+        byte buf[] = fileContents.getBytes();
+        switch (requestType) {
+            case "InputStream":
+                InputStream inputStream = new FileInputStream(smallFile.toFile());
+                builder = client.request(uri)
+                                .body(fromInputStream(inputStream));
+                break;
+            case "byteArray":
+                builder = client.request(uri)
+                                .body(fromByteArray(buf));
+                break;
+            case "byteArrays":
+                Iterable iterable = Arrays.asList(buf);
+                builder = client.request(uri)
+                                .body(fromByteArrays(iterable.iterator()));
+                break;
+            case "string":
+                builder = client.request(uri)
+                                .body(fromString(fileContents));
+                break;
+            case "byteArray_offset":
+                builder = client.request(uri)
+                                .body(fromByteArray(buf,
+                                                    DEFAULT_OFFSET,
+                                                    DEFAULT_LENGTH));
+                break;
+            case "file":
+                builder = client.request(uri)
+                                .body(fromFile(smallFile));
+                break;
+            case "string_charset":
+                builder = client.request(uri)
+                                .body(fromString(new String(buf),
+                                                 Charset.defaultCharset()));
+                break;
+            default:
+                builder = null;
+                break;
+        }
+        return builder;
+    }
+
+    public static void checkResponse(final HttpResponse response,
+                                     String requestType,
+                                     final String responseType)
+        throws IOException
+    {
+        String filename = smallFile.toFile().getAbsolutePath();
+        String fileContents = HttpUtils.getFileContent(filename);
+        if (requestType.equals("byteArray_offset")) {
+            fileContents = fileContents.substring(DEFAULT_OFFSET,
+                                                  DEFAULT_OFFSET + DEFAULT_LENGTH);
+        }
+        byte buf[] = fileContents.getBytes();
+        String responseBody;
+        switch (responseType) {
+            case "string":
+                responseBody = response.body(asString());
+                if (!responseBody.equals(fileContents)) {
+                    throw new RuntimeException();
+                }
+                break;
+            case "byteArray":
+                byte arr[] = response.body(asByteArray());
+                if (!Arrays.equals(arr, buf)) {
+                    throw new RuntimeException();
+                }
+                break;
+            case "file":
+                response.body(asFile(Paths.get("barf.txt")));
+                Path downloaded = Paths.get("barf.txt");
+                if (Files.size(downloaded) != fileContents.length()) {
+                    throw new RuntimeException("Size mismatch");
+                }
+                break;
+            case "InputStream":
+                InputStream is = response.body(asInputStream());
+                byte arr1[] = new byte[1024];
+                int byteRead;
+                StringBuilder sb = new StringBuilder();
+                while ((byteRead = is.read(arr1)) != -1) {
+                    sb.append(new String(arr1, 0, byteRead));
+                }
+                if (!sb.toString().equals(fileContents)) {
+                    throw new RuntimeException();
+                }
+                break;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/ImmutableHeaders.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @run main/othervm ImmutableHeaders
+ * @summary ImmutableHeaders
+ */
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.Headers;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.http.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.List;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+public class ImmutableHeaders {
+
+    final static String RESPONSE = "Hello world";
+
+    public static void main(String[] args) throws Exception {
+        HttpServer server = HttpServer.create(new InetSocketAddress(0), 10);
+        ExecutorService e = Executors.newCachedThreadPool();
+        Handler h = new Handler();
+        HttpContext serverContext = server.createContext("/test", h);
+        int port = server.getAddress().getPort();
+        System.out.println("Server port = " + port);
+
+        server.setExecutor(e);
+        server.start();
+        HttpClient client = HttpClient.create()
+                                      .build();
+
+        try {
+            URI uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/test/foo");
+            HttpRequest req = client.request(uri)
+                .headers("X-Foo", "bar")
+                .headers("X-Bar", "foo")
+                .GET();
+
+            try {
+                HttpHeaders hd = req.headers();
+                List<String> v = hd.allValues("X-Foo");
+                if (!v.get(0).equals("bar"))
+                    throw new RuntimeException("Test failed");
+                v.add("XX");
+                throw new RuntimeException("Test failed");
+            } catch (UnsupportedOperationException ex) {
+            }
+            HttpResponse resp = req.response();
+            try {
+                HttpHeaders hd = resp.headers();
+                List<String> v = hd.allValues("X-Foo-Response");
+                if (!v.get(0).equals("resp"))
+                    throw new RuntimeException("Test failed");
+                v.add("XX");
+                throw new RuntimeException("Test failed");
+            } catch (UnsupportedOperationException ex) {
+            }
+
+        } finally {
+            client.executorService().shutdownNow();
+            server.stop(0);
+            e.shutdownNow();
+        }
+        System.out.println("OK");
+    }
+
+   static class Handler implements HttpHandler {
+
+        @Override
+        public void handle(HttpExchange he) throws IOException {
+            String method = he.getRequestMethod();
+            InputStream is = he.getRequestBody();
+            Headers h = he.getResponseHeaders();
+            h.add("X-Foo-Response", "resp");
+            he.sendResponseHeaders(200, RESPONSE.length());
+            OutputStream os = he.getResponseBody();
+            os.write(RESPONSE.getBytes(US_ASCII));
+            os.close();
+        }
+
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/LightWeightHttpServer.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+/**
+ * @library /lib/testlibrary/
+ * @build jdk.testlibrary.SimpleSSLContext ProxyServer
+ * @compile ../../../com/sun/net/httpserver/LogFilter.java
+ * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
+ */
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+
+public class LightWeightHttpServer {
+
+    static SSLContext ctx;
+    static HttpServer httpServer;
+    static HttpsServer httpsServer;
+    static ExecutorService executor;
+    static int port;
+    static int httpsport;
+    static String httproot;
+    static String httpsroot;
+    static ProxyServer proxy;
+    static int proxyPort;
+    static RedirectErrorHandler redirectErrorHandler, redirectErrorHandlerSecure;
+    static RedirectHandler redirectHandler, redirectHandlerSecure;
+    static DelayHandler delayHandler;
+    static final String midSizedFilename = "/files/notsobigfile.txt";
+    static final String smallFilename = "/files/smallfile.txt";
+    static Path midSizedFile;
+    static Path smallFile;
+    static String fileroot;
+
+    public static void initServer() throws IOException {
+
+        Logger logger = Logger.getLogger("com.sun.net.httpserver");
+        ConsoleHandler ch = new ConsoleHandler();
+        logger.setLevel(Level.ALL);
+        ch.setLevel(Level.ALL);
+        logger.addHandler(ch);
+
+        String root = System.getProperty("test.src") + "/docs";
+        InetSocketAddress addr = new InetSocketAddress(0);
+        httpServer = HttpServer.create(addr, 0);
+        if (httpServer instanceof HttpsServer) {
+            throw new RuntimeException("should not be httpsserver");
+        }
+        httpsServer = HttpsServer.create(addr, 0);
+        HttpHandler h = new FileServerHandler(root);
+
+        HttpContext c1 = httpServer.createContext("/files", h);
+        HttpContext c2 = httpsServer.createContext("/files", h);
+        HttpContext c3 = httpServer.createContext("/echo", new EchoHandler());
+        redirectHandler = new RedirectHandler("/redirect");
+        redirectHandlerSecure = new RedirectHandler("/redirect");
+        HttpContext c4 = httpServer.createContext("/redirect", redirectHandler);
+        HttpContext c41 = httpsServer.createContext("/redirect", redirectHandlerSecure);
+        HttpContext c5 = httpsServer.createContext("/echo", new EchoHandler());
+        HttpContext c6 = httpServer.createContext("/keepalive", new KeepAliveHandler());
+        redirectErrorHandler = new RedirectErrorHandler("/redirecterror");
+        redirectErrorHandlerSecure = new RedirectErrorHandler("/redirecterror");
+        HttpContext c7 = httpServer.createContext("/redirecterror", redirectErrorHandler);
+        HttpContext c71 = httpsServer.createContext("/redirecterror", redirectErrorHandlerSecure);
+        delayHandler = new DelayHandler();
+        HttpContext c8 = httpServer.createContext("/delay", delayHandler);
+        HttpContext c81 = httpsServer.createContext("/delay", delayHandler);
+
+        executor = Executors.newCachedThreadPool();
+        httpServer.setExecutor(executor);
+        httpsServer.setExecutor(executor);
+        ctx = new SimpleSSLContext().get();
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(ctx));
+        httpServer.start();
+        httpsServer.start();
+
+        port = httpServer.getAddress().getPort();
+        System.out.println("HTTP server port = " + port);
+        httpsport = httpsServer.getAddress().getPort();
+        System.out.println("HTTPS server port = " + httpsport);
+        httproot = "http://127.0.0.1:" + port + "/";
+        httpsroot = "https://127.0.0.1:" + httpsport + "/";
+
+        proxy = new ProxyServer(0, false);
+        proxyPort = proxy.getPort();
+        System.out.println("Proxy port = " + proxyPort);
+    }
+
+    public static void stop() throws IOException {
+        if (httpServer != null) {
+            httpServer.stop(0);
+        }
+        if (httpsServer != null) {
+            httpsServer.stop(0);
+        }
+        if (proxy != null) {
+            proxy.close();
+        }
+        if (executor != null) {
+            executor.shutdownNow();
+        }
+    }
+
+    static class RedirectErrorHandler implements HttpHandler {
+
+        String root;
+        volatile int count = 1;
+
+        RedirectErrorHandler(String root) {
+            this.root = root;
+        }
+
+        synchronized int count() {
+            return count;
+        }
+
+        synchronized void increment() {
+            count++;
+        }
+
+        @Override
+        public synchronized void handle(HttpExchange t)
+                throws IOException {
+            byte[] buf = new byte[2048];
+            try (InputStream is = t.getRequestBody()) {
+                while (is.read(buf) != -1) ;
+            }
+
+            Headers map = t.getResponseHeaders();
+            String redirect = root + "/foo/" + Integer.toString(count);
+            increment();
+            map.add("Location", redirect);
+            t.sendResponseHeaders(301, -1);
+            t.close();
+        }
+    }
+
+    static class RedirectHandler implements HttpHandler {
+
+        String root;
+        volatile int count = 0;
+
+        RedirectHandler(String root) {
+            this.root = root;
+        }
+
+        @Override
+        public synchronized void handle(HttpExchange t)
+                throws IOException {
+            byte[] buf = new byte[2048];
+            try (InputStream is = t.getRequestBody()) {
+                while (is.read(buf) != -1) ;
+            }
+
+            Headers map = t.getResponseHeaders();
+
+            if (count++ < 1) {
+                map.add("Location", root + "/foo/" + count);
+            } else {
+                map.add("Location", SmokeTest.midSizedFilename);
+            }
+            t.sendResponseHeaders(301, -1);
+            t.close();
+        }
+
+        int count() {
+            return count;
+        }
+
+        void reset() {
+            count = 0;
+        }
+    }
+
+    static class KeepAliveHandler implements HttpHandler {
+
+        volatile int counter = 0;
+        HashSet<Integer> portSet = new HashSet<>();
+        volatile int[] ports = new int[4];
+
+        void sleep(int n) {
+            try {
+                Thread.sleep(n);
+            } catch (InterruptedException e) {
+            }
+        }
+
+        @Override
+        public synchronized void handle(HttpExchange t)
+                throws IOException {
+            int remotePort = t.getRemoteAddress().getPort();
+            String result = "OK";
+
+            int n = counter++;
+            /// First test
+            if (n < 4) {
+                ports[n] = remotePort;
+            }
+            if (n == 3) {
+                // check all values in ports[] are the same
+                if (ports[0] != ports[1] || ports[2] != ports[3]
+                        || ports[0] != ports[2]) {
+                    result = "Error " + Integer.toString(n);
+                    System.out.println(result);
+                }
+            }
+            // Second test
+            if (n >= 4 && n < 8) {
+                // delay to ensure ports are different
+                sleep(500);
+                ports[n - 4] = remotePort;
+            }
+            if (n == 7) {
+                // should be all different
+                if (ports[0] == ports[1] || ports[2] == ports[3]
+                        || ports[0] == ports[2]) {
+                    result = "Error " + Integer.toString(n);
+                    System.out.println(result);
+                    System.out.printf("Ports: %d, %d, %d, %d\n",
+                                      ports[0], ports[1], ports[2], ports[3]);
+                }
+                // setup for third test
+                for (int i = 0; i < 4; i++) {
+                    portSet.add(ports[i]);
+                }
+            }
+            // Third test
+            if (n > 7) {
+                // just check that port is one of the ones in portSet
+                if (!portSet.contains(remotePort)) {
+                    System.out.println("UNEXPECTED REMOTE PORT " + remotePort);
+                    result = "Error " + Integer.toString(n);
+                    System.out.println(result);
+                }
+            }
+            byte[] buf = new byte[2048];
+
+            try (InputStream is = t.getRequestBody()) {
+                while (is.read(buf) != -1) ;
+            }
+            t.sendResponseHeaders(200, result.length());
+            OutputStream o = t.getResponseBody();
+            o.write(result.getBytes("US-ASCII"));
+            t.close();
+        }
+    }
+
+    static class DelayHandler implements HttpHandler {
+
+        CyclicBarrier bar1 = new CyclicBarrier(2);
+        CyclicBarrier bar2 = new CyclicBarrier(2);
+        CyclicBarrier bar3 = new CyclicBarrier(2);
+
+        CyclicBarrier barrier1() {
+            return bar1;
+        }
+
+        CyclicBarrier barrier2() {
+            return bar2;
+        }
+
+        @Override
+        public synchronized void handle(HttpExchange he) throws IOException {
+            byte[] buf = Util.readAll(he.getRequestBody());
+            try {
+                bar1.await();
+                bar2.await();
+            } catch (InterruptedException | BrokenBarrierException e) {
+            }
+            he.sendResponseHeaders(200, -1); // will probably fail
+            he.close();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/ManyRequests.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @library /lib/testlibrary/
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @compile ../../../com/sun/net/httpserver/LogFilter.java
+ * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
+ * @run main/othervm ManyRequests
+ * @summary Send a large number of requests asynchronously
+ */
+
+//package javaapplication16;
+
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+
+public class ManyRequests {
+
+    public static void main(String[] args) throws Exception {
+        SSLContext ctx = new SimpleSSLContext().get();
+
+        InetSocketAddress addr = new InetSocketAddress(0);
+        HttpsServer server = HttpsServer.create(addr, 0);
+        server.setHttpsConfigurator(new HttpsConfigurator(ctx));
+
+        HttpClient client = HttpClient.create()
+                                      .sslContext(ctx)
+                                      .build();
+        try {
+            test(server, client);
+            System.out.println("OK");
+        } finally {
+            server.stop(0);
+            client.executorService().shutdownNow();
+        }
+    }
+
+    static final int REQUESTS = 1000;
+
+    static void test(HttpsServer server, HttpClient client) throws Exception {
+        int port = server.getAddress().getPort();
+        URI uri = new URI("https://127.0.0.1:" + port + "/foo/x");
+        server.createContext("/foo", new EchoHandler());
+        server.start();
+
+        RequestLimiter limiter = new RequestLimiter(40);
+        Random rand = new Random();
+        CompletableFuture<Void>[] results = new CompletableFuture[REQUESTS];
+        HashMap<HttpRequest,byte[]> bodies = new HashMap<>();
+
+        for (int i=0; i<REQUESTS; i++) {
+            byte[] buf = new byte[i+1];  // different size bodies
+            rand.nextBytes(buf);
+            HttpRequest r = client.request(uri)
+                                  .body(HttpRequest.fromByteArray(buf))
+                                  .POST();
+            bodies.put(r, buf);
+
+            results[i] =
+                limiter.whenOkToSend()
+                       .thenCompose((v) -> r.responseAsync())
+                       .thenCompose((resp) -> {
+                           limiter.requestComplete();
+                           if (resp.statusCode() != 200) {
+                               resp.bodyAsync(HttpResponse.ignoreBody());
+                               String s = "Expected 200, got: " + resp.statusCode();
+                               return completedWithIOException(s);
+                           }
+                           return resp.bodyAsync(HttpResponse.asByteArray())
+                                      .thenApply((b) -> new Pair<>(resp, b));
+                       })
+                      .thenAccept((pair) -> {
+                          HttpRequest request = pair.t.request();
+                          byte[] requestBody = bodies.get(request);
+                          check(Arrays.equals(requestBody, pair.u),
+                                "bodies not equal");
+
+                      });
+        }
+        // wait for them all to complete and throw exception in case of error
+        CompletableFuture.allOf(results).join();
+    }
+
+    static <T> CompletableFuture<T> completedWithIOException(String message) {
+        CompletableFuture<T> cf = new CompletableFuture<>();
+        cf.completeExceptionally(new IOException(message));
+        return cf;
+    }
+
+    static final class Pair<T,U> {
+        Pair(T t, U u) {
+            this.t = t; this.u = u;
+        }
+        T t;
+        U u;
+    }
+
+    /**
+     * A simple limiter for controlling the number of requests to be run in
+     * parallel whenOkToSend() is called which returns a CF<Void> that allows
+     * each individual request to proceed, or block temporarily (blocking occurs
+     * on the waiters list here. As each request actually completes
+     * requestComplete() is called to notify this object, and allow some
+     * requests to continue.
+     */
+    static class RequestLimiter {
+
+        static final CompletableFuture<Void> COMPLETED_FUTURE =
+                CompletableFuture.completedFuture(null);
+
+        final int maxnumber;
+        final LinkedList<CompletableFuture<Void>> waiters;
+        int number;
+        boolean blocked;
+
+        RequestLimiter(int maximum) {
+            waiters = new LinkedList<>();
+            maxnumber = maximum;
+        }
+
+        synchronized void requestComplete() {
+            number--;
+            // don't unblock until number of requests has halved.
+            if ((blocked && number <= maxnumber / 2) ||
+                        (!blocked && waiters.size() > 0)) {
+                int toRelease = Math.min(maxnumber - number, waiters.size());
+                for (int i=0; i<toRelease; i++) {
+                    CompletableFuture<Void> f = waiters.remove();
+                    number ++;
+                    f.complete(null);
+                }
+                blocked = number >= maxnumber;
+            }
+        }
+
+        synchronized CompletableFuture<Void> whenOkToSend() {
+            if (blocked || number + 1 >= maxnumber) {
+                blocked = true;
+                CompletableFuture<Void> r = new CompletableFuture<>();
+                waiters.add(r);
+                return r;
+            } else {
+                number++;
+                return COMPLETED_FUTURE;
+            }
+        }
+    }
+
+    static void check(boolean cond, Object... msg) {
+        if (cond)
+            return;
+        StringBuilder sb = new StringBuilder();
+        for (Object o : msg)
+            sb.append(o);
+        throw new RuntimeException(sb.toString());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/ProxyServer.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.util.*;
+import java.security.*;
+
+/**
+ * A minimal proxy server that supports CONNECT tunneling. It does not do
+ * any header transformations. In future this could be added.
+ * Two threads are created per client connection. So, it's not
+ * intended for large numbers of parallel connections.
+ */
+public class ProxyServer extends Thread implements Closeable {
+
+    ServerSocket listener;
+    int port;
+    volatile boolean debug;
+
+    /**
+     * Create proxy on port (zero means don't care). Call getPort()
+     * to get the assigned port.
+     */
+    public ProxyServer(Integer port) throws IOException {
+        this(port, false);
+    }
+
+    public ProxyServer(Integer port, Boolean debug) throws IOException {
+        this.debug = debug;
+        listener = new ServerSocket(port);
+        this.port = listener.getLocalPort();
+        setName("ProxyListener");
+        setDaemon(true);
+        connections = new LinkedList<>();
+        start();
+    }
+
+    public ProxyServer(String s) {  }
+
+    /**
+     * Returns the port number this proxy is listening on
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * Shuts down the proxy, probably aborting any connections
+     * currently open
+     */
+    public void close() throws IOException {
+        if (debug) System.out.println("Proxy: closing");
+            done = true;
+        listener.close();
+        for (Connection c : connections) {
+            if (c.running()) {
+                c.close();
+            }
+        }
+    }
+
+    List<Connection> connections;
+
+    volatile boolean done;
+
+    public void run() {
+        if (System.getSecurityManager() == null) {
+            execute();
+        } else {
+            // so calling domain does not need to have socket permission
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                public Void run() {
+                    execute();
+                    return null;
+                }
+            });
+        }
+    }
+
+    public void execute() {
+        try {
+            while(!done) {
+                Socket s = listener.accept();
+                if (debug)
+                    System.out.println("Client: " + s);
+                Connection c = new Connection(s);
+                connections.add(c);
+            }
+        } catch(Throwable e) {
+            if (debug && !done) {
+                System.out.println("Fatal error: Listener: " + e);
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Transparently forward everything, once we know what the destination is
+     */
+    class Connection {
+
+        Socket clientSocket, serverSocket;
+        Thread out, in;
+        volatile InputStream clientIn, serverIn;
+        volatile OutputStream clientOut, serverOut;
+
+        boolean forwarding = false;
+
+        final static int CR = 13;
+        final static int LF = 10;
+
+        Connection(Socket s) throws IOException {
+            this.clientSocket= s;
+            this.clientIn = new BufferedInputStream(s.getInputStream());
+            this.clientOut = s.getOutputStream();
+            init();
+        }
+
+        byte[] readHeaders(InputStream is) throws IOException {
+            byte[] outbuffer = new byte[8000];
+            int crlfcount = 0;
+            int bytecount = 0;
+            int c;
+            while ((c=is.read()) != -1 && bytecount < outbuffer.length) {
+                outbuffer[bytecount++] = (byte)c;
+                if (debug) System.out.write(c);
+                // were looking for CRLFCRLF sequence
+                if (c == CR || c == LF) {
+                    switch(crlfcount) {
+                        case 0:
+                            if (c == CR) crlfcount ++;
+                            break;
+                        case 1:
+                            if (c == LF) crlfcount ++;
+                            break;
+                        case 2:
+                            if (c == CR) crlfcount ++;
+                            break;
+                        case 3:
+                            if (c == LF) crlfcount ++;
+                            break;
+                    }
+                } else {
+                    crlfcount = 0;
+                }
+                if (crlfcount == 4) {
+                    break;
+                }
+            }
+            byte[] ret = new byte[bytecount];
+            System.arraycopy(outbuffer, 0, ret, 0, bytecount);
+            return ret;
+        }
+
+        boolean running() {
+            return out.isAlive() || in.isAlive();
+        }
+
+        public void close() throws IOException {
+            if (debug) System.out.println("Closing connection (proxy)");
+            if (serverSocket != null) serverSocket.close();
+            if (clientSocket != null) clientSocket.close();
+        }
+
+        int findCRLF(byte[] b) {
+            for (int i=0; i<b.length-1; i++) {
+                if (b[i] == CR && b[i+1] == LF) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        public void init() {
+            try {
+                byte[] buf = readHeaders(clientIn);
+                int p = findCRLF(buf);
+                if (p == -1) {
+                    close();
+                    return;
+                }
+                String cmd = new String(buf, 0, p, "US-ASCII");
+                String[] params = cmd.split(" ");
+                if (params[0].equals("CONNECT")) {
+                    doTunnel(params[1]);
+                } else {
+                    doProxy(params[1], buf, p, cmd);
+                }
+            } catch (IOException e) {
+                if (debug) {
+                    System.out.println (e);
+                }
+                try {close(); } catch (IOException e1) {}
+            }
+        }
+
+        void doProxy(String dest, byte[] buf, int p, String cmdLine)
+            throws IOException
+        {
+            try {
+                URI uri = new URI(dest);
+                if (!uri.isAbsolute()) {
+                    throw new IOException("request URI not absolute");
+                }
+                dest = uri.getAuthority();
+                // now extract the path from the URI and recreate the cmd line
+                int sp = cmdLine.indexOf(' ');
+                String method = cmdLine.substring(0, sp);
+                cmdLine = method + " " + uri.getPath() + " HTTP/1.1";
+                int x = cmdLine.length() - 1;
+                int i = p;
+                while (x >=0) {
+                    buf[i--] = (byte)cmdLine.charAt(x--);
+                }
+                i++;
+
+                commonInit(dest, 80);
+                serverOut.write(buf, i, buf.length-i);
+                proxyCommon();
+
+            } catch (URISyntaxException e) {
+                throw new IOException(e);
+            }
+        }
+
+        void commonInit(String dest, int defaultPort) throws IOException {
+            int port;
+            String[] hostport = dest.split(":");
+            if (hostport.length == 1) {
+                port = defaultPort;
+            } else {
+                port = Integer.parseInt(hostport[1]);
+            }
+            if (debug) System.out.printf("Server: (%s/%d)\n", hostport[0], port);
+            serverSocket = new Socket(hostport[0], port);
+            serverOut = serverSocket.getOutputStream();
+
+            serverIn = new BufferedInputStream(serverSocket.getInputStream());
+        }
+
+        void proxyCommon() throws IOException {
+            out = new Thread(() -> {
+                try {
+                    byte[] bb = new byte[8000];
+                    int n;
+                    while ((n = clientIn.read(bb)) != -1) {
+                        serverOut.write(bb, 0, n);
+                    }
+                    serverSocket.close();
+                    clientSocket.close();
+                } catch (IOException e) {
+                    if (debug) {
+                        System.out.println (e);
+                    }
+                }
+            });
+            in = new Thread(() -> {
+                try {
+                    byte[] bb = new byte[8000];
+                    int n;
+                    while ((n = serverIn.read(bb)) != -1) {
+                        clientOut.write(bb, 0, n);
+                    }
+                    serverSocket.close();
+                    clientSocket.close();
+                } catch (IOException e) {
+                    if (debug) {
+                        System.out.println(e);
+                        e.printStackTrace();
+                    }
+                }
+            });
+            out.setName("Proxy-outbound");
+            out.setDaemon(true);
+            in.setDaemon(true);
+            in.setName("Proxy-inbound");
+            out.start();
+            in.start();
+        }
+
+        void doTunnel(String dest) throws IOException {
+            commonInit(dest, 443);
+            clientOut.write("HTTP/1.1 200 OK\r\n\r\n".getBytes());
+            proxyCommon();
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        int port = Integer.parseInt(args[0]);
+        boolean debug = args.length > 1 && args[1].equals("-debug");
+        System.out.println("Debugging : " + debug);
+        ProxyServer ps = new ProxyServer(port, debug);
+        System.out.println("Proxy server listening on port " + ps.getPort());
+        while (true) {
+            Thread.sleep(5000);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/QuickResponses.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @test
+ * @bug 8087112
+ * @build Server
+ * @run main/othervm -Djava.net.HttpClient.log=all QuickResponses
+ */
+
+/**
+ * Tests the buffering of data on connections across multiple
+ * responses
+ */
+public class QuickResponses {
+
+    static Server server;
+
+    static String response(String body) {
+        return "HTTP/1.1 200 OK\r\nContent-length: " + Integer.toString(body.length())
+                + "\r\n\r\n" + body;
+    }
+
+    static final String responses[] = {
+        "Lorem ipsum",
+        "dolor sit amet",
+        "consectetur adipiscing elit, sed do eiusmod tempor",
+        "quis nostrud exercitation ullamco",
+        "laboris nisi",
+        "ut",
+        "aliquip ex ea commodo consequat." +
+        "Duis aute irure dolor in reprehenderit in voluptate velit esse" +
+        "cillum dolore eu fugiat nulla pariatur.",
+        "Excepteur sint occaecat cupidatat non proident."
+    };
+
+    static String entireResponse() {
+        String s = "";
+        for (String r : responses) {
+            s += response(r);
+        }
+        return s;
+    }
+
+    public static void main(String[] args) throws Exception {
+        server = new Server(0);
+        URI uri = new URI(server.getURL());
+
+        HttpRequest request = HttpRequest.create(uri)
+                .GET();
+
+        CompletableFuture<HttpResponse> cf1 = request.responseAsync();
+        Server.Connection s1 = server.activity();
+        s1.send(entireResponse());
+
+
+        HttpResponse r = cf1.join();
+        if (r.statusCode()!= 200 || !r.body(HttpResponse.asString()).equals(responses[0]))
+            throw new RuntimeException("Failed on first response");
+
+        //now get the same identical response, synchronously to ensure same connection
+        int remaining = responses.length - 1;
+
+        for (int i=0; i<remaining; i++) {
+            r = HttpRequest.create(uri)
+                    .GET()
+                    .response();
+            if (r.statusCode()!= 200)
+                throw new RuntimeException("Failed");
+
+            String body = r.body(HttpResponse.asString());
+            if (!body.equals(responses[i+1]))
+                throw new RuntimeException("Failed");
+        }
+        HttpClient.getDefault().executorService().shutdownNow();
+        System.out.println("OK");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/RequestBodyTest.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+/**
+ * @test @bug 8087112
+ * @library /lib/testlibrary/
+ * @build jdk.testlibrary.SimpleSSLContext ProxyServer
+ * @compile ../../../com/sun/net/httpserver/LogFilter.java
+ * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
+ * @run main/othervm RequestBodyTest
+ */
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.Executors;
+import javax.net.ssl.SSLContext;
+
+public class RequestBodyTest {
+
+    final static String STRING = "string";
+    final static String BYTE_ARRAY = "byteArray";
+    final static String BYTE_ARRAYS = "byteArrays";
+    final static String BYTE_ARRAY_OFFSET = "byteArray_offset";
+    final static String FILE = "file";
+    final static String STRING_CHARSET = "string_charset";
+    final static String INPUTSTREAM = "InputStream";
+
+    final static String midSizedFilename = "/files/notsobigfile.txt";
+    final static String smallFilename = "/files/smallfile.txt";
+    static Path midSizedFile;
+    static Path smallFile;
+    static String fileroot;
+    static HttpClient client;
+    static SSLContext ctx;
+    static String httproot;
+    static String httpsroot;
+
+    public static void main(String args[]) throws Exception {
+        fileroot = System.getProperty("test.src") + "/docs";
+        midSizedFile = Paths.get(fileroot + midSizedFilename);
+        smallFile = Paths.get(fileroot + smallFilename);
+        //start the server
+        LightWeightHttpServer.initServer();
+
+        httproot = LightWeightHttpServer.httproot;
+        httpsroot = LightWeightHttpServer.httpsroot;
+        ctx = LightWeightHttpServer.ctx;
+        client = HttpClient.create().sslContext(ctx)
+                .followRedirects(HttpClient.Redirect.ALWAYS)
+                .executorService(Executors.newCachedThreadPool())
+                .build();
+
+        String TARGET = httproot + "echo/foo";
+        boolean isSync = false;
+        requestBodyTypes(TARGET, STRING, STRING, isSync);
+        requestBodyTypes(TARGET, STRING, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, STRING, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, STRING, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, STRING, FILE, isSync);
+
+        requestBodyTypes(TARGET, BYTE_ARRAY, STRING, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, FILE, isSync);
+
+        requestBodyTypes(TARGET, BYTE_ARRAYS, STRING, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, FILE, isSync);
+
+        requestBodyTypes(TARGET, INPUTSTREAM, STRING, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, FILE, isSync);
+
+        requestBodyTypes(TARGET, FILE, STRING, isSync);
+        requestBodyTypes(TARGET, FILE, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, FILE, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, FILE, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, FILE, FILE, isSync);
+
+        isSync = true;
+        requestBodyTypes(TARGET, STRING, STRING, isSync);
+        requestBodyTypes(TARGET, STRING, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, STRING, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, STRING, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, STRING, FILE, isSync);
+
+        requestBodyTypes(TARGET, BYTE_ARRAY, STRING, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAY, FILE, isSync);
+
+        requestBodyTypes(TARGET, BYTE_ARRAYS, STRING, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, BYTE_ARRAYS, FILE, isSync);
+
+        requestBodyTypes(TARGET, INPUTSTREAM, STRING, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, INPUTSTREAM, FILE, isSync);
+
+        requestBodyTypes(TARGET, FILE, STRING, isSync);
+        requestBodyTypes(TARGET, FILE, BYTE_ARRAY, isSync);
+        requestBodyTypes(TARGET, FILE, BYTE_ARRAYS, isSync);
+        requestBodyTypes(TARGET, FILE, INPUTSTREAM, isSync);
+        requestBodyTypes(TARGET, FILE, FILE, isSync);
+
+    }
+
+    static void requestBodyTypes(final String target,
+                                 final String requestType,
+                                 final String responseType,
+                                 final boolean isAsync)
+        throws Exception
+    {
+        System.out.println("Running test_request_body_type " + requestType +
+                " and response type " + responseType + " and sync=" + isAsync);
+        URI uri = new URI(target);
+        byte buf[];
+        String filename = smallFile.toFile().getAbsolutePath();
+        String fileContents = HttpUtils.getFileContent(filename);
+        buf = fileContents.getBytes();
+        HttpRequest.Builder builder = HttpUtils.getHttpRequestBuilder(client,
+                                                                      requestType,
+                                                                      uri);
+        HttpResponse response;
+        if (!isAsync) {
+            response = builder.GET().response();
+        } else {
+            response = builder.GET().responseAsync().join();
+        }
+        HttpUtils.checkResponse(response, requestType, responseType);
+        System.out.println("OK");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/Server.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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 javaapplication16;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A cut-down Http/1 Server for testing various error situations
+ *
+ * use interrupt() to halt
+ */
+public class Server extends Thread {
+
+    ServerSocket ss;
+    List<Connection> sockets;
+    AtomicInteger counter = new AtomicInteger(0);
+
+    // waits up to 20 seconds for something to happen
+    // dont use this unless certain activity coming.
+    public Connection activity() {
+        for (int i = 0; i < 80 * 100; i++) {
+            for (Connection c : sockets) {
+                if (c.poll()) {
+                    return c;
+                }
+            }
+            try {
+                Thread.sleep(250);
+            } catch (InterruptedException e) {
+            }
+        }
+        return null;
+    }
+
+    // clears all current connections on Server.
+    public void reset() {
+        for (Connection c : sockets) {
+            c.close();
+        }
+    }
+
+    /**
+     * Reads data into an ArrayBlockingQueue<String> where each String
+     * is a line of input, that was terminated by CRLF (not included)
+     */
+    class Connection extends Thread {
+        Connection(Socket s) throws IOException {
+            this.socket = s;
+            id = counter.incrementAndGet();
+            is = s.getInputStream();
+            os = s.getOutputStream();
+            incoming = new ArrayBlockingQueue<>(100);
+            setName("Server-Connection");
+            setDaemon(true);
+            start();
+        }
+        final Socket socket;
+        final int id;
+        final InputStream is;
+        final OutputStream os;
+        final ArrayBlockingQueue<String> incoming;
+
+        final static String CRLF = "\r\n";
+
+        // sentinel indicating connection closed
+        final static String CLOSED = "C.L.O.S.E.D";
+        volatile boolean closed = false;
+
+        @Override
+        public void run() {
+            byte[] buf = new byte[256];
+            String s = "";
+            try {
+                while (true) {
+                    int n = is.read(buf);
+                    if (n == -1) {
+                        cleanup();
+                        return;
+                    }
+                    String s0 = new String(buf, 0, n, StandardCharsets.ISO_8859_1);
+                    s = s + s0;
+                    int i;
+                    while ((i=s.indexOf(CRLF)) != -1) {
+                        String s1 = s.substring(0, i+2);
+                        incoming.put(s1);
+                        if (i+2 == s.length()) {
+                            s = "";
+                            break;
+                        }
+                        s = s.substring(i+2);
+                    }
+                }
+            } catch (IOException |InterruptedException e1) {
+                cleanup();
+            } catch (Throwable t) {
+                System.out.println("X: " + t);
+                cleanup();
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Server.Connection: " + socket.toString();
+        }
+
+        public void sendHttpResponse(int code, String body, String... headers)
+            throws IOException
+        {
+            String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF;
+            for (int i=0; i<headers.length; i+=2) {
+                r1 += headers[i] + ": " + headers[i+1] + CRLF;
+            }
+            int clen = body == null ? 0 : body.length();
+            r1 += "Content-Length: " + Integer.toString(clen) + CRLF;
+            r1 += CRLF;
+            if (body != null) {
+                r1 += body;
+            }
+            send(r1);
+        }
+
+        // content-length is 10 bytes too many
+        public void sendIncompleteHttpResponseBody(int code) throws IOException {
+            String body = "Hello World Helloworld Goodbye World";
+            String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF;
+            int clen = body.length() + 10;
+            r1 += "Content-Length: " + Integer.toString(clen) + CRLF;
+            r1 += CRLF;
+            if (body != null) {
+                r1 += body;
+            }
+            send(r1);
+        }
+
+        public void sendIncompleteHttpResponseHeaders(int code)
+            throws IOException
+        {
+            String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF;
+            send(r1);
+        }
+
+        public void send(String r) throws IOException {
+            os.write(r.getBytes(StandardCharsets.ISO_8859_1));
+        }
+
+        public synchronized void close() {
+            cleanup();
+            closed = true;
+            incoming.clear();
+        }
+
+        public String nextInput(long timeout, TimeUnit unit) {
+            String result = "";
+            while (poll()) {
+                try {
+                    String s = incoming.poll(timeout, unit);
+                    if (s == null && closed) {
+                        return CLOSED;
+                    } else {
+                        result += s;
+                    }
+                } catch (InterruptedException e) {
+                    return null;
+                }
+            }
+            return result;
+        }
+
+        public String nextInput() {
+            return nextInput(0, TimeUnit.SECONDS);
+        }
+
+        public boolean poll() {
+            return incoming.peek() != null;
+        }
+
+        private void cleanup() {
+            try {
+                socket.close();
+            } catch (IOException e) {}
+            sockets.remove(this);
+        }
+    }
+
+    Server(int port) throws IOException {
+        ss = new ServerSocket(port);
+        sockets = Collections.synchronizedList(new LinkedList<>());
+        setName("Test-Server");
+        setDaemon(true);
+        start();
+    }
+
+    Server() throws IOException {
+        this(0);
+    }
+
+    int port() {
+        return ss.getLocalPort();
+    }
+
+    public String getURL() {
+        return "http://127.0.0.1:" + port() + "/foo/";
+    }
+
+    public void close() {
+        try {
+            ss.close();
+        } catch (IOException e) {
+        }
+        for (Connection c : sockets) {
+            c.close();
+        }
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            try {
+                Socket s = ss.accept();
+                Connection c = new Connection(s);
+                sockets.add(c);
+            } catch (IOException e) {
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/SmokeTest.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,949 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @library /lib/testlibrary/
+ * @build jdk.testlibrary.SimpleSSLContext ProxyServer
+ * @compile ../../../com/sun/net/httpserver/LogFilter.java
+ * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
+ * @run main/othervm SmokeTest
+ */
+
+//package javaapplication16;
+
+import com.sun.net.httpserver.*;
+import java.net.*;
+import java.net.http.*;
+import java.io.*;
+import java.util.concurrent.*;
+import javax.net.ssl.*;
+import java.nio.file.*;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import jdk.testlibrary.SimpleSSLContext;
+import static java.net.http.HttpRequest.*;
+import static java.net.http.HttpResponse.*;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * * Basic smoke test for Http/1.1 client
+ * - basic request response
+ * - request body POST
+ * - response body GET
+ * - redirect
+ * - chunked request/response
+ * - SSL
+ * - proxies
+ * - 100 continue
+ * - check keep alive appears to be working
+ * - cancel of long request
+ *
+ * Uses a FileServerHandler serving a couple of known files
+ * in docs directory.
+ */
+public class SmokeTest {
+    static SSLContext ctx;
+    static HttpServer s1 ;
+    static HttpsServer s2;
+    static ExecutorService executor;
+    static int port;
+    static int httpsport;
+    static String httproot;
+    static String httpsroot;
+    static HttpClient client;
+    static ProxyServer proxy;
+    static int proxyPort;
+    static RedirectErrorHandler redirectErrorHandler, redirectErrorHandlerSecure;
+    static RedirectHandler redirectHandler, redirectHandlerSecure;
+    static DelayHandler delayHandler;
+    final static String midSizedFilename = "/files/notsobigfile.txt";
+    final static String smallFilename = "/files/smallfile.txt";
+    static Path midSizedFile;
+    static Path smallFile;
+    static String fileroot;
+
+    static String getFileContent(String path) throws IOException {
+        FileInputStream fis = new FileInputStream(path);
+        byte[] buf = new byte[2000];
+        StringBuilder sb = new StringBuilder();
+        int n;
+        while ((n=fis.read(buf)) != -1) {
+            sb.append(new String(buf, 0, n, "US-ASCII"));
+        }
+        return sb.toString();
+    }
+
+    public static void main(String[] args) throws Exception {
+        initServer();
+        fileroot = System.getProperty ("test.src", ".")+ "/docs";
+        midSizedFile = Paths.get(fileroot + midSizedFilename);
+        smallFile = Paths.get(fileroot + smallFilename);
+
+        client = HttpClient.create()
+                           .sslContext(ctx)
+                           .followRedirects(HttpClient.Redirect.ALWAYS)
+                           .executorService(Executors.newCachedThreadPool())
+                           .build();
+
+        try {
+            test1(httproot + "files/foo.txt", true);
+
+            test1(httproot + "files/foo.txt", false);
+            test1(httpsroot + "files/foo.txt", true);
+            test1(httpsroot + "files/foo.txt", false);
+            test2(httproot + "echo/foo", "This is a short test");
+            test2(httpsroot + "echo/foo", "This is a short test");
+
+            test3(httproot + "redirect/foo.txt");
+            test3(httpsroot + "redirect/foo.txt");
+            test4(httproot + "files/foo.txt");
+            test4(httpsroot + "files/foo.txt");
+            test5(httproot + "echo/foo", true);
+            test5(httpsroot + "echo/foo", true);
+            test5(httproot + "echo/foo", false);
+            test5(httpsroot + "echo/foo", false);
+
+            test6(httproot + "echo/foo", true);
+            test6(httpsroot + "echo/foo", true);
+            test6(httproot + "echo/foo", false);
+            test6(httpsroot + "echo/foo", false);
+
+            test7(httproot + "keepalive/foo");
+
+            test8(httproot + "files/foo.txt", true);
+            test8(httproot + "files/foo.txt", false);
+            test8(httpsroot + "files/foo.txt", true);
+            test8(httpsroot + "files/foo.txt", false);
+            // disabled test9();
+
+            test10(httproot + "redirecterror/foo.txt");
+
+            test10(httpsroot + "redirecterror/foo.txt");
+
+            test11(httproot + "echo/foo");
+            test11(httpsroot + "echo/foo");
+            //test12(httproot + "delay/foo", delayHandler);
+
+        } finally {
+            s1.stop(0);
+            s2.stop(0);
+            proxy.close();
+            executor.shutdownNow();
+            client.executorService().shutdownNow();
+        }
+    }
+
+    static class Auth extends java.net.Authenticator {
+        volatile int count = 0;
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            if (count++ == 0) {
+                return new PasswordAuthentication("user", "passwd".toCharArray());
+            } else {
+                return new PasswordAuthentication("user", "goober".toCharArray());
+            }
+        }
+        int count() {
+            return count;
+        }
+    }
+
+    // Basic test
+    static void test1(String target, boolean fixedLen) throws Exception {
+        System.out.print("test1: " + target);
+        URI uri = new URI(target);
+
+        HttpRequest.Builder builder = client.request(uri)
+                                             .body(noBody());
+
+        if (fixedLen) {
+            builder.header("XFixed", "yes");
+        }
+
+        HttpResponse response = builder.GET().response();
+
+        String body = response.body(asString());
+        if (!body.equals("This is foo.txt\r\n")) {
+            throw new RuntimeException();
+        }
+
+        // repeat async
+        response = builder.GET().responseAsync().join();
+
+        body = response.body(asString());
+        if (!body.equals("This is foo.txt\r\n")) {
+            throw new RuntimeException();
+        }
+        System.out.println(" OK");
+    }
+
+    // POST use echo to check reply
+    static void test2(String s, String body) throws Exception {
+        System.out.print("test2: " + s);
+        URI uri = new URI(s);
+
+        HttpResponse response = client.request(uri)
+                                       .body(fromString(body))
+                                       .POST()
+                                       .response();
+
+        if (response.statusCode() != 200) {
+            throw new RuntimeException(
+                "Expected 200, got [ " + response.statusCode() + " ]");
+        }
+        String reply = response.body(asString());
+        if (!reply.equals(body)) {
+            throw new RuntimeException(
+                "Body mismatch: expected [" + body + "], got [" + reply + "]");
+        }
+        System.out.println(" OK");
+    }
+
+    // Redirect
+    static void test3(String s) throws Exception {
+        System.out.print("test3: " + s);
+        URI uri = new URI(s);
+        RedirectHandler handler = uri.getScheme().equals("https")
+                ? redirectHandlerSecure : redirectHandler;
+
+        HttpResponse response = client.request(uri)
+                                      .body(noBody())
+                                      .GET()
+                                      .response();
+
+        if (response.statusCode() != 200) {
+            throw new RuntimeException(
+                "Expected 200, got [ " + response.statusCode() + " ]");
+        } else {
+            response.body(HttpResponse.asFile(Paths.get("redir1.txt")));
+        }
+
+        Path downloaded = Paths.get("redir1.txt");
+        if (Files.size(downloaded) != Files.size(midSizedFile)) {
+            throw new RuntimeException("Size mismatch");
+        }
+
+        System.out.printf(" (count: %d) ", handler.count());
+        // repeat with async api
+
+        handler.reset();
+
+        response = client.request(uri)
+                         .body(noBody())
+                         .GET()
+                         .responseAsync()
+                         .join();
+
+        if (response.statusCode() != 200) {
+            throw new RuntimeException(
+                    "Expected 200, got [ " + response.statusCode() + " ]");
+        } else {
+            response.body(HttpResponse.asFile(Paths.get("redir2.txt")));
+        }
+
+        downloaded = Paths.get("redir2.txt");
+        if (Files.size(downloaded) != Files.size(midSizedFile)) {
+            throw new RuntimeException("Size mismatch 2");
+        }
+        System.out.printf(" (count: %d) ", handler.count());
+        System.out.println(" OK");
+    }
+
+    // Proxies
+    static void test4(String s) throws Exception {
+        System.out.print("test4: " + s);
+        URI uri = new URI(s);
+        InetSocketAddress proxyAddr = new InetSocketAddress("127.0.0.1", proxyPort);
+        String filename = fileroot + uri.getPath();
+
+        HttpClient cl = HttpClient.create()
+                                  .proxy(ProxySelector.of(proxyAddr))
+                                  .sslContext(ctx)
+                                  .build();
+
+        CompletableFuture<String> fut = cl.request(uri)
+                                          .body(noBody())
+                                          .GET()
+                                          .responseAsync()
+                                          .thenCompose((HttpResponse response) ->
+                                                          response.bodyAsync(asString())
+                                          );
+
+        String body = fut.get(5, TimeUnit.HOURS);
+
+        String fc = getFileContent(filename);
+
+        if (!body.equals(fc)) {
+            throw new RuntimeException(
+                    "Body mismatch: expected [" + body + "], got [" + fc + "]");
+        }
+        cl.executorService().shutdownNow();
+        System.out.println(" OK");
+    }
+
+    // 100 Continue: use echo target
+    static void test5(String target, boolean fixedLen) throws Exception {
+        System.out.print("test5: " + target);
+        URI uri = new URI(target);
+        String requestBody = generateString(12 * 1024 + 13);
+
+        HttpRequest.Builder builder = client.request(uri)
+                                            .expectContinue(true)
+                                            .body(fromString(requestBody));
+
+        if (fixedLen) {
+            builder.header("XFixed", "yes");
+        }
+
+        HttpResponse response = builder.GET().response();
+
+        String body = response.body(asString());
+
+        if (!body.equals(requestBody)) {
+            throw new RuntimeException(
+                    "Body mismatch: expected [" + body + "], got [" + body + "]");
+        }
+        System.out.println(" OK");
+    }
+
+    // use echo
+    static void test6(String target, boolean fixedLen) throws Exception {
+        System.out.print("test6: " + target);
+        URI uri = new URI(target);
+        String requestBody = generateString(12 * 1024 + 3);
+
+        HttpRequest.Builder builder = client.request(uri)
+                                            .body(noBody());
+
+        if (fixedLen) {
+            builder.header("XFixed", "yes");
+        }
+
+        HttpResponse response = builder.GET().response();
+
+        if (response.statusCode() != 200) {
+            throw new RuntimeException(
+                    "Expected 200, got [ " + response.statusCode() + " ]");
+        }
+
+        String responseBody = response.body(asString());
+
+        if (responseBody.equals(requestBody)) {
+            throw new RuntimeException(
+                    "Body mismatch: expected [" + requestBody + "], got [" + responseBody + "]");
+        }
+        System.out.println(" OK");
+    }
+
+    @SuppressWarnings("rawtypes")
+    static void test7(String target) throws Exception {
+        System.out.print("test7: " + target);
+
+        // First test
+        URI uri = new URI(target);
+        for (int i=0; i<4; i++) {
+            HttpResponse r = client.request(uri)
+                                   .body(noBody())
+                                   .GET()
+                                   .response();
+            String body = r.body(asString());
+            if (!body.equals("OK")) {
+                throw new RuntimeException("Expected OK, got: " + body);
+            }
+        }
+
+        // Second test: 4 x parallel
+        List<CompletableFuture<HttpResponse>> futures = new LinkedList<>();
+        for (int i=0; i<4; i++) {
+            futures.add(client.request(uri)
+                              .body(noBody())
+                              .GET()
+                              .responseAsync());
+        }
+        // all sent?
+        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+                         .join();
+
+        List<CompletableFuture<String>> futureBodies = new LinkedList<>();
+        for (int i=0; i<4; i++) {
+            futureBodies.add(futures.get(i)
+                                    .join()
+                                    .bodyAsync(asString()));
+        }
+        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+                         .join();
+
+        for (CompletableFuture<String> future : futureBodies) {
+            String body = future.get();
+            if (!body.equals("OK")) {
+                throw new RuntimeException("Expected OK, got: " + body);
+            }
+        }
+
+        // Third test: Multiple of 4 parallel requests
+        BlockingQueue<String> q = new LinkedBlockingQueue<>();
+        for (int i=0; i<4; i++) {
+            client.request(uri)
+                  .body(noBody())
+                  .GET()
+                  .responseAsync()
+                  .thenApply((HttpResponse resp) -> {
+                      String body = resp.body(asString());
+                      putQ(q, body);
+                      return body;
+                  });
+        }
+        // we've sent four requests. Now, just send another request
+        // as each response is received. The idea is to ensure that
+        // only four sockets ever get used.
+
+        for (int i=0; i<100; i++) {
+            // block until response received
+            String body = takeQ(q);
+            if (!body.equals("OK")) {
+                throw new RuntimeException(body);
+            }
+            client.request(uri)
+                  .body(noBody())
+                  .GET()
+                  .responseAsync()
+                  .thenApply((HttpResponse resp) -> {
+                      String body1 = resp.body(asString());
+                      putQ(q, body1);
+                      return body1;
+                  });
+        }
+        // should be four left
+        for (int i=0; i<4; i++) {
+            takeQ(q);
+        }
+        System.out.println(" OK");
+    }
+
+    static String takeQ(BlockingQueue<String> q) {
+        String r = null;
+        try {
+            r = q.take();
+        } catch (InterruptedException e) {}
+
+        return r;
+    }
+
+    static void putQ(BlockingQueue<String> q, String o) {
+        try {
+            q.put(o);
+        } catch (InterruptedException e) {
+            // can't happen
+        }
+    }
+
+    static void test8(String target, boolean fixedLen) throws Exception {
+        System.out.print("test8: " + target);
+        URI uri = new URI(target);
+
+        HttpRequest.Builder builder = client.request(uri)
+                                            .body(noBody());
+
+        if (fixedLen) {
+            builder.header("XFixed", "yes");
+        }
+
+        HttpResponse response = builder.GET().response();
+
+        StringBuilder sb = new StringBuilder();
+
+        InputStream is = response.body(asInputStream());
+        int c;
+        byte[] buf = new byte[2048];
+        while ((c = is.read(buf)) != -1) {
+            for (int i=0; i<c; i++)
+                sb.append((char)buf[i]);
+        }
+        is.close();
+        String body = sb.toString();
+
+        if (!body.equals("This is foo.txt\r\n")) {
+            throw new RuntimeException("Expected \"This is foo.txt\", got: " + body);
+        }
+        System.out.println(" OK");
+    }
+
+    // Chunked output stream
+    static void test11(String target) throws Exception {
+        System.out.print("test11: " + target);
+        URI uri = new URI(target);
+
+        FileInputStream file = new FileInputStream(smallFile.toFile());
+
+        HttpRequest.Builder builder = client.request(uri)
+                                            .body(HttpRequest.fromInputStream(file));
+        HttpResponse response = builder.POST().response();
+
+        if (response.statusCode() != 200) {
+            throw new RuntimeException("Wrong response code");
+        }
+
+        Path download = Paths.get("test11.txt");
+        download.toFile().delete();
+        response.body(HttpResponse.asFile(download));
+
+        if (Files.size(download) != Files.size(smallFile)) {
+            System.out.println("Original size: " + Files.size(smallFile));
+            System.out.println("Downloaded size: " + Files.size(download));
+            throw new RuntimeException("Size mismatch");
+        }
+        System.out.println(" OK");
+    }
+
+    // cancel
+    /*
+    static void test12(String target, DelayHandler h) throws Exception {
+        System.out.print("test12: " + target);
+        URI uri = new URI(target);
+
+        HttpRequest.Builder builder = client
+            .request(uri)
+            .body(HttpRequest.fromString("Hello world"));
+
+        HttpRequest request = builder
+                .GET();
+        request.sendAsync();
+        h.barrier1().await();
+        // request has been processed
+        CompletableFuture<HttpResponse> cf = request.responseAsync();
+        request.cancel();
+        h.barrier2().await();
+        try {
+            HttpResponse r = cf.get();
+            throw new RuntimeException("failed 2");
+        } catch (Exception e) {
+        }
+        System.out.println(" OK");
+    }
+*/
+    static void delay(int seconds) {
+        try {
+            Thread.sleep(seconds * 1000);
+        } catch (InterruptedException e) {
+        }
+    }
+/*
+    // test won't work until sending fully decoupled from receiving in impl
+    static void test9() throws Exception {
+        System.out.print("test9: ");
+        UploadServer up = new UploadServer(1000 * 1000);
+        int size = up.size();
+        String u = "http://127.0.0.1:" + up.port() + "/";
+        URI uri = new URI(u);
+
+        HttpRequest request = client
+            .request(uri)
+            .body(new HttpRequestBodyProcessor() {
+                @Override
+                public ByteBuffer onRequestBodyChunk(ByteBuffer b) throws IOException {
+                    // slow things down
+                    delay(1);
+                    b.position(b.limit()); // fill it
+                    return b;
+                }
+                @Override
+                public long onRequestStart(HttpRequest req) throws IOException {
+                    return size;
+                }
+             })
+            .PUT();
+
+        CompletableFuture<HttpRequest> cf1 = request.sendAsync();
+        CompletableFuture<HttpResponse> cf = request.responseAsync();
+
+        HttpResponse resp = cf.get(1, TimeUnit.MINUTES);
+        if (resp.statusCode() != 201) {
+            throw new RuntimeException("failed: wrong response code");
+        }
+        delay(2); // allow some data to be sent
+        request.cancel();
+        delay(1);
+        if (up.failed()) {
+            throw new RuntimeException("failed to cancel request");
+        }
+        System.out.println(" OK");
+    }
+  */
+    // Redirect loop: return an error after a certain number of redirects
+    static void test10(String s) throws Exception {
+        System.out.print("test10: " + s);
+        URI uri = new URI(s);
+        RedirectErrorHandler handler = uri.getScheme().equals("https")
+                ? redirectErrorHandlerSecure : redirectErrorHandler;
+
+        CompletableFuture<HttpResponse> cf = client.request(uri)
+                                                   .body(noBody())
+                                                   .GET()
+                                                   .responseAsync();
+
+        try {
+            HttpResponse response = cf.join();
+            throw new RuntimeException("Exepected Completion Exception");
+        } catch (CompletionException e) {
+            //System.out.println(e);
+        }
+
+        System.out.printf(" (Calls %d) ", handler.count());
+        System.out.println(" OK");
+    }
+
+    static final int NUM = 50;
+
+    static Random random = new Random();
+    static final String alphabet = "ABCDEFGHIJKLMNOPQRST";
+
+    static char randomChar() {
+        return alphabet.charAt(random.nextInt(alphabet.length()));
+    }
+
+    static String generateString(int length) {
+        StringBuilder sb = new StringBuilder(length);
+        for (int i=0; i<length; i++) {
+            sb.append(randomChar());
+        }
+        return sb.toString();
+    }
+
+    static void initServer() throws Exception {
+        Logger logger = Logger.getLogger("com.sun.net.httpserver");
+        ConsoleHandler ch = new ConsoleHandler();
+        logger.setLevel(Level.ALL);
+        ch.setLevel(Level.ALL);
+        logger.addHandler(ch);
+
+        String root = System.getProperty ("test.src")+ "/docs";
+        InetSocketAddress addr = new InetSocketAddress (0);
+        s1 = HttpServer.create (addr, 0);
+        if (s1 instanceof HttpsServer) {
+            throw new RuntimeException ("should not be httpsserver");
+        }
+        s2 = HttpsServer.create (addr, 0);
+        HttpHandler h = new FileServerHandler(root);
+
+        HttpContext c1 = s1.createContext("/files", h);
+        HttpContext c2 = s2.createContext("/files", h);
+        HttpContext c3 = s1.createContext("/echo", new EchoHandler());
+        redirectHandler = new RedirectHandler("/redirect");
+        redirectHandlerSecure = new RedirectHandler("/redirect");
+        HttpContext c4 = s1.createContext("/redirect", redirectHandler);
+        HttpContext c41 = s2.createContext("/redirect", redirectHandlerSecure);
+        HttpContext c5 = s2.createContext("/echo", new EchoHandler());
+        HttpContext c6 = s1.createContext("/keepalive", new KeepAliveHandler());
+        redirectErrorHandler = new RedirectErrorHandler("/redirecterror");
+        redirectErrorHandlerSecure = new RedirectErrorHandler("/redirecterror");
+        HttpContext c7 = s1.createContext("/redirecterror", redirectErrorHandler);
+        HttpContext c71 = s2.createContext("/redirecterror", redirectErrorHandlerSecure);
+        delayHandler = new DelayHandler();
+        HttpContext c8 = s1.createContext("/delay", delayHandler);
+        HttpContext c81 = s2.createContext("/delay", delayHandler);
+
+        executor = Executors.newCachedThreadPool();
+        s1.setExecutor(executor);
+        s2.setExecutor(executor);
+        ctx = new SimpleSSLContext().get();
+        s2.setHttpsConfigurator(new HttpsConfigurator(ctx));
+        s1.start();
+        s2.start();
+
+        port = s1.getAddress().getPort();
+        System.out.println("HTTP server port = " + port);
+        httpsport = s2.getAddress().getPort();
+        System.out.println("HTTPS server port = " + httpsport);
+        httproot = "http://127.0.0.1:" + port + "/";
+        httpsroot = "https://127.0.0.1:" + httpsport + "/";
+
+        proxy = new ProxyServer(0, false);
+        proxyPort = proxy.getPort();
+        System.out.println("Proxy port = " + proxyPort);
+    }
+}
+
+class UploadServer extends Thread {
+    int statusCode;
+    ServerSocket ss;
+    int port;
+    int size;
+    Object lock;
+    boolean failed = false;
+
+    UploadServer(int size) throws IOException {
+        this.statusCode = statusCode;
+        this.size = size;
+        ss = new ServerSocket(0);
+        port = ss.getLocalPort();
+        lock = new Object();
+    }
+
+    int port() {
+          return port;
+    }
+
+    int size() {
+          return size;
+    }
+
+    // wait a sec before calling this
+    boolean failed() {
+        synchronized(lock) {
+            return failed;
+        }
+    }
+
+    @Override
+    public void run () {
+        int nbytes = 0;
+        Socket s = null;
+
+        synchronized(lock) {
+            try {
+                s = ss.accept();
+
+                InputStream is = s.getInputStream();
+                OutputStream os = s.getOutputStream();
+                os.write("HTTP/1.1 201 OK\r\nContent-length: 0\r\n\r\n".getBytes());
+                int n;
+                byte[] buf = new byte[8000];
+                while ((n=is.read(buf)) != -1) {
+                    nbytes += n;
+                }
+            } catch (IOException e) {
+                System.out.println ("read " + nbytes);
+                System.out.println ("size " + size);
+                failed = nbytes >= size;
+            } finally {
+                try {
+                    ss.close();
+                    if (s != null)
+                        s.close();
+                } catch (IOException e) {}
+            }
+        }
+    }
+}
+
+class RedirectHandler implements HttpHandler {
+    String root;
+    volatile int count = 0;
+
+    RedirectHandler(String root) {
+        this.root = root;
+    }
+
+    @Override
+    public synchronized void handle(HttpExchange t)
+        throws IOException
+    {
+        byte[] buf = new byte[2048];
+        try (InputStream is = t.getRequestBody()) {
+            while (is.read(buf) != -1) ;
+        }
+
+        Headers responseHeaders = t.getResponseHeaders();
+
+        if (count++ < 1) {
+            responseHeaders.add("Location", root + "/foo/" + count);
+        } else {
+            responseHeaders.add("Location", SmokeTest.midSizedFilename);
+        }
+        t.sendResponseHeaders(301, -1);
+        t.close();
+    }
+
+    int count() {
+        return count;
+    }
+
+    void reset() {
+        count = 0;
+    }
+}
+
+class RedirectErrorHandler implements HttpHandler {
+    String root;
+    volatile int count = 1;
+
+    RedirectErrorHandler(String root) {
+        this.root = root;
+    }
+
+    synchronized int count() {
+        return count;
+    }
+
+    synchronized void increment() {
+        count++;
+    }
+
+    @Override
+    public synchronized void handle (HttpExchange t)
+        throws IOException
+    {
+        byte[] buf = new byte[2048];
+        try (InputStream is = t.getRequestBody()) {
+            while (is.read(buf) != -1) ;
+        }
+
+        Headers map = t.getResponseHeaders();
+        String redirect = root + "/foo/" + Integer.toString(count);
+        increment();
+        map.add("Location", redirect);
+        t.sendResponseHeaders(301, -1);
+        t.close();
+    }
+}
+
+class Util {
+    static byte[] readAll(InputStream is) throws IOException {
+        byte[] buf = new byte[1024];
+        byte[] result = new byte[0];
+
+        while (true) {
+            int n = is.read(buf);
+            if (n > 0) {
+                byte[] b1 = new byte[result.length + n];
+                System.arraycopy(result, 0, b1, 0, result.length);
+                System.arraycopy(buf, 0, b1, result.length, n);
+                result = b1;
+            } else if (n == -1) {
+                return result;
+            }
+        }
+    }
+}
+
+class DelayHandler implements HttpHandler {
+
+    CyclicBarrier bar1 = new CyclicBarrier(2);
+    CyclicBarrier bar2 = new CyclicBarrier(2);
+    CyclicBarrier bar3 = new CyclicBarrier(2);
+
+    CyclicBarrier barrier1() {
+        return bar1;
+    }
+
+    CyclicBarrier barrier2() {
+        return bar2;
+    }
+
+    @Override
+    public synchronized void handle(HttpExchange he) throws IOException {
+        byte[] buf = Util.readAll(he.getRequestBody());
+        try {
+            bar1.await();
+            bar2.await();
+        } catch (Exception e) {}
+        he.sendResponseHeaders(200, -1); // will probably fail
+        he.close();
+    }
+
+}
+
+// check for simple hardcoded sequence and use remote address
+// to check.
+// First 4 requests executed in sequence (should use same connection/address)
+// Next 4 requests parallel (should use different addresses)
+// Then send 4 requests in parallel x 100 times (same four addresses used all time)
+
+class KeepAliveHandler implements HttpHandler {
+    volatile int counter = 0;
+
+    HashSet<Integer> portSet = new HashSet<>();
+
+    volatile int[] ports = new int[4];
+
+    void sleep(int n) {
+        try {
+            Thread.sleep(n);
+        } catch (InterruptedException e) {}
+    }
+
+    @Override
+    public synchronized void handle (HttpExchange t)
+        throws IOException
+    {
+        int remotePort = t.getRemoteAddress().getPort();
+        String result = "OK";
+
+        int n = counter++;
+        /// First test
+        if (n < 4) {
+            ports[n] = remotePort;
+        }
+        if (n == 3) {
+            // check all values in ports[] are the same
+            if (ports[0] != ports[1] || ports[2] != ports[3]
+                    || ports[0] != ports[2]) {
+                result = "Error " + Integer.toString(n);
+                System.out.println(result);
+            }
+        }
+        // Second test
+        if (n >=4 && n < 8) {
+            // delay to ensure ports are different
+            sleep(500);
+            ports[n-4] = remotePort;
+        }
+        if (n == 7) {
+            // should be all different
+            if (ports[0] == ports[1] || ports[2] == ports[3]
+                    || ports[0] == ports[2]) {
+                result = "Error " + Integer.toString(n);
+                System.out.println(result);
+                System.out.printf("Ports: %d, %d, %d, %d\n", ports[0], ports[1], ports[2], ports[3]);
+            }
+            // setup for third test
+            for (int i=0; i<4; i++) {
+                portSet.add(ports[i]);
+            }
+        }
+        // Third test
+        if (n > 7) {
+            // just check that port is one of the ones in portSet
+            if (!portSet.contains(remotePort)) {
+                System.out.println ("UNEXPECTED REMOTE PORT " + remotePort);
+                result = "Error " + Integer.toString(n);
+                System.out.println(result);
+            }
+        }
+        byte[] buf = new byte[2048];
+
+        try (InputStream is = t.getRequestBody()) {
+            while (is.read(buf) != -1) ;
+        }
+        t.sendResponseHeaders(200, result.length());
+        OutputStream o = t.getResponseBody();
+        o.write(result.getBytes("US-ASCII"));
+        t.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/SplitResponse.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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 javaapplication16;
+
+import java.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @test
+ * @bug 8087112
+ * @build Server
+ * @run main/othervm -Djava.net.HttpClient.log=all SplitResponse
+ */
+
+/**
+ * Similar test to QuickResponses except that each byte of the response
+ * is sent in a separate packet, which tests the stability of the implementation
+ * for receiving unusual packet sizes.
+ */
+public class SplitResponse {
+
+    static Server server;
+
+    static String response(String body) {
+        return "HTTP/1.1 200 OK\r\nConnection: Close\r\nContent-length: "
+                + Integer.toString(body.length())
+                + "\r\n\r\n" + body;
+    }
+
+    static final String responses[] = {
+        "Lorem ipsum",
+        "dolor sit amet",
+        "consectetur adipiscing elit, sed do eiusmod tempor",
+        "quis nostrud exercitation ullamco",
+        "laboris nisi",
+        "ut",
+        "aliquip ex ea commodo consequat." +
+        "Duis aute irure dolor in reprehenderit in voluptate velit esse" +
+        "cillum dolore eu fugiat nulla pariatur.",
+        "Excepteur sint occaecat cupidatat non proident."
+    };
+
+    public static void main(String[] args) throws Exception {
+        server = new Server(0);
+        URI uri = new URI(server.getURL());
+
+        HttpRequest request;
+        HttpResponse r;
+        CompletableFuture<HttpResponse> cf1;
+
+        for (int i=0; i<responses.length; i++) {
+            cf1 = HttpRequest.create(uri)
+                    .GET()
+                    .responseAsync();
+            String body = responses[i];
+
+            Server.Connection c = server.activity();
+            sendSplitResponse(response(body), c);
+            r = cf1.get();
+            if (r.statusCode()!= 200)
+                throw new RuntimeException("Failed");
+
+            String rxbody = r.body(HttpResponse.asString());
+            System.out.println("received " + rxbody);
+            if (!rxbody.equals(body))
+                throw new RuntimeException("Failed");
+            c.close();
+        }
+        HttpClient.getDefault().executorService().shutdownNow();
+        System.out.println("OK");
+    }
+
+    // send the response one byte at a time with a small delay between bytes
+    // to ensure that each byte is read in a separate read
+    static void sendSplitResponse(String s, Server.Connection conn) {
+        System.out.println("Sending: ");
+        Thread t = new Thread(() -> {
+            try {
+                int len = s.length();
+                for (int i = 0; i < len; i++) {
+                    String onechar = s.substring(i, i + 1);
+                    conn.send(onechar);
+                    Thread.sleep(30);
+                }
+                System.out.println("sent");
+            } catch (IOException | InterruptedException e) {
+            }
+        });
+        t.setDaemon(true);
+        t.start();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/TimeoutTest.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpTimeoutException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @test
+ * @bug 8087112
+ * @run main/othervm TimeoutTest
+ */
+
+public class TimeoutTest {
+
+    static int[] timeouts = {6, 4, 8, 6, 6, 4};
+    static HttpRequest[] rqs = new HttpRequest[timeouts.length];
+    static LinkedBlockingQueue<HttpRequest> queue = new LinkedBlockingQueue<>();
+    static volatile boolean error = false;
+    static ExecutorService executor = Executors.newCachedThreadPool();
+
+    public static void main(String[] args) throws Exception {
+        try {
+            dotest();
+        } finally {
+            HttpClient.getDefault().executorService().shutdownNow();
+            executor.shutdownNow();
+        }
+    }
+    public static void dotest() throws Exception {
+        System.out.println("Test takes over 40 seconds");
+        ServerSocket ss = new ServerSocket(0, 20);
+        int port = ss.getLocalPort();
+
+        URI uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/foo");
+        int i = 0;
+        for (int timeout : timeouts) {
+            HttpRequest request;
+            (request = rqs[i] = HttpRequest.create(uri)
+                .timeout(TimeUnit.SECONDS, timeout)
+                .GET())
+                .responseAsync()
+                .whenComplete((HttpResponse r, Throwable t) -> {
+                    if (!(t.getCause() instanceof HttpTimeoutException)) {
+                        System.out.println("Wrong exception type:" + t.toString());
+                        error = true;
+                    }
+                    if (t != null) {
+                        queue.add(request);
+                    }
+                })
+                .thenAccept((HttpResponse r) -> {
+                    r.bodyAsync(HttpResponse.ignoreBody());
+                });
+            i++;
+        }
+
+        System.out.println("SUBMITTED");
+
+        checkReturnOrder();
+
+        if (error)
+            throw new RuntimeException("Failed");
+
+        // Repeat blocking in separate threads. Use queue to wait.
+        System.out.println("DOING BLOCKING");
+
+        i = 0;
+        for (int timeout : timeouts) {
+            HttpRequest req = HttpRequest.create(uri)
+                .timeout(TimeUnit.SECONDS, timeout)
+                .GET();
+            rqs[i] = req;
+            executor.execute(() -> {
+                try {
+                    req.response().body(HttpResponse.ignoreBody());
+                } catch (HttpTimeoutException e) {
+                    queue.offer(req);
+                } catch (IOException | InterruptedException ee) {
+                    error = true;
+                }
+            });
+            i++;
+        }
+
+        checkReturnOrder();
+
+        if (error)
+            throw new RuntimeException("Failed");
+    }
+
+    static void checkReturnOrder() throws InterruptedException {
+        // wait for exceptions and check order
+        for (int j = 0; j < timeouts.length; j++) {
+            HttpRequest req = queue.take();
+            switch (j) {
+                case 0:
+                case 1:
+                    if (req != rqs[1] && req != rqs[5]) {
+                        System.out.printf("Expected 1 or 5. Got %s\n", getRequest(req));
+                        throw new RuntimeException("Error");
+                    }
+                    break;
+                case 2:
+                case 3:
+                case 4:
+                    if (req != rqs[0] && req != rqs[3] && req != rqs[4]) {
+                        System.out.printf("Expected r1, r4 or r5. Got %s\n", getRequest(req));
+                        throw new RuntimeException("Error");
+                    }
+                    break;
+                case 5:
+                    if (req != rqs[2]) {
+                        System.out.printf("Expected r3. Got %s\n", getRequest(req));
+                        throw new RuntimeException("Error");
+                    }
+            }
+        }
+        System.out.println("Return order ok");
+    }
+
+    static String getRequest(HttpRequest req) {
+        for (int i=0; i<rqs.length; i++) {
+            if (req == rqs[i]) {
+                return "[" + Integer.toString(i) + "]";
+            }
+        }
+        return "unknown";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/docs/files/foo.txt	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,1 @@
+This is foo.txt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/docs/files/notsobigfile.txt	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015, 2016, 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.
+ *
+ * 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.
+ */
+
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
+This is a not so big file at all
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/docs/files/smallfile.txt	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,1792 @@
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
+This is a small file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/0.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy: 0
+
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/1.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 1
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/10.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,22 @@
+// Policy 10
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:*";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/11.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 11
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "socket://127.0.0.1:27301", "CONNECT";
+};
+
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/12.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 11
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "socket://127.0.0.1:27301", "CONNECT";
+};
+
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/15.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,27 @@
+// Policy 11
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "socket://127.0.0.1:27301", "CONNECT";
+
+    // Test checks for this explicitly
+    permission java.net.RuntimePermission "foobar"; 
+};
+
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/2.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 2
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/*", "GET";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/3.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 3
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/redirect/foo.txt", "GET";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/4.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,25 @@
+// Policy 4
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/redirect/foo.txt", "GET";
+    permission java.net.URLPermission "http://127.0.0.1:*/redirect/bar.txt", "GET";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/5.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 5
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/redirect/bar.txt", "GET";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/6.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 6
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "POST";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/7.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 7
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:X-Bar";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/8.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 8
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:X-Foo1,X-Foo,X-Bar";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/9.policy	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,24 @@
+// Policy 9
+grant {
+    // permissions common to all tests
+    permission java.util.PropertyPermission "test.src", "read";
+    permission java.util.PropertyPermission "test.classes", "read";
+    permission java.io.FilePermission "${test.classes}${/}-", "read,write,delete";
+    permission java.net.NetPermission "getDefaultHttpClient";
+    permission java.lang.RuntimePermission "modifyThread";
+    permission java.util.logging.LoggingPermission "control", "";
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen";
+    permission java.io.FilePermission "${test.src}${/}docs${/}-", "read";
+    permission java.lang.RuntimePermission "createClassLoader";
+
+
+    // permissions specific to this test
+    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:*";
+};
+
+// For proxy only. Not being tested
+grant codebase "file:${test.classes}/proxydir/-" {
+    permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
+    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/security/Security.java	Thu Feb 25 23:14:22 2016 +0000
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2015, 2016, 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
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @library /lib/testlibrary/
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @compile ../../../../com/sun/net/httpserver/LogFilter.java
+ * @compile ../../../../com/sun/net/httpserver/FileServerHandler.java
+ * @compile ../ProxyServer.java
+ *
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=0.policy Security 0
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=1.policy Security 1
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=2.policy Security 2
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=3.policy Security 3
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=4.policy Security 4
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=5.policy Security 5
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=6.policy Security 6
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=7.policy Security 7
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=8.policy Security 8
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=9.policy Security 9
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=10.policy Security 10
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=11.policy Security 11
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=12.policy Security 12
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=0.policy Security 13
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=1.policy Security 14
+ * @run main/othervm/secure=java.lang.SecurityManager/policy=15.policy Security 15
+ */
+
+import com.sun.net.httpserver.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.File;
+import java.io.OutputStream;
+import java.lang.reflect.Constructor;
+import java.net.*;
+import java.net.http.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.ByteBuffer;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.function.*;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Security checks test
+ */
+public class Security {
+
+    static HttpServer s1 = null;
+    static ExecutorService executor=null;
+    static int port;
+    static HttpClient client;
+    static String httproot, fileuri, fileroot, redirectroot;
+    static List<HttpClient> clients = new LinkedList<>();
+    static URI uri;
+
+    interface Test {
+        public void execute() throws IOException, InterruptedException;
+    }
+
+    static class TestAndResult {
+        Test test;
+        boolean result;
+
+        TestAndResult (Test t, boolean result) {
+            this.test = t;
+            this.result = result;
+        }
+    }
+
+    static TestAndResult test(boolean result, Test t) {
+        return new TestAndResult(t, result);
+    }
+
+    static TestAndResult[] tests;
+    static String testclasses;
+    static File subdir;
+
+    /**
+     * The ProxyServer class is compiled by jtreg, but we want to
+     * move it so it is not on the application claspath. We want to
+     * load it through a separate classloader so that it has a separate
+     * protection domain and security permissions.
+     *
+     * Its permissions are in the second grant block in each policy file
+     */
+    static void setupProxy() throws IOException, ClassNotFoundException, NoSuchMethodException {
+        testclasses = System.getProperty("test.classes");
+        subdir = new File (testclasses, "proxydir");
+        subdir.mkdir();
+
+        movefile("ProxyServer.class");
+        movefile("ProxyServer$Connection.class");
+        movefile("ProxyServer$1.class");
+
+        URL url = subdir.toURL();
+        System.out.println("URL for class loader = " + url);
+        URLClassLoader urlc = new URLClassLoader(new URL[] {url});
+        proxyClass = Class.forName("ProxyServer", true, urlc);
+        proxyConstructor = proxyClass.getConstructor(Integer.class, Boolean.class);
+    }
+
+    static void movefile(String f) throws IOException {
+        Path src = Paths.get(testclasses, f);
+        Path dest = subdir.toPath().resolve(f);
+        if (!dest.toFile().exists()) {
+            System.out.printf("moving %s to %s\n", src.toString(), dest.toString());
+            Files.move(src, dest,  StandardCopyOption.REPLACE_EXISTING);
+        } else {
+            System.out.printf("NOT moving %s to %s\n", src.toString(), dest.toString());
+        }
+    }
+
+    static Object getProxy(int port, boolean b) throws Exception {
+        return proxyConstructor.newInstance(port, b);
+    }
+
+    static Class<?> proxyClass;
+    static Constructor<?> proxyConstructor;
+
+    static void setupTests() {
+        tests = new TestAndResult[]{
+            // (0) policy does not have permission for file. Should fail
+            test(false, () -> { // Policy 0
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (1) policy has permission for file URL
+            test(true, () -> { //Policy 1
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (2) policy has permission for all file URLs under /files
+            test(true, () -> { // Policy 2
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (3) policy has permission for first URL but not redirected URL
+            test(false, () -> { // Policy 3
+                URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (4) policy has permission for both first URL and redirected URL
+            test(true, () -> { // Policy 4
+                URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (5) policy has permission for redirected but not first URL
+            test(false, () -> { // Policy 5
+                URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (6) policy has permission for file URL, but not method
+            test(false, () -> { //Policy 6
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (7) policy has permission for file URL, method, but not header
+            test(false, () -> { //Policy 7
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .header("X-Foo", "bar")
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (8) policy has permission for file URL, method and header
+            test(true, () -> { //Policy 8
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .header("X-Foo", "bar")
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (9) policy has permission for file URL, method and header
+            test(true, () -> { //Policy 9
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .headers("X-Foo", "bar", "X-Bar", "foo")
+                        .GET();
+                HttpResponse response = request.response();
+            }),
+            // (10) policy has permission for destination URL but not for proxy
+            test(false, () -> { //Policy 10
+                directProxyTest(27208, true);
+            }),
+            // (11) policy has permission for both destination URL and proxy
+            test(true, () -> { //Policy 11
+                directProxyTest(27301, true);
+            }),
+            // (12) policy has permission for both destination URL and proxy
+            test(false, () -> { //Policy 11
+                directProxyTest(28301, false);
+            }),
+            // (13) async version of test 0
+            test(false, () -> { // Policy 0
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                try {
+                    HttpResponse response = request.responseAsync().get();
+                } catch (ExecutionException e) {
+                    if (e.getCause() instanceof SecurityException) {
+                        throw (SecurityException)e.getCause();
+                    } else {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }),
+            // (14) async version of test 1
+            test(true, () -> { //Policy 1
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                try {
+                    HttpResponse response = request.responseAsync().get();
+                } catch (ExecutionException e) {
+                    if (e.getCause() instanceof SecurityException) {
+                        throw (SecurityException)e.getCause();
+                    } else {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }),
+            // (15) check that user provided unprivileged code running on a worker
+            //      thread does not gain ungranted privileges.
+            test(false, () -> { //Policy 12
+                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+                HttpRequest request = client.request(u)
+                        .GET();
+                HttpResponse response = request.response();
+                HttpResponse.BodyProcessor<String> stproc = HttpResponse.asString();
+
+                CompletableFuture<String> cf;
+                cf = response.bodyAsync(new HttpResponse.BodyProcessor<String>() {
+                        public void onResponseBodyChunk(ByteBuffer b) throws IOException {
+                            // do some mischief here
+                            SecurityManager sm = System.getSecurityManager();
+                            System.setSecurityManager(null);
+                            System.setSecurityManager(sm);
+                            // problem if we get this far
+                            stproc.onResponseBodyChunk(b);
+                        }
+                        public String onResponseBodyStart(long contentLength,
+                                        HttpHeaders responseHeaders,
+                                        LongConsumer fc) throws IOException {
+
+                            SecurityManager sm = System.getSecurityManager();
+                            // should succeed.
+                            sm.checkPermission(new RuntimePermission("foobar"));
+                            return stproc.onResponseBodyStart(contentLength,responseHeaders, fc);
+                        }
+                        public String onResponseComplete() throws IOException {
+                            return stproc.onResponseComplete();
+                        }
+                        public void onResponseError(Throwable t) {
+                            stproc.onResponseError(t);
+                        }
+                    }
+                );
+                try {
+                    System.out.println("Body = " + cf.get());// should not reach here
+                } catch (ExecutionException e) {
+                    if (e.getCause() instanceof SecurityException) {
+                        throw (SecurityException)e.getCause();
+                    } else {
+                        throw new RuntimeException(e);
+                    }
+                }
+            })
+        };
+    }
+
+    private static void directProxyTest(int proxyPort, boolean samePort) throws IOException, InterruptedException {
+        Object proxy = null;
+        try {
+            proxy = getProxy(proxyPort, true);
+        } catch (IOException e) {
+            System.out.println("Cannot bind. Not running test");
+            throw new SecurityException("test not run");
+        } catch (Exception ee) {
+            throw new RuntimeException(ee);
+        }
+        System.out.println("Proxy port = " + proxyPort);
+        if (!samePort)
+            proxyPort++;
+
+        HttpClient cl = HttpClient.create()
+                .proxy(ProxySelector.of(
+                                new InetSocketAddress("127.0.0.1", proxyPort)))
+                .build();
+        clients.add(cl);
+
+        URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+        HttpRequest request = cl.request(u)
+                .headers("X-Foo", "bar", "X-Bar", "foo")
+                .GET();
+        HttpResponse response = request.response();
+    }
+
+    static void runtest(Test r, String policy, boolean succeeds) {
+        System.out.println("Using policy file: " + policy);
+        try {
+            r.execute();
+            if (!succeeds) {
+                System.out.println("FAILED: expected security exception");
+                throw new RuntimeException("Failed");
+            }
+            System.out.println (policy + " succeeded as expected");
+        } catch (SecurityException e) {
+            if (succeeds) {
+                System.out.println("FAILED");
+                throw new RuntimeException(e);
+            }
+            System.out.println (policy + " threw exception as expected");
+        } catch (IOException | InterruptedException ee) {
+            throw new RuntimeException(ee);
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        initServer();
+        setupProxy();
+        fileroot = System.getProperty ("test.src")+ "/docs";
+        int testnum = Integer.parseInt(args[0]);
+        String policy = args[0];
+
+        client = HttpClient
+            .create()
+            .followRedirects(HttpClient.Redirect.ALWAYS)
+            .build();
+
+        clients.add(HttpClient.getDefault());
+        clients.add(client);
+
+        try {
+            setupTests();
+            TestAndResult tr = tests[testnum];
+            runtest(tr.test, policy, tr.result);
+        } finally {
+            s1.stop(0);
+            //executor.shutdownNow();
+            for (HttpClient client : clients)
+                client.executorService().shutdownNow();
+        }
+    }
+
+    // create Http Server on port range below. So, we can
+    HttpServer createServer() {
+        HttpServer server;
+        for (int i=25800; i<26800; i++) {
+            InetSocketAddress a = new InetSocketAddress(i);
+            try {
+                server = HttpServer.create(a, 0);
+                return server;
+            } catch (IOException e) {}
+        }
+        return null;
+    }
+
+    public static void initServer() throws Exception {
+        Logger logger = Logger.getLogger("com.sun.net.httpserver");
+        ConsoleHandler ch = new ConsoleHandler();
+        logger.setLevel(Level.ALL);
+        ch.setLevel(Level.ALL);
+        logger.addHandler(ch);
+        String root = System.getProperty ("test.src")+ "/docs";
+        InetSocketAddress addr = new InetSocketAddress (0);
+        s1 = HttpServer.create (addr, 0);
+        if (s1 instanceof HttpsServer) {
+            throw new RuntimeException ("should not be httpsserver");
+        }
+        HttpHandler h = new FileServerHandler (root);
+        HttpContext c = s1.createContext ("/files", h);
+
+        HttpHandler h1 = new RedirectHandler ("/redirect");
+        HttpContext c1 = s1.createContext ("/redirect", h1);
+
+        executor = Executors.newCachedThreadPool();
+        s1.setExecutor (executor);
+        s1.start();
+
+        port = s1.getAddress().getPort();
+        System.out.println("HTTP server port = " + port);
+        httproot = "http://127.0.0.1:" + port + "/files/";
+        redirectroot = "http://127.0.0.1:" + port + "/redirect/";
+        uri = new URI(httproot);
+        fileuri = httproot + "foo.txt";
+    }
+
+    static class RedirectHandler implements HttpHandler {
+
+        String root;
+        int count = 0;
+
+        RedirectHandler(String root) {
+            this.root = root;
+        }
+
+        synchronized int count() {
+            return count;
+        }
+
+        synchronized void increment() {
+            count++;
+        }
+
+        @Override
+        public synchronized void handle(HttpExchange t)
+                throws IOException {
+            byte[] buf = new byte[2048];
+            System.out.println("Server: " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                while (is.read(buf) != -1) ;
+            }
+            increment();
+            if (count() == 1) {
+                Headers map = t.getResponseHeaders();
+                String redirect = "/redirect/bar.txt";
+                map.add("Location", redirect);
+                t.sendResponseHeaders(301, -1);
+                t.close();
+            } else {
+                String response = "Hello world";
+                t.sendResponseHeaders(200, response.length());
+                OutputStream os = t.getResponseBody();
+                os.write(response.getBytes(StandardCharsets.ISO_8859_1));
+                t.close();
+            }
+        }
+    }
+}