changeset 14029:1aaa07a97e58

Merge
author ddehaven
date Mon, 29 Feb 2016 09:00:35 -0800
parents fe7f84c6defd 42794e648cfe
children 0c9bc87633bc
files
diffstat 112 files changed, 18707 insertions(+), 109 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Mon Feb 29 14:19:40 2016 +0530
+++ b/.hgtags	Mon Feb 29 09:00:35 2016 -0800
@@ -349,3 +349,4 @@
 8faf1aec77a9517c69d2f4d8dd146429852ace7f jdk-9+104
 55518739e399a1066c8613e19100d51b38d9f223 jdk-9+105
 6e9ecae50b4e0d37483fb2719202eea5dca026a4 jdk-9+106
+8701b2bb1d2e1b9abc2a9be0933993c7150a9dbe jdk-9+107
--- a/make/lib/Lib-jdk.jdi.gmk	Mon Feb 29 14:19:40 2016 +0530
+++ b/make/lib/Lib-jdk.jdi.gmk	Mon Feb 29 09:00:35 2016 -0800
@@ -47,6 +47,7 @@
       CFLAGS := $(CFLAGS_JDKLIB) -DUSE_MMAP \
           $(LIBDT_SHMEM_CPPFLAGS), \
       LDFLAGS := $(LDFLAGS_JDKLIB), \
+      LDFLAGS_windows := -export:jdwpTransport_OnLoad, \
       LIBS := $(JDKLIB_LIBS), \
       VERSIONINFO_RESOURCE := $(GLOBAL_VERSION_INFO_RESOURCE), \
       RC_FLAGS := $(RC_FLAGS) \
--- a/make/lib/Lib-jdk.jdwp.agent.gmk	Mon Feb 29 14:19:40 2016 +0530
+++ b/make/lib/Lib-jdk.jdwp.agent.gmk	Mon Feb 29 09:00:35 2016 -0800
@@ -46,6 +46,7 @@
     MAPFILE := $(JDK_TOPDIR)/make/mapfiles/libdt_socket/mapfile-vers, \
     LDFLAGS := $(LDFLAGS_JDKLIB) \
         $(call SET_SHARED_LIBRARY_ORIGIN), \
+    LDFLAGS_windows := -export:jdwpTransport_OnLoad, \
     LIBS_linux := -lpthread, \
     LIBS_solaris := -lnsl -lsocket -lc, \
     LIBS_windows := $(JDKLIB_LIBS) ws2_32.lib, \
--- a/make/src/classes/build/tools/module/boot.modules	Mon Feb 29 14:19:40 2016 +0530
+++ b/make/src/classes/build/tools/module/boot.modules	Mon Feb 29 09:00:35 2016 -0800
@@ -2,6 +2,7 @@
 java.compiler
 java.datatransfer
 java.desktop
+java.httpclient
 java.instrument
 java.logging
 java.management
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon Feb 29 09:00:35 2016 -0800
@@ -4137,8 +4137,10 @@
         // The cleanup parameter list (minus the leading Throwable and result parameters) must be a sublist of the
         // target parameter list.
         int cleanupArgIndex = rtype == void.class ? 1 : 2;
-        if (!cleanupParamTypes.subList(cleanupArgIndex, cleanupParamTypes.size()).
-                equals(target.type().parameterList().subList(0, cleanupParamTypes.size() - cleanupArgIndex))) {
+        List<Class<?>> cleanupArgSuffix = cleanupParamTypes.subList(cleanupArgIndex, cleanupParamTypes.size());
+        List<Class<?>> targetParamTypes = target.type().parameterList();
+        if (targetParamTypes.size() < cleanupArgSuffix.size() ||
+                !cleanupArgSuffix.equals(targetParamTypes.subList(0, cleanupParamTypes.size() - cleanupArgIndex))) {
             throw misMatchedTypes("cleanup parameters after (Throwable,result) and target parameter list prefix",
                     cleanup.type(), target.type());
         }
--- a/src/java.base/share/classes/java/net/Authenticator.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/java/net/Authenticator.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.base/share/classes/java/net/JarURLConnection.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/java/net/JarURLConnection.java	Mon Feb 29 09:00:35 2016 -0800
@@ -173,6 +173,14 @@
         }
 
         jarFileURL = new URL(spec.substring(0, separator++));
+        /*
+         * The url argument may have had a runtime fragment appended, so
+         * we need to add a runtime fragment to the jarFileURL to enable
+         * runtime versioning when the underlying jar file is opened.
+         */
+        if ("runtime".equals(url.getRef())) {
+            jarFileURL = new URL(jarFileURL, "#runtime");
+        }
         entryName = null;
 
         /* if ! is the last letter of the innerURL, entryName is null */
--- a/src/java.base/share/classes/java/net/ProxySelector.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/java/net/ProxySelector.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.base/share/classes/java/net/package-info.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/java/net/package-info.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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
--- a/src/java.base/share/classes/java/util/jar/Attributes.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/java/util/jar/Attributes.java	Mon Feb 29 09:00:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
@@ -646,5 +646,13 @@
          * manifest attribute used for package versioning.
          */
         public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
+
+        /**
+         * {@code Name} object for {@code Multi-Release}
+         * manifest attribute that indicates this is a multi-release JAR file.
+         *
+         * @since   9
+         */
+        public static final Name MULTI_RELEASE = new Name("Multi-Release");
     }
 }
--- a/src/java.base/share/classes/java/util/jar/JarFile.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/java/util/jar/JarFile.java	Mon Feb 29 09:00:35 2016 -0800
@@ -28,6 +28,7 @@
 import java.io.*;
 import java.lang.ref.SoftReference;
 import java.net.URL;
+import java.security.PrivilegedAction;
 import java.util.*;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
@@ -37,28 +38,91 @@
 import java.security.AccessController;
 import java.security.CodeSource;
 import jdk.internal.misc.SharedSecrets;
-import sun.security.action.GetPropertyAction;
 import sun.security.util.ManifestEntryVerifier;
 import sun.security.util.SignatureFileVerifier;
 
+import static java.util.jar.Attributes.Name.MULTI_RELEASE;
+
 /**
  * The {@code JarFile} class is used to read the contents of a jar file
  * from any file that can be opened with {@code java.io.RandomAccessFile}.
  * It extends the class {@code java.util.zip.ZipFile} with support
- * for reading an optional {@code Manifest} entry. The
- * {@code Manifest} can be used to specify meta-information about the
- * jar file and its entries.
+ * for reading an optional {@code Manifest} entry, and support for
+ * processing multi-release jar files.  The {@code Manifest} can be used
+ * to specify meta-information about the jar file and its entries.
+ *
+ * <p>A multi-release jar file is a jar file that contains
+ * a manifest with a main attribute named "Multi-Release",
+ * a set of "base" entries, some of which are public classes with public
+ * or protected methods that comprise the public interface of the jar file,
+ * and a set of "versioned" entries contained in subdirectories of the
+ * "META-INF/versions" directory.  The versioned entries are partitioned by the
+ * major version of the Java platform.  A versioned entry, with a version
+ * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides
+ * the base entry as well as any entry with a version number {@code i} where
+ * {@code 8 < i < n}.
+ *
+ * <p>By default, a {@code JarFile} for a multi-release jar file is configured
+ * to process the multi-release jar file as if it were a plain (unversioned) jar
+ * file, and as such an entry name is associated with at most one base entry.
+ * The {@code JarFile} may be configured to process a multi-release jar file by
+ * creating the {@code JarFile} with the
+ * {@link JarFile#JarFile(File, boolean, int, Release)} constructor.  The
+ * {@code Release} object sets a maximum version used when searching for
+ * versioned entries.  When so configured, an entry name
+ * can correspond with at most one base entry and zero or more versioned
+ * entries. A search is required to associate the entry name with the latest
+ * versioned entry whose version is less than or equal to the maximum version
+ * (see {@link #getEntry(String)}).
+ *
+ * <p>Class loaders that utilize {@code JarFile} to load classes from the
+ * contents of {@code JarFile} entries should construct the {@code JarFile}
+ * by invoking the {@link JarFile#JarFile(File, boolean, int, Release)}
+ * constructor with the value {@code Release.RUNTIME} assigned to the last
+ * argument.  This assures that classes compatible with the major
+ * version of the running JVM are loaded from multi-release jar files.
+ *
+ * <p>If the verify flag is on when opening a signed jar file, the content of
+ * the file is verified against its signature embedded inside the file. Please
+ * note that the verification process does not include validating the signer's
+ * certificate. A caller should inspect the return value of
+ * {@link JarEntry#getCodeSigners()} to further determine if the signature
+ * can be trusted.
  *
  * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
  * or method in this class will cause a {@link NullPointerException} to be
  * thrown.
  *
- * If the verify flag is on when opening a signed jar file, the content of the
- * file is verified against its signature embedded inside the file. Please note
- * that the verification process does not include validating the signer's
- * certificate. A caller should inspect the return value of
- * {@link JarEntry#getCodeSigners()} to further determine if the signature
- * can be trusted.
+ * @implNote
+ * <div class="block">
+ * If the API can not be used to configure a {@code JarFile} (e.g. to override
+ * the configuration of a compiled application or library), two {@code System}
+ * properties are available.
+ * <ul>
+ * <li>
+ * {@code jdk.util.jar.version} can be assigned a value that is the
+ * {@code String} representation of a non-negative integer
+ * {@code <= Version.current().major()}.  The value is used to set the effective
+ * runtime version to something other than the default value obtained by
+ * evaluating {@code Version.current().major()}. The effective runtime version
+ * is the version that the {@link JarFile#JarFile(File, boolean, int, Release)}
+ * constructor uses when the value of the last argument is
+ * {@code Release.RUNTIME}.
+ * </li>
+ * <li>
+ * {@code jdk.util.jar.enableMultiRelease} can be assigned one of the three
+ * {@code String} values <em>true</em>, <em>false</em>, or <em>force</em>.  The
+ * value <em>true</em>, the default value, enables multi-release jar file
+ * processing.  The value <em>false</em> disables multi-release jar processing,
+ * ignoring the "Multi-Release" manifest attribute, and the versioned
+ * directories in a multi-release jar file if they exist.  Furthermore,
+ * the method {@link JarFile#isMultiRelease()} returns <em>false</em>. The value
+ * <em>force</em> causes the {@code JarFile} to be initialized to runtime
+ * versioning after construction.  It effectively does the same as this code:
+ * {@code (new JarFile(File, boolean, int, Release.RUNTIME)}.
+ * </li>
+ * </ul>
+ * </div>
  *
  * @author  David Connelly
  * @see     Manifest
@@ -68,26 +132,126 @@
  */
 public
 class JarFile extends ZipFile {
+    private final static int BASE_VERSION;
+    private final static int RUNTIME_VERSION;
+    private final static boolean MULTI_RELEASE_ENABLED;
+    private final static boolean MULTI_RELEASE_FORCED;
     private SoftReference<Manifest> manRef;
     private JarEntry manEntry;
     private JarVerifier jv;
     private boolean jvInitialized;
     private boolean verify;
+    private final int version;
+    private boolean notVersioned;
+    private final boolean runtimeVersioned;
 
     // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
     private boolean hasClassPathAttribute;
     // true if manifest checked for special attributes
     private volatile boolean hasCheckedSpecialAttributes;
 
-    // Set up JavaUtilJarAccess in SharedSecrets
     static {
+        // Set up JavaUtilJarAccess in SharedSecrets
         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
+
+        BASE_VERSION = 8;  // one less than lowest version for versioned entries
+        RUNTIME_VERSION = AccessController.doPrivileged(
+                new PrivilegedAction<Integer>() {
+                    public Integer run() {
+                        Integer v = sun.misc.Version.jdkMajorVersion(); // fixme when JEP 223 Version integrated
+                        Integer i = Integer.getInteger("jdk.util.jar.version", v);
+                        i = i < 0 ? 0 : i;
+                        return i > v ? v : i;
+                    }
+                }
+        );
+        String multi_release = AccessController.doPrivileged(
+                new PrivilegedAction<String>() {
+                    public String run() {
+                        return System.getProperty("jdk.util.jar.enableMultiRelease", "true");
+                    }
+                }
+        );
+        switch (multi_release) {
+            case "true":
+            default:
+                MULTI_RELEASE_ENABLED = true;
+                MULTI_RELEASE_FORCED = false;
+                break;
+            case "false":
+                MULTI_RELEASE_ENABLED = false;
+                MULTI_RELEASE_FORCED = false;
+                break;
+            case "force":
+                MULTI_RELEASE_ENABLED = true;
+                MULTI_RELEASE_FORCED = true;
+                break;
+        }
     }
 
     /**
+     * A set of constants that represent the entries in either the base directory
+     * or one of the versioned directories in a multi-release jar file.  It's
+     * possible for a multi-release jar file to contain versioned directories
+     * that are not represented by the constants of the {@code Release} enum.
+     * In those cases, the entries will not be located by this {@code JarFile}
+     * through the aliasing mechanism, but they can be directly accessed by
+     * specifying the full path name of the entry.
+     *
+     * @since 9
+     */
+    public enum Release {
+        /**
+         * Represents unversioned entries, or entries in "regular", as opposed
+         * to multi-release jar files.
+         */
+        BASE(BASE_VERSION),
+
+        /**
+         * Represents entries found in the META-INF/versions/9 directory of a
+         * multi-release jar file.
+         */
+        VERSION_9(9),
+
+        // fill in the "blanks" for future releases
+
+        /**
+         * Represents entries found in the META-INF/versions/{n} directory of a
+         * multi-release jar file, where {@code n} is the effective runtime
+         * version of the jar file.
+         *
+         * @implNote
+         * <div class="block">
+         * The effective runtime version is determined
+         * by evaluating {@code Version.current().major()} or by using the value
+         * of the {@code jdk.util.jar.version} System property if it exists.
+         * </div>
+         */
+        RUNTIME(RUNTIME_VERSION);
+
+        Release(int version) {
+            this.version = version;
+        }
+
+        private static Release valueOf(int version) {
+            return version <= BASE.value() ? BASE : valueOf("VERSION_" + version);
+        }
+
+        private final int version;
+
+        private int value() {
+            return this.version;
+        }
+    }
+
+    private static final String META_INF = "META-INF/";
+
+    private static final String META_INF_VERSIONS = META_INF + "versions/";
+
+    /**
      * The JAR manifest file name.
      */
-    public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
+    public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF";
 
     /**
      * Creates a new {@code JarFile} to read from the specified
@@ -129,7 +293,6 @@
         this(file, true, ZipFile.OPEN_READ);
     }
 
-
     /**
      * Creates a new {@code JarFile} to read from the specified
      * {@code File} object.
@@ -144,7 +307,6 @@
         this(file, verify, ZipFile.OPEN_READ);
     }
 
-
     /**
      * Creates a new {@code JarFile} to read from the specified
      * {@code File} object in the specified mode.  The mode argument
@@ -162,10 +324,104 @@
      * @since 1.3
      */
     public JarFile(File file, boolean verify, int mode) throws IOException {
+        this(file, verify, mode, Release.BASE);
+        this.notVersioned = true;
+    }
+
+    /**
+     * Creates a new {@code JarFile} to read from the specified
+     * {@code File} object in the specified mode.  The mode argument
+     * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
+     * The version argument configures the {@code JarFile} for processing
+     * multi-release jar files.
+     *
+     * @param file the jar file to be opened for reading
+     * @param verify whether or not to verify the jar file if
+     * it is signed.
+     * @param mode the mode in which the file is to be opened
+     * @param version specifies the release version for a multi-release jar file
+     * @throws IOException if an I/O error has occurred
+     * @throws IllegalArgumentException
+     *         if the {@code mode} argument is invalid
+     * @throws SecurityException if access to the file is denied
+     *         by the SecurityManager
+     * @throws NullPointerException if {@code version} is {@code null}
+     * @since 9
+     */
+    public JarFile(File file, boolean verify, int mode, Release version) throws IOException {
         super(file, mode);
+        Objects.requireNonNull(version);
         this.verify = verify;
+        // version applies to multi-release jar files, ignored for regular jar files
+        this.version = MULTI_RELEASE_FORCED ? RUNTIME_VERSION : version.value();
+        this.runtimeVersioned = version == Release.RUNTIME;
+        assert runtimeVersionExists();
     }
 
+    private boolean runtimeVersionExists() {
+        int version = sun.misc.Version.jdkMajorVersion();  // fixme when JEP 223 integrated
+        try {
+            Release.valueOf(version);
+            return true;
+        } catch (IllegalArgumentException x) {
+            System.err.println("No JarFile.Release object for release " + version);
+            return false;
+        }
+    }
+
+    /**
+     * Returns the maximum version used when searching for versioned entries.
+     *
+     * @return the maximum version, or {@code Release.BASE} if this jar file is
+     *         processed as if it is an unversioned jar file or is not a
+     *         multi-release jar file
+     * @since 9
+     */
+    public final Release getVersion() {
+        if (isMultiRelease()) {
+            return runtimeVersioned ? Release.RUNTIME : Release.valueOf(version);
+        } else {
+            return Release.BASE;
+        }
+    }
+
+    /**
+     * Indicates whether or not this jar file is a multi-release jar file.
+     *
+     * @return true if this JarFile is a multi-release jar file
+     * @since 9
+     */
+    public final boolean isMultiRelease() {
+        // do not call this code in a constructor because some subclasses use
+        // lazy loading of manifest so it won't be available at construction time
+        if (MULTI_RELEASE_ENABLED) {
+            // Doubled-checked locking pattern
+            Boolean result = isMultiRelease;
+            if (result == null) {
+                synchronized (this) {
+                    result = isMultiRelease;
+                    if (result == null) {
+                        Manifest man = null;
+                        try {
+                            man = getManifest();
+                        } catch (IOException e) {
+                            //Ignored, manifest cannot be read
+                        }
+                        isMultiRelease = result = (man != null)
+                                && man.getMainAttributes().containsKey(MULTI_RELEASE)
+                                ? Boolean.TRUE : Boolean.FALSE;
+                    }
+                }
+            }
+            return result == Boolean.TRUE;
+        } else {
+            return false;
+        }
+    }
+    // the following field, isMultiRelease, should only be used in the method
+    // isMultiRelease(), like a static local
+    private volatile Boolean isMultiRelease;    // is jar multi-release?
+
     /**
      * Returns the jar file manifest, or {@code null} if none.
      *
@@ -209,40 +465,87 @@
     }
 
     /**
-     * Returns the {@code JarEntry} for the given entry name or
+     * Returns the {@code JarEntry} for the given base entry name or
      * {@code null} if not found.
      *
+     * <p>If this {@code JarFile} is a multi-release jar file and is configured
+     * to be processed as such, then a search is performed to find and return
+     * a {@code JarEntry} that is the latest versioned entry associated with the
+     * given entry name.  The returned {@code JarEntry} is the versioned entry
+     * corresponding to the given base entry name prefixed with the string
+     * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
+     * which an entry exists.  If such a versioned entry does not exist, then
+     * the {@code JarEntry} for the base entry is returned, otherwise
+     * {@code null} is returned if no entries are found.  The initial value for
+     * the version {@code n} is the maximum version as returned by the method
+     * {@link JarFile#getVersion()}.
+     *
      * @param name the jar file entry name
-     * @return the {@code JarEntry} for the given entry name or
-     *         {@code null} if not found.
+     * @return the {@code JarEntry} for the given entry name, or
+     *         the versioned entry name, or {@code null} if not found
      *
      * @throws IllegalStateException
      *         may be thrown if the jar file has been closed
      *
      * @see java.util.jar.JarEntry
+     *
+     * @implSpec
+     * <div class="block">
+     * This implementation invokes {@link JarFile#getEntry(String)}.
+     * </div>
      */
     public JarEntry getJarEntry(String name) {
         return (JarEntry)getEntry(name);
     }
 
     /**
-     * Returns the {@code ZipEntry} for the given entry name or
+     * Returns the {@code ZipEntry} for the given base entry name or
      * {@code null} if not found.
      *
+     * <p>If this {@code JarFile} is a multi-release jar file and is configured
+     * to be processed as such, then a search is performed to find and return
+     * a {@code ZipEntry} that is the latest versioned entry associated with the
+     * given entry name.  The returned {@code ZipEntry} is the versioned entry
+     * corresponding to the given base entry name prefixed with the string
+     * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
+     * which an entry exists.  If such a versioned entry does not exist, then
+     * the {@code ZipEntry} for the base entry is returned, otherwise
+     * {@code null} is returned if no entries are found.  The initial value for
+     * the version {@code n} is the maximum version as returned by the method
+     * {@link JarFile#getVersion()}.
+     *
      * @param name the jar file entry name
      * @return the {@code ZipEntry} for the given entry name or
-     *         {@code null} if not found
+     *         the versioned entry name or {@code null} if not found
      *
      * @throws IllegalStateException
      *         may be thrown if the jar file has been closed
      *
      * @see java.util.zip.ZipEntry
+     *
+     * @implSpec
+     * <div class="block">
+     * This implementation may return a versioned entry for the requested name
+     * even if there is not a corresponding base entry.  This can occur
+     * if there is a private or package-private versioned entry that matches.
+     * If a subclass overrides this method, assure that the override method
+     * invokes {@code super.getEntry(name)} to obtain all versioned entries.
+     * </div>
      */
     public ZipEntry getEntry(String name) {
         ZipEntry ze = super.getEntry(name);
         if (ze != null) {
             return new JarFileEntry(ze);
         }
+        // no matching base entry, but maybe there is a versioned entry,
+        // like a new private class
+        if (isMultiRelease()) {
+            ze = new ZipEntry(name);
+            ZipEntry vze = getVersionedEntry(ze);
+            if (ze != vze) {
+                return new JarFileEntry(name, vze);
+            }
+        }
         return null;
     }
 
@@ -250,14 +553,42 @@
             Iterator<JarEntry>
     {
         final Enumeration<? extends ZipEntry> e = JarFile.super.entries();
+        ZipEntry ze;
 
         public boolean hasNext() {
-            return e.hasMoreElements();
+            if (notVersioned) {
+                return e.hasMoreElements();
+            }
+            if (ze != null) {
+                return true;
+            }
+            return findNext();
+        }
+
+        private boolean findNext() {
+            while (e.hasMoreElements()) {
+                ZipEntry ze2 = e.nextElement();
+                if (!ze2.getName().startsWith(META_INF_VERSIONS)) {
+                    ze = ze2;
+                    return true;
+                }
+            }
+            return false;
         }
 
         public JarEntry next() {
-            ZipEntry ze = e.nextElement();
-            return new JarFileEntry(ze);
+            ZipEntry ze2;
+
+            if (notVersioned) {
+                ze2 = e.nextElement();
+                return new JarFileEntry(ze2.getName(), ze2);
+            }
+            if (ze != null || findNext()) {
+                ze2 = ze;
+                ze = null;
+                return new JarFileEntry(ze2);
+            }
+            throw new NoSuchElementException();
         }
 
         public boolean hasMoreElements() {
@@ -274,7 +605,19 @@
     }
 
     /**
-     * Returns an enumeration of the jar file entries.
+     * Returns an enumeration of the jar file entries.  The set of entries
+     * returned depends on whether or not the jar file is a multi-release jar
+     * file, and on the constructor used to create the {@code JarFile}.  If the
+     * jar file is not a multi-release jar file, all entries are returned,
+     * regardless of how the {@code JarFile} is created.  If the constructor
+     * does not take a {@code Release} argument, all entries are returned.
+     * If the jar file is a multi-release jar file and the constructor takes a
+     * {@code Release} argument, then the set of entries returned is equivalent
+     * to the set of entries that would be returned if the set was built by
+     * invoking {@link JarFile#getEntry(String)} or
+     * {@link JarFile#getJarEntry(String)} with the name of each base entry in
+     * the jar file.  A base entry is an entry whose path name does not start
+     * with "META-INF/versions/".
      *
      * @return an enumeration of the jar file entries
      * @throws IllegalStateException
@@ -285,10 +628,21 @@
     }
 
     /**
-     * Returns an ordered {@code Stream} over the jar file entries.
+     * Returns an ordered {@code Stream} over all the jar file entries.
      * Entries appear in the {@code Stream} in the order they appear in
-     * the central directory of the jar file.
-     *
+     * the central directory of the jar file.  The set of entries
+     * returned depends on whether or not the jar file is a multi-release jar
+     * file, and on the constructor used to create the {@code JarFile}.  If the
+     * jar file is not a multi-release jar file, all entries are returned,
+     * regardless of how the {@code JarFile} is created.  If the constructor
+     * does not take a {@code Release} argument, all entries are returned.
+     * If the jar file is a multi-release jar file and the constructor takes a
+     * {@code Release} argument, then the set of entries returned is equivalent
+     * to the set of entries that would be returned if the set was built by
+     * invoking {@link JarFile#getEntry(String)} or
+     * {@link JarFile#getJarEntry(String)} with the name of each base entry in
+     * the jar file.  A base entry is an entry whose path name does not start
+     * with "META-INF/versions/".
      * @return an ordered {@code Stream} of entries in this jar file
      * @throws IllegalStateException if the jar file has been closed
      * @since 1.8
@@ -300,14 +654,44 @@
                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
     }
 
+    private ZipEntry searchForVersionedEntry(final int version, String name) {
+        ZipEntry vze = null;
+        String sname = "/" + name;
+        int i = version;
+        while (i > BASE_VERSION) {
+            vze = super.getEntry(META_INF_VERSIONS + i + sname);
+            if (vze != null) break;
+            i--;
+        }
+        return vze;
+    }
+
+    private ZipEntry getVersionedEntry(ZipEntry ze) {
+        ZipEntry vze = null;
+        if (version > BASE_VERSION && !ze.isDirectory()) {
+            String name = ze.getName();
+            if (!name.startsWith(META_INF)) {
+                vze = searchForVersionedEntry(version, name);
+            }
+        }
+        return vze == null ? ze : vze;
+    }
+
     private class JarFileEntry extends JarEntry {
+        final private String name;
+
         JarFileEntry(ZipEntry ze) {
-            super(ze);
+            super(isMultiRelease() ? getVersionedEntry(ze) : ze);
+            this.name = ze.getName();
+        }
+        JarFileEntry(String name, ZipEntry vze) {
+            super(vze);
+            this.name = name;
         }
         public Attributes getAttributes() throws IOException {
             Manifest man = JarFile.this.getManifest();
             if (man != null) {
-                return man.getAttributes(getName());
+                return man.getAttributes(super.getName());
             } else {
                 return null;
             }
@@ -319,7 +703,7 @@
                 throw new RuntimeException(e);
             }
             if (certs == null && jv != null) {
-                certs = jv.getCerts(JarFile.this, this);
+                certs = jv.getCerts(JarFile.this, reifiedEntry());
             }
             return certs == null ? null : certs.clone();
         }
@@ -330,10 +714,22 @@
                 throw new RuntimeException(e);
             }
             if (signers == null && jv != null) {
-                signers = jv.getCodeSigners(JarFile.this, this);
+                signers = jv.getCodeSigners(JarFile.this, reifiedEntry());
             }
             return signers == null ? null : signers.clone();
         }
+        JarFileEntry reifiedEntry() {
+            if (isMultiRelease()) {
+                String entryName = super.getName();
+                return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
+            }
+            return this;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
     }
 
     /*
@@ -491,12 +887,19 @@
         // wrap a verifier stream around the real stream
         return new JarVerifier.VerifierStream(
             getManifestFromReference(),
-            ze instanceof JarFileEntry ?
-            (JarEntry) ze : getJarEntry(ze.getName()),
+            verifiableEntry(ze),
             super.getInputStream(ze),
             jv);
     }
 
+    private JarEntry verifiableEntry(ZipEntry ze) {
+        if (!(ze instanceof JarFileEntry)) {
+            ze = getJarEntry(ze.getName());
+        }
+        // assure the name and entry match for verification
+        return ze == null ? null : ((JarFileEntry)ze).reifiedEntry();
+    }
+
     // Statics for hand-coded Boyer-Moore search
     private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'};
     // The bad character shift for "class-path"
@@ -523,7 +926,7 @@
     private JarEntry getManEntry() {
         if (manEntry == null) {
             // First look up manifest entry using standard name
-            manEntry = getJarEntry(MANIFEST_NAME);
+            ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
             if (manEntry == null) {
                 // If not found, then iterate through all the "META-INF/"
                 // entries to find a match.
@@ -531,12 +934,15 @@
                 if (names != null) {
                     for (String name : names) {
                         if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
-                            manEntry = getJarEntry(name);
+                            manEntry = super.getEntry(name);
                             break;
                         }
                     }
                 }
             }
+            this.manEntry = (manEntry == null)
+                    ? null
+                    : new JarFileEntry(manEntry.getName(), manEntry);
         }
         return manEntry;
     }
--- a/src/java.base/share/classes/sun/misc/URLClassPath.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/sun/misc/URLClassPath.java	Mon Feb 29 09:00:35 2016 -0800
@@ -63,6 +63,7 @@
 import java.util.jar.Manifest;
 import java.util.jar.Attributes;
 import java.util.jar.Attributes.Name;
+import java.util.zip.ZipFile;
 
 import jdk.internal.jimage.ImageLocation;
 import jdk.internal.jimage.ImageReader;
@@ -727,9 +728,10 @@
                 if (!p.exists()) {
                     throw new FileNotFoundException(p.getPath());
                 }
-                return checkJar(new JarFile(p.getPath()));
+                return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ,
+                        JarFile.Release.RUNTIME));
             }
-            URLConnection uc = getBaseURL().openConnection();
+            URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection();
             uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
             JarFile jarFile = ((JarURLConnection)uc).getJarFile();
             return checkJar(jarFile);
@@ -756,7 +758,9 @@
 
             final URL url;
             try {
-                url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
+                // add #runtime fragment to tell JarURLConnection to use
+                // runtime versioning if the underlying jar file is multi-release
+                url = new URL(getBaseURL(), ParseUtil.encodePath(name, false) + "#runtime");
                 if (check) {
                     URLClassPath.check(url);
                 }
--- a/src/java.base/share/classes/sun/net/www/protocol/jar/URLJarFile.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/sun/net/www/protocol/jar/URLJarFile.java	Mon Feb 29 09:00:35 2016 -0800
@@ -65,9 +65,10 @@
     }
 
     static JarFile getJarFile(URL url, URLJarFileCloseController closeController) throws IOException {
-        if (isFileURL(url))
-            return new URLJarFile(url, closeController);
-        else {
+        if (isFileURL(url)) {
+            Release version = "runtime".equals(url.getRef()) ? Release.RUNTIME : Release.BASE;
+            return new URLJarFile(url, closeController, version);
+        } else {
             return retrieve(url, closeController);
         }
     }
@@ -89,8 +90,13 @@
         this.closeController = closeController;
     }
 
-    private URLJarFile(URL url, URLJarFileCloseController closeController) throws IOException {
-        super(ParseUtil.decode(url.getFile()));
+    private URLJarFile(File file, URLJarFileCloseController closeController, Release version) throws IOException {
+        super(file, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE, version);
+        this.closeController = closeController;
+    }
+
+    private URLJarFile(URL url, URLJarFileCloseController closeController, Release version) throws IOException {
+        super(new File(ParseUtil.decode(url.getFile())), true, ZipFile.OPEN_READ, version);
         this.closeController = closeController;
     }
 
@@ -179,14 +185,6 @@
      * Given a URL, retrieves a JAR file, caches it to disk, and creates a
      * cached JAR file object.
      */
-    private static JarFile retrieve(final URL url) throws IOException {
-        return retrieve(url, null);
-    }
-
-    /**
-     * Given a URL, retrieves a JAR file, caches it to disk, and creates a
-     * cached JAR file object.
-     */
      private static JarFile retrieve(final URL url, final URLJarFileCloseController closeController) throws IOException {
         /*
          * See if interface is set, then call retrieve function of the class
@@ -202,6 +200,7 @@
         {
 
             JarFile result = null;
+            Release version = "runtime".equals(url.getRef()) ? Release.RUNTIME : Release.BASE;
 
             /* get the stream before asserting privileges */
             try (final InputStream in = url.openConnection().getInputStream()) {
@@ -211,7 +210,7 @@
                             Path tmpFile = Files.createTempFile("jar_cache", null);
                             try {
                                 Files.copy(in, tmpFile, StandardCopyOption.REPLACE_EXISTING);
-                                JarFile jarFile = new URLJarFile(tmpFile.toFile(), closeController);
+                                JarFile jarFile = new URLJarFile(tmpFile.toFile(), closeController, version);
                                 tmpFile.toFile().deleteOnExit();
                                 return jarFile;
                             } catch (Throwable thr) {
--- a/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java	Mon Feb 29 09:00:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -81,9 +81,6 @@
 
     private boolean serverKeyExchangeReceived;
 
-    private final boolean enableStatusRequestExtension =
-            Debug.getBooleanProperty(
-                    "jdk.tls.client.enableStatusRequestExtension", true);
     private boolean staplingActive = false;
     private X509Certificate[] deferredCerts;
 
@@ -761,7 +758,7 @@
                     type == ExtensionType.EXT_STATUS_REQUEST_V2) {
                 // Only enable the stapling feature if the client asserted
                 // these extensions.
-                if (enableStatusRequestExtension) {
+                if (sslContext.isStaplingEnabled(true)) {
                     staplingActive = true;
                 } else {
                     fatalSE(Alerts.alert_unexpected_message, "Server set " +
@@ -1562,7 +1559,7 @@
         }
 
         // Add status_request and status_request_v2 extensions
-        if (enableStatusRequestExtension) {
+        if (sslContext.isStaplingEnabled(true)) {
             clientHelloMessage.addCertStatusReqListV2Extension();
             clientHelloMessage.addCertStatusRequestExtension();
         }
--- a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 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
@@ -55,7 +55,11 @@
     // DTLS cookie exchange manager
     private volatile HelloCookieManager helloCookieManager;
 
-    private StatusResponseManager statusResponseManager;
+    private final boolean clientEnableStapling = Debug.getBooleanProperty(
+            "jdk.tls.client.enableStatusRequestExtension", true);
+    private final boolean serverEnableStapling = Debug.getBooleanProperty(
+            "jdk.tls.server.enableStatusRequestExtension", false);
+    private volatile StatusResponseManager statusResponseManager;
 
     SSLContextImpl() {
         ephemeralKeyManager = new EphemeralKeyManager();
@@ -80,7 +84,6 @@
             }
         }
         trustManager = chooseTrustManager(tm);
-        statusResponseManager = new StatusResponseManager();
 
         if (sr == null) {
             secureRandom = JsseJce.getSecureRandom();
@@ -258,6 +261,18 @@
     }
 
     StatusResponseManager getStatusResponseManager() {
+        if (serverEnableStapling && statusResponseManager == null) {
+            synchronized (this) {
+                if (statusResponseManager == null) {
+                    if (debug != null && Debug.isOn("sslctx")) {
+                        System.out.println(
+                                "Initializing StatusResponseManager");
+                    }
+                    statusResponseManager = new StatusResponseManager();
+                }
+            }
+        }
+
         return statusResponseManager;
     }
 
@@ -309,6 +324,18 @@
                (cipherSuites == getClientDefaultCipherSuiteList());
     }
 
+    /**
+     * Return whether client or server side stapling has been enabled
+     * for this SSLContextImpl
+     * @param isClient true if the caller is operating in a client side role,
+     * false if acting as a server.
+     * @return true if stapling has been enabled for the specified role, false
+     * otherwise.
+     */
+    boolean isStaplingEnabled(boolean isClient) {
+        return isClient ? clientEnableStapling : serverEnableStapling;
+    }
+
     /*
      * Return the list of all available CipherSuites with a priority of
      * minPriority or above.
--- a/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java	Mon Feb 29 09:00:35 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 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
@@ -118,10 +118,6 @@
                     LegacyAlgorithmConstraints.PROPERTY_TLS_LEGACY_ALGS,
                     new SSLAlgorithmDecomposer());
 
-    // To switch off the status_request[_v2] extensions
-    private static final boolean enableStatusRequestExtension =
-            Debug.getBooleanProperty(
-                    "jdk.tls.server.enableStatusRequestExtension", false);
     private boolean staplingActive = false;
     private long statusRespTimeout;
 
@@ -589,7 +585,7 @@
                 (CertStatusReqListV2Extension)mesg.extensions.get(
                         ExtensionType.EXT_STATUS_REQUEST_V2);
         // Keep stapling active if at least one of the extensions has been set
-        staplingActive = enableStatusRequestExtension &&
+        staplingActive = sslContext.isStaplingEnabled(false) &&
                 (statReqExt != null || statReqExtV2 != null);
 
         /*
@@ -932,19 +928,32 @@
             }
 
             if (statReqType != null && statReqData != null) {
-                // Next, attempt to obtain status responses
                 StatusResponseManager statRespMgr =
                         sslContext.getStatusResponseManager();
-                responseMap = statRespMgr.get(statReqType, statReqData, certs,
-                        statusRespTimeout, TimeUnit.MILLISECONDS);
-                if (!responseMap.isEmpty()) {
-                    // We now can safely assert status_request[_v2] in our
-                    // ServerHello, and know for certain that we can provide
-                    // responses back to this client for this connection.
-                    if (statusRespExt == ExtensionType.EXT_STATUS_REQUEST) {
-                        m1.extensions.add(new CertStatusReqExtension());
-                    } else if (statusRespExt == ExtensionType.EXT_STATUS_REQUEST_V2) {
-                        m1.extensions.add(new CertStatusReqListV2Extension());
+                if (statRespMgr != null) {
+                    responseMap = statRespMgr.get(statReqType, statReqData,
+                            certs, statusRespTimeout, TimeUnit.MILLISECONDS);
+                    if (!responseMap.isEmpty()) {
+                        // We now can safely assert status_request[_v2] in our
+                        // ServerHello, and know for certain that we can provide
+                        // responses back to this client for this connection.
+                        if (statusRespExt == ExtensionType.EXT_STATUS_REQUEST) {
+                            m1.extensions.add(new CertStatusReqExtension());
+                        } else if (statusRespExt ==
+                                ExtensionType.EXT_STATUS_REQUEST_V2) {
+                            m1.extensions.add(
+                                    new CertStatusReqListV2Extension());
+                        }
+                    }
+                } else {
+                    // This should not happen if stapling is active, but
+                    // if lazy initialization of the StatusResponseManager
+                    // doesn't occur we should turn off stapling.
+                    if (debug != null && Debug.isOn("handshake")) {
+                        System.out.println("Warning: lazy initialization " +
+                                "of the StatusResponseManager failed.  " +
+                                "Stapling has been disabled.");
+                        staplingActive = false;
                     }
                 }
             }
--- a/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/unix/classes/sun/net/www/protocol/jar/JarFileFactory.java	Mon Feb 29 09:00:35 2016 -0800
@@ -85,7 +85,7 @@
                 synchronized (instance) {
                     result = getCachedJarFile(url);
                     if (result == null) {
-                        fileCache.put(URLUtil.urlNoFragString(url), local_result);
+                        fileCache.put(urlKey(url), local_result);
                         urlCache.put(local_result, url);
                         result = local_result;
                     } else {
@@ -113,13 +113,13 @@
         synchronized (instance) {
             URL urlRemoved = urlCache.remove(jarFile);
             if (urlRemoved != null)
-                fileCache.remove(URLUtil.urlNoFragString(urlRemoved));
+                fileCache.remove(urlKey(urlRemoved));
         }
     }
 
     private JarFile getCachedJarFile(URL url) {
         assert Thread.holdsLock(instance);
-        JarFile result = fileCache.get(URLUtil.urlNoFragString(url));
+        JarFile result = fileCache.get(urlKey(url));
 
         /* if the JAR file is cached, the permission will always be there */
         if (result != null) {
@@ -149,6 +149,12 @@
         return result;
     }
 
+    private String urlKey(URL url) {
+        String urlstr =  URLUtil.urlNoFragString(url);
+        if ("runtime".equals(url.getRef())) urlstr += "#runtime";
+        return urlstr;
+    }
+
     private Permission getPermission(JarFile jarFile) {
         try {
             URLConnection uc = getConnection(jarFile);
--- a/src/java.base/windows/classes/sun/net/www/protocol/jar/JarFileFactory.java	Mon Feb 29 14:19:40 2016 +0530
+++ b/src/java.base/windows/classes/sun/net/www/protocol/jar/JarFileFactory.java	Mon Feb 29 09:00:35 2016 -0800
@@ -95,7 +95,7 @@
                 synchronized (instance) {
                     result = getCachedJarFile(url);
                     if (result == null) {
-                        fileCache.put(URLUtil.urlNoFragString(url), local_result);
+                        fileCache.put(urlKey(url), local_result);
                         urlCache.put(local_result, url);
                         result = local_result;
                     } else {
@@ -123,13 +123,13 @@
         synchronized (instance) {
             URL urlRemoved = urlCache.remove(jarFile);
             if (urlRemoved != null)
-                fileCache.remove(URLUtil.urlNoFragString(urlRemoved));
+                fileCache.remove(urlKey(urlRemoved));
         }
     }
 
     private JarFile getCachedJarFile(URL url) {
         assert Thread.holdsLock(instance);
-        JarFile result = fileCache.get(URLUtil.urlNoFragString(url));
+        JarFile result = fileCache.get(urlKey(url));
 
         /* if the JAR file is cached, the permission will always be there */
         if (result != null) {
@@ -159,6 +159,12 @@
         return result;
     }
 
+    private String urlKey(URL url) {
+        String urlstr =  URLUtil.urlNoFragString(url);
+        if ("runtime".equals(url.getRef())) urlstr += "#runtime";
+        return urlstr;
+    }
+
     private Permission getPermission(JarFile jarFile) {
         try {
             URLConnection uc = getConnection(jarFile);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.httpclient/share/classes/java/net/http/AsyncEvent.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/AuthenticationFilter.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/BufferHandler.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/ConnectionPool.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/CookieFilter.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Exchange.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/ExchangeImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/ExecutorWrapper.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/FilterFactory.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HeaderFilter.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HeaderParser.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Http1Exchange.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Http1Request.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Http1Response.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Http2ClientImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Http2Connection.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpClient.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpClientBuilderImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpClientImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpConnection.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpHeaders.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpHeaders1.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpHeadersImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpRedirectImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpRequest.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpRequestBuilderImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpRequestImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpResponse.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpResponseImpl.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/HttpTimeoutException.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Log.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/MultiExchange.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/Pair.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/PlainHttpConnection.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/PlainProxyConnection.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/PlainTunnelingConnection.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/RawChannel.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/RedirectFilter.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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/src/java.httpclient/share/classes/java/net/http/ResponseContent.java	Mon Feb 29 09:00:35 2016 -0800
@@ -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);