http-client-branch: more remaining impl types to internal http-client-branch
authorchegar
Tue, 06 Feb 2018 14:10:28 +0000
branchhttp-client-branch
changeset 56079 d23b02f37fce
parent 56078 6c11b48a0695
child 56080 64846522c0d5
http-client-branch: more remaining impl types to internal
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractAsyncSSLConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractSubscription.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLTunnelConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncTriggerEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AuthenticationFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ConnectionPool.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/CookieFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/FilterFactory.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderParser.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1AsyncReceiver.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Exchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1HeaderParser.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Request.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Response.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientBuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientFacade.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponse.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponseImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ImmutableHeaders.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainHttpConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainProxyConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PrivilegedExecutor.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ProxyAuthenticationRequired.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PullPublisher.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PushGroup.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RawChannelImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RedirectFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RequestPublishers.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Response.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseContent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SocketTube.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Stream.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/TimeoutEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowController.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowUpdateSender.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AbstractAsyncSSLConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AbstractSubscription.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncSSLConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncSSLTunnelConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncTriggerEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AuthenticationFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ConnectionPool.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/CookieFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Exchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ExchangeImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/FilterFactory.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HeaderFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HeaderParser.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1AsyncReceiver.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1Exchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1HeaderParser.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1Request.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1Response.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http2ClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http2Connection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpClientBuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpClientFacade.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpRequestBuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpRequestImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpResponseImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ImmutableHeaders.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/MultiExchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PlainHttpConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PlainProxyConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PlainTunnelingConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PrivilegedExecutor.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ProxyAuthenticationRequired.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PullPublisher.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PushGroup.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/RawChannelImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/RedirectFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/RequestPublishers.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Response.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ResponseBodyHandlers.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ResponseContent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/SSLDelegate.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/SocketTube.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Stream.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/TimeoutEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/UntrustedBodyHandler.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/WindowController.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/WindowUpdateSender.java
test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java
test/jdk/java/net/httpclient/whitebox/Driver.java
test/jdk/java/net/httpclient/whitebox/FramesDecoderTestDriver.java
test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/ConnectionPoolTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/Http1HeaderParserTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/RawChannelTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/SelectorTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/ConnectionPoolTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/Http1HeaderParserTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/RawChannelTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/SelectorTest.java
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractAsyncSSLConnection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import javax.net.ssl.SNIHostName;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLParameters;
-
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-
-
-/**
- * Asynchronous version of SSLConnection.
- *
- * There are two concrete implementations of this class: AsyncSSLConnection
- * and AsyncSSLTunnelConnection.
- * This abstraction is useful when downgrading from HTTP/2 to HTTP/1.1 over
- * an SSL connection. See ExchangeImpl::get in the case where an ALPNException
- * is thrown.
- *
- * Note: An AsyncSSLConnection wraps a PlainHttpConnection, while an
- *       AsyncSSLTunnelConnection wraps a PlainTunnelingConnection.
- *       If both these wrapped classes where made to inherit from a
- *       common abstraction then it might be possible to merge
- *       AsyncSSLConnection and AsyncSSLTunnelConnection back into
- *       a single class - and simply use different factory methods to
- *       create different wrappees, but this is left up for further cleanup.
- *
- */
-abstract class AbstractAsyncSSLConnection extends HttpConnection
-{
-    protected final SSLEngine engine;
-    protected final String serverName;
-    protected final SSLParameters sslParameters;
-
-    AbstractAsyncSSLConnection(InetSocketAddress addr,
-                               HttpClientImpl client,
-                               String serverName,
-                               String[] alpn) {
-        super(addr, client);
-        this.serverName = serverName;
-        SSLContext context = client.theSSLContext();
-        sslParameters = createSSLParameters(client, serverName, alpn);
-        Log.logParams(sslParameters);
-        engine = createEngine(context, sslParameters);
-    }
-
-    abstract HttpConnection plainConnection();
-    abstract SSLTube getConnectionFlow();
-
-    final CompletableFuture<String> getALPN() {
-        assert connected();
-        return getConnectionFlow().getALPN();
-    }
-
-    final SSLEngine getEngine() { return engine; }
-
-    private static SSLParameters createSSLParameters(HttpClientImpl client,
-                                                     String serverName,
-                                                     String[] alpn) {
-        SSLParameters sslp = client.sslParameters();
-        SSLParameters sslParameters = Utils.copySSLParameters(sslp);
-        if (alpn != null) {
-            Log.logSSL("AbstractAsyncSSLConnection: Setting application protocols: {0}",
-                       Arrays.toString(alpn));
-            sslParameters.setApplicationProtocols(alpn);
-        } else {
-            Log.logSSL("AbstractAsyncSSLConnection: no applications set!");
-        }
-        if (serverName != null) {
-            sslParameters.setServerNames(List.of(new SNIHostName(serverName)));
-        }
-        return sslParameters;
-    }
-
-    private static SSLEngine createEngine(SSLContext context,
-                                          SSLParameters sslParameters) {
-        SSLEngine engine = context.createSSLEngine();
-        engine.setUseClientMode(true);
-        engine.setSSLParameters(sslParameters);
-        return engine;
-    }
-
-    @Override
-    final boolean isSecure() {
-        return true;
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    static final class SSLConnectionChannel extends DetachedConnectionChannel {
-        final DetachedConnectionChannel delegate;
-        final SSLDelegate sslDelegate;
-        SSLConnectionChannel(DetachedConnectionChannel delegate, SSLDelegate sslDelegate) {
-            this.delegate = delegate;
-            this.sslDelegate = sslDelegate;
-        }
-
-        SocketChannel channel() {
-            return delegate.channel();
-        }
-
-        @Override
-        ByteBuffer read() throws IOException {
-            SSLDelegate.WrapperResult r = sslDelegate.recvData(ByteBuffer.allocate(8192));
-            // TODO: check for closure
-            int n = r.result.bytesProduced();
-            if (n > 0) {
-                return r.buf;
-            } else if (n == 0) {
-                return Utils.EMPTY_BYTEBUFFER;
-            } else {
-                return null;
-            }
-        }
-        @Override
-        long write(ByteBuffer[] buffers, int start, int number) throws IOException {
-            long l = SSLDelegate.countBytes(buffers, start, number);
-            SSLDelegate.WrapperResult r = sslDelegate.sendData(buffers, start, number);
-            if (r.result.getStatus() == SSLEngineResult.Status.CLOSED) {
-                if (l > 0) {
-                    throw new IOException("SSLHttpConnection closed");
-                }
-            }
-            return l;
-        }
-        @Override
-        public void shutdownInput() throws IOException {
-            delegate.shutdownInput();
-        }
-        @Override
-        public void shutdownOutput() throws IOException {
-            delegate.shutdownOutput();
-        }
-        @Override
-        public void close() {
-            delegate.close();
-        }
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    @Override
-    DetachedConnectionChannel detachChannel() {
-        assert client() != null;
-        DetachedConnectionChannel detachedChannel = plainConnection().detachChannel();
-        SSLDelegate sslDelegate = new SSLDelegate(engine,
-                                                  detachedChannel.channel());
-        return new SSLConnectionChannel(detachedChannel, sslDelegate);
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractSubscription.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.concurrent.Flow;
-import jdk.incubator.http.internal.common.Demand;
-
-/**
- * A {@link Flow.Subscription} wrapping a {@link Demand} instance.
- *
- */
-abstract class AbstractSubscription implements Flow.Subscription {
-
-    private final Demand demand = new Demand();
-
-    /**
-     * Returns the subscription's demand.
-     * @return the subscription's demand.
-     */
-    protected Demand demand() { return demand; }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncEvent.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.nio.channels.SelectableChannel;
-
-/**
- * Event handling interface from HttpClientImpl's selector.
- *
- * If REPEATING is set then the event is not cancelled after being posted.
- */
-abstract class AsyncEvent {
-
-    public static final int REPEATING = 0x2; // one off event if not set
-
-    protected final int flags;
-
-    AsyncEvent() {
-        this(0);
-    }
-
-    AsyncEvent(int flags) {
-        this.flags = flags;
-    }
-
-    /** 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 an error occurs during registration, or when the selector has
-     * been shut down. Aborts all exchanges.
-     *
-     * @param ioe  the IOException, or null
-     */
-    public abstract void abort(IOException ioe);
-
-    public boolean repeating() {
-        return (flags & REPEATING) != 0;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.http.internal.common.Utils;
-
-
-/**
- * Asynchronous version of SSLConnection.
- */
-class AsyncSSLConnection extends AbstractAsyncSSLConnection {
-
-    final PlainHttpConnection plainConnection;
-    final PlainHttpPublisher writePublisher;
-    private volatile SSLTube flow;
-
-    AsyncSSLConnection(InetSocketAddress addr,
-                       HttpClientImpl client,
-                       String[] alpn) {
-        super(addr, client, Utils.getServerName(addr), alpn);
-        plainConnection = new PlainHttpConnection(addr, client);
-        writePublisher = new PlainHttpPublisher();
-    }
-
-    @Override
-    PlainHttpConnection plainConnection() {
-        return plainConnection;
-    }
-
-    @Override
-    public CompletableFuture<Void> connectAsync() {
-        return plainConnection
-                .connectAsync()
-                .thenApply( unused -> {
-                    // create the SSLTube wrapping the SocketTube, with the given engine
-                    flow = new SSLTube(engine,
-                                       client().theExecutor(),
-                                       plainConnection.getConnectionFlow());
-                    return null; } );
-    }
-
-    @Override
-    boolean connected() {
-        return plainConnection.connected();
-    }
-
-    @Override
-    HttpPublisher publisher() { return writePublisher; }
-
-    @Override
-    boolean isProxied() {
-        return false;
-    }
-
-    @Override
-    SocketChannel channel() {
-        return plainConnection.channel();
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return ConnectionPool.cacheKey(address, null);
-    }
-
-    @Override
-    public void close() {
-        plainConnection.close();
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        debug.log(Level.DEBUG, "plainConnection.channel().shutdownInput()");
-        plainConnection.channel().shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        debug.log(Level.DEBUG, "plainConnection.channel().shutdownOutput()");
-        plainConnection.channel().shutdownOutput();
-    }
-
-   @Override
-   SSLTube getConnectionFlow() {
-       return flow;
-   }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLTunnelConnection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * An SSL tunnel built on a Plain (CONNECT) TCP tunnel.
- */
-class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
-
-    final PlainTunnelingConnection plainConnection;
-    final PlainHttpPublisher writePublisher;
-    volatile SSLTube flow;
-
-    AsyncSSLTunnelConnection(InetSocketAddress addr,
-                             HttpClientImpl client,
-                             String[] alpn,
-                             InetSocketAddress proxy,
-                             HttpHeaders proxyHeaders)
-    {
-        super(addr, client, Utils.getServerName(addr), alpn);
-        this.plainConnection = new PlainTunnelingConnection(addr, proxy, client, proxyHeaders);
-        this.writePublisher = new PlainHttpPublisher();
-    }
-
-    @Override
-    public CompletableFuture<Void> connectAsync() {
-        debug.log(Level.DEBUG, "Connecting plain tunnel connection");
-        // This will connect the PlainHttpConnection flow, so that
-        // its HttpSubscriber and HttpPublisher are subscribed to the
-        // SocketTube
-        return plainConnection
-                .connectAsync()
-                .thenApply( unused -> {
-                    debug.log(Level.DEBUG, "creating SSLTube");
-                    // create the SSLTube wrapping the SocketTube, with the given engine
-                    flow = new SSLTube(engine,
-                                       client().theExecutor(),
-                                       plainConnection.getConnectionFlow());
-                    return null;} );
-    }
-
-    @Override
-    boolean isTunnel() { return true; }
-
-    @Override
-    boolean connected() {
-        return plainConnection.connected(); // && sslDelegate.connected();
-    }
-
-    @Override
-    HttpPublisher publisher() { return writePublisher; }
-
-    @Override
-    public String toString() {
-        return "AsyncSSLTunnelConnection: " + super.toString();
-    }
-
-    @Override
-    PlainTunnelingConnection plainConnection() {
-        return plainConnection;
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return ConnectionPool.cacheKey(address, plainConnection.proxyAddr);
-    }
-
-    @Override
-    public void close() {
-        plainConnection.close();
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        plainConnection.channel().shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        plainConnection.channel().shutdownOutput();
-    }
-
-    @Override
-    SocketChannel channel() {
-        return plainConnection.channel();
-    }
-
-    @Override
-    boolean isProxied() {
-        return true;
-    }
-
-    @Override
-    SSLTube getConnectionFlow() {
-       return flow;
-   }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncTriggerEvent.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.nio.channels.SelectableChannel;
-import java.util.Objects;
-import java.util.function.Consumer;
-
-/**
- * An asynchronous event which is triggered only once from the selector manager
- * thread as soon as event registration are handled.
- */
-final class AsyncTriggerEvent extends AsyncEvent{
-
-    private final Runnable trigger;
-    private final Consumer<? super IOException> errorHandler;
-    AsyncTriggerEvent(Consumer<? super IOException> errorHandler,
-                      Runnable trigger) {
-        super(0);
-        this.trigger = Objects.requireNonNull(trigger);
-        this.errorHandler = Objects.requireNonNull(errorHandler);
-    }
-    /** Returns null */
-    @Override
-    public SelectableChannel channel() { return null; }
-    /** Returns 0 */
-    @Override
-    public int interestOps() { return 0; }
-    @Override
-    public void handle() { trigger.run(); }
-    @Override
-    public void abort(IOException ioe) { errorHandler.accept(ioe); }
-    @Override
-    public boolean repeating() { return false; }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AuthenticationFilter.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,398 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.PasswordAuthentication;
-import java.net.URI;
-import java.net.InetSocketAddress;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.util.Base64;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.WeakHashMap;
-
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-import static java.net.Authenticator.RequestorType.PROXY;
-import static java.net.Authenticator.RequestorType.SERVER;
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-
-/**
- * Implementation of Http Basic authentication.
- */
-class AuthenticationFilter implements HeaderFilter {
-    volatile MultiExchange<?> exchange;
-    private static final Base64.Encoder encoder = Base64.getEncoder();
-
-    static final int DEFAULT_RETRY_LIMIT = 3;
-
-    static final int retry_limit = Utils.getIntegerNetProperty(
-            "jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT);
-
-    static final int UNAUTHORIZED = 401;
-    static final int PROXY_UNAUTHORIZED = 407;
-
-    private static final List<String> BASIC_DUMMY =
-            List.of("Basic " + Base64.getEncoder()
-                    .encodeToString("o:o".getBytes(ISO_8859_1)));
-
-    // A public no-arg constructor is required by FilterFactory
-    public AuthenticationFilter() {}
-
-    private PasswordAuthentication getCredentials(String header,
-                                                  boolean proxy,
-                                                  HttpRequestImpl req)
-        throws IOException
-    {
-        HttpClientImpl client = exchange.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;
-        URL url = toURL(uri, req.method(), proxy);
-
-        // needs to be instance method in Authenticator
-        return auth.requestPasswordAuthenticationInstance(uri.getHost(),
-                                                          null,
-                                                          uri.getPort(),
-                                                          uri.getScheme(),
-                                                          realm,
-                                                          authscheme,
-                                                          url,
-                                                          rtype
-        );
-    }
-
-    private URL toURL(URI uri, String method, boolean proxy)
-            throws MalformedURLException
-    {
-        if (proxy && "CONNECT".equalsIgnoreCase(method)
-                && "socket".equalsIgnoreCase(uri.getScheme())) {
-            return null; // proxy tunneling
-        }
-        return uri.toURL();
-    }
-
-    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, MultiExchange<?> e) throws IOException {
-        // use preemptive authentication if an entry exists.
-        Cache cache = getCache(e);
-        this.exchange = e;
-
-        // Proxy
-        if (exchange.proxyauth == null) {
-            URI proxyURI = getProxyURI(r);
-            if (proxyURI != null) {
-                CacheEntry ca = cache.get(proxyURI, true);
-                if (ca != null) {
-                    exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca);
-                    addBasicCredentials(r, true, ca.value);
-                }
-            }
-        }
-
-        // Server
-        if (exchange.serverauth == null) {
-            CacheEntry ca = cache.get(r.uri(), false);
-            if (ca != null) {
-                exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca);
-                addBasicCredentials(r, false, ca.value);
-            }
-        }
-    }
-
-    // TODO: refactor into per auth scheme class
-    private static 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;
-        if (proxy) {
-            if (r.isConnect()) {
-                if (!Utils.PROXY_TUNNEL_FILTER
-                        .test(hdrname, List.of(value))) {
-                    Log.logError("{0} disabled", hdrname);
-                    return;
-                }
-            } else if (r.proxy() != null) {
-                if (!Utils.PROXY_FILTER
-                        .test(hdrname, List.of(value))) {
-                    Log.logError("{0} disabled", hdrname);
-                    return;
-                }
-            }
-        }
-        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;
-        }
-
-        AuthInfo retryWithCredentials(PasswordAuthentication pw) {
-            // If the info was already in the cache we need to create a new
-            // instance with fromCache==false so that it's put back in the
-            // cache if authentication succeeds
-            AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw) : this;
-            res.credentials = Objects.requireNonNull(pw);
-            res.retries = retries;
-            return res;
-        }
-
-    }
-
-    @Override
-    public HttpRequestImpl response(Response r) throws IOException {
-        Cache cache = getCache(exchange);
-        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 (exchange.serverauth != null && !exchange.serverauth.fromcache) {
-                AuthInfo au = exchange.serverauth;
-                cache.store(au.scheme, req.uri(), false, au.credentials);
-            }
-            if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
-                AuthInfo au = exchange.proxyauth;
-                cache.store(au.scheme, req.uri(), false, au.credentials);
-            }
-            return null;
-        }
-
-        boolean proxy = status == PROXY_UNAUTHORIZED;
-        String authname = proxy ? "Proxy-Authenticate" : "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
-        }
-
-        if (proxy) {
-            if (r.isConnectResponse) {
-                if (!Utils.PROXY_TUNNEL_FILTER
-                        .test("Proxy-Authorization", BASIC_DUMMY)) {
-                    Log.logError("{0} disabled", "Proxy-Authorization");
-                    return null;
-                }
-            } else if (req.proxy() != null) {
-                if (!Utils.PROXY_FILTER
-                        .test("Proxy-Authorization", BASIC_DUMMY)) {
-                    Log.logError("{0} disabled", "Proxy-Authorization");
-                    return null;
-                }
-            }
-        }
-
-        AuthInfo au = proxy ? exchange.proxyauth : exchange.serverauth;
-        if (au == null) {
-            // if no authenticator, let the user deal with 407/401
-            if (!exchange.client().authenticator().isPresent()) return 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) {
-                exchange.proxyauth = au;
-            } else {
-                exchange.serverauth = au;
-            }
-            addBasicCredentials(req, proxy, pw);
-            return req;
-        } else if (au.retries > retry_limit) {
-            throw new IOException("too many authentication attempts. Limit: " +
-                    Integer.toString(retry_limit));
-        } else {
-            // we sent credentials, but they were rejected
-            if (au.fromcache) {
-                cache.remove(au.cacheEntry);
-            }
-
-            // if no authenticator, let the user deal with 407/401
-            if (!exchange.client().authenticator().isPresent()) return null;
-
-            // try again
-            PasswordAuthentication pw = getCredentials(authval, proxy, req);
-            if (pw == null) {
-                throw new IOException("No credentials provided");
-            }
-            au = au.retryWithCredentials(pw);
-            if (proxy) {
-                exchange.proxyauth = au;
-            } else {
-                exchange.serverauth = au;
-            }
-            addBasicCredentials(req, proxy, au.credentials);
-            au.retries++;
-            return req;
-        }
-    }
-
-    // Use a WeakHashMap to make it possible for the HttpClient to
-    // be garbaged collected when no longer referenced.
-    static final WeakHashMap<HttpClientImpl,Cache> caches = new WeakHashMap<>();
-
-    static synchronized Cache getCache(MultiExchange<?> exchange) {
-        HttpClientImpl client = exchange.client();
-        Cache c = caches.get(client);
-        if (c == null) {
-            c = new Cache();
-            caches.put(client, c);
-        }
-        return c;
-    }
-
-    // Note: Make sure that Cache and CacheEntry do not keep any strong
-    //       reference to the HttpClient: it would prevent the client being
-    //       GC'ed when no longer referenced.
-    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);
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ConnectionPool.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,490 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.Flow;
-import java.util.stream.Collectors;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Http 1.1 connection pool.
- */
-final class ConnectionPool {
-
-    static final long KEEP_ALIVE = Utils.getIntegerNetProperty(
-            "jdk.httpclient.keepalive.timeout", 1200); // seconds
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    // Pools of idle connections
-
-    private final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool;
-    private final HashMap<CacheKey,LinkedList<HttpConnection>> sslPool;
-    private final ExpiryList expiryList;
-    private final String dbgTag; // used for debug
-    boolean stopped;
-
-    /**
-     * 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);
-        }
-    }
-
-    ConnectionPool(long clientId) {
-        this("ConnectionPool("+clientId+")");
-    }
-
-    /**
-     * There should be one of these per HttpClient.
-     */
-    private ConnectionPool(String tag) {
-        dbgTag = tag;
-        plainPool = new HashMap<>();
-        sslPool = new HashMap<>();
-        expiryList = new ExpiryList();
-    }
-
-    final String dbgString() {
-        return dbgTag;
-    }
-
-    synchronized void start() {
-        assert !stopped : "Already stopped";
-    }
-
-    static CacheKey cacheKey(InetSocketAddress destination,
-                             InetSocketAddress proxy)
-    {
-        return new CacheKey(destination, proxy);
-    }
-
-    synchronized HttpConnection getConnection(boolean secure,
-                                              InetSocketAddress addr,
-                                              InetSocketAddress proxy) {
-        if (stopped) return null;
-        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.
-     */
-    void returnToPool(HttpConnection conn) {
-        returnToPool(conn, Instant.now(), KEEP_ALIVE);
-    }
-
-    // Called also by whitebox tests
-    void returnToPool(HttpConnection conn, Instant now, long keepAlive) {
-
-        // Don't call registerCleanupTrigger while holding a lock,
-        // but register it before the connection is added to the pool,
-        // since we don't want to trigger the cleanup if the connection
-        // is not in the pool.
-        CleanupTrigger cleanup = registerCleanupTrigger(conn);
-
-        // it's possible that cleanup may have been called.
-        synchronized(this) {
-            if (cleanup.isDone()) {
-                return;
-            } else if (stopped) {
-                conn.close();
-                return;
-            }
-            if (conn instanceof PlainHttpConnection) {
-                putConnection(conn, plainPool);
-            } else {
-                assert conn.isSecure();
-                putConnection(conn, sslPool);
-            }
-            expiryList.add(conn, now, keepAlive);
-        }
-        //System.out.println("Return to pool: " + conn);
-    }
-
-    private CleanupTrigger registerCleanupTrigger(HttpConnection conn) {
-        // Connect the connection flow to a pub/sub pair that will take the
-        // connection out of the pool and close it if anything happens
-        // while the connection is sitting in the pool.
-        CleanupTrigger cleanup = new CleanupTrigger(conn);
-        FlowTube flow = conn.getConnectionFlow();
-        debug.log(Level.DEBUG, "registering %s", cleanup);
-        flow.connectFlows(cleanup, cleanup);
-        return cleanup;
-    }
-
-    private HttpConnection
-    findConnection(CacheKey key,
-                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
-        LinkedList<HttpConnection> l = pool.get(key);
-        if (l == null || l.isEmpty()) {
-            return null;
-        } else {
-            HttpConnection c = l.removeFirst();
-            expiryList.remove(c);
-            return c;
-        }
-    }
-
-    /* called from cache cleaner only  */
-    private boolean
-    removeFromPool(HttpConnection c,
-                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
-        //System.out.println("cacheCleaner removing: " + c);
-        assert Thread.holdsLock(this);
-        CacheKey k = c.cacheKey();
-        List<HttpConnection> l = pool.get(k);
-        if (l == null || l.isEmpty()) {
-            pool.remove(k);
-            return false;
-        }
-        return l.remove(c);
-    }
-
-    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);
-    }
-
-    /**
-     * Purge expired connection and return the number of milliseconds
-     * in which the next connection is scheduled to expire.
-     * If no connections are scheduled to be purged return 0.
-     * @return the delay in milliseconds in which the next connection will
-     *         expire.
-     */
-    long purgeExpiredConnectionsAndReturnNextDeadline() {
-        if (!expiryList.purgeMaybeRequired()) return 0;
-        return purgeExpiredConnectionsAndReturnNextDeadline(Instant.now());
-    }
-
-    // Used for whitebox testing
-    long purgeExpiredConnectionsAndReturnNextDeadline(Instant now) {
-        long nextPurge = 0;
-
-        // We may be in the process of adding new elements
-        // to the expiry list - but those elements will not
-        // have outlast their keep alive timer yet since we're
-        // just adding them.
-        if (!expiryList.purgeMaybeRequired()) return nextPurge;
-
-        List<HttpConnection> closelist;
-        synchronized (this) {
-            closelist = expiryList.purgeUntil(now);
-            for (HttpConnection c : closelist) {
-                if (c instanceof PlainHttpConnection) {
-                    boolean wasPresent = removeFromPool(c, plainPool);
-                    assert wasPresent;
-                } else {
-                    boolean wasPresent = removeFromPool(c, sslPool);
-                    assert wasPresent;
-                }
-            }
-            nextPurge = now.until(
-                    expiryList.nextExpiryDeadline().orElse(now),
-                    ChronoUnit.MILLIS);
-        }
-        closelist.forEach(this::close);
-        return nextPurge;
-    }
-
-    private void close(HttpConnection c) {
-        try {
-            c.close();
-        } catch (Throwable e) {} // ignore
-    }
-
-    void stop() {
-        List<HttpConnection> closelist = Collections.emptyList();
-        try {
-            synchronized (this) {
-                stopped = true;
-                closelist = expiryList.stream()
-                    .map(e -> e.connection)
-                    .collect(Collectors.toList());
-                expiryList.clear();
-                plainPool.clear();
-                sslPool.clear();
-            }
-        } finally {
-            closelist.forEach(this::close);
-        }
-    }
-
-    static final class ExpiryEntry {
-        final HttpConnection connection;
-        final Instant expiry; // absolute time in seconds of expiry time
-        ExpiryEntry(HttpConnection connection, Instant expiry) {
-            this.connection = connection;
-            this.expiry = expiry;
-        }
-    }
-
-    /**
-     * Manages a LinkedList of sorted ExpiryEntry. The entry with the closer
-     * deadline is at the tail of the list, and the entry with the farther
-     * deadline is at the head. In the most common situation, new elements
-     * will need to be added at the head (or close to it), and expired elements
-     * will need to be purged from the tail.
-     */
-    private static final class ExpiryList {
-        private final LinkedList<ExpiryEntry> list = new LinkedList<>();
-        private volatile boolean mayContainEntries;
-
-        // A loosely accurate boolean whose value is computed
-        // at the end of each operation performed on ExpiryList;
-        // Does not require synchronizing on the ConnectionPool.
-        boolean purgeMaybeRequired() {
-            return mayContainEntries;
-        }
-
-        // Returns the next expiry deadline
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        Optional<Instant> nextExpiryDeadline() {
-            if (list.isEmpty()) return Optional.empty();
-            else return Optional.of(list.getLast().expiry);
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        void add(HttpConnection conn) {
-            add(conn, Instant.now(), KEEP_ALIVE);
-        }
-
-        // Used by whitebox test.
-        void add(HttpConnection conn, Instant now, long keepAlive) {
-            Instant then = now.truncatedTo(ChronoUnit.SECONDS)
-                    .plus(keepAlive, ChronoUnit.SECONDS);
-
-            // Elements with the farther deadline are at the head of
-            // the list. It's more likely that the new element will
-            // have the farthest deadline, and will need to be inserted
-            // at the head of the list, so we're using an ascending
-            // list iterator to find the right insertion point.
-            ListIterator<ExpiryEntry> li = list.listIterator();
-            while (li.hasNext()) {
-                ExpiryEntry entry = li.next();
-
-                if (then.isAfter(entry.expiry)) {
-                    li.previous();
-                    // insert here
-                    li.add(new ExpiryEntry(conn, then));
-                    mayContainEntries = true;
-                    return;
-                }
-            }
-            // last (or first) element of list (the last element is
-            // the first when the list is empty)
-            list.add(new ExpiryEntry(conn, then));
-            mayContainEntries = true;
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        void remove(HttpConnection c) {
-            if (c == null || list.isEmpty()) return;
-            ListIterator<ExpiryEntry> li = list.listIterator();
-            while (li.hasNext()) {
-                ExpiryEntry e = li.next();
-                if (e.connection.equals(c)) {
-                    li.remove();
-                    mayContainEntries = !list.isEmpty();
-                    return;
-                }
-            }
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool.
-        // Purge all elements whose deadline is before now (now included).
-        List<HttpConnection> purgeUntil(Instant now) {
-            if (list.isEmpty()) return Collections.emptyList();
-
-            List<HttpConnection> closelist = new ArrayList<>();
-
-            // elements with the closest deadlines are at the tail
-            // of the queue, so we're going to use a descending iterator
-            // to remove them, and stop when we find the first element
-            // that has not expired yet.
-            Iterator<ExpiryEntry> li = list.descendingIterator();
-            while (li.hasNext()) {
-                ExpiryEntry entry = li.next();
-                // use !isAfter instead of isBefore in order to
-                // remove the entry if its expiry == now
-                if (!entry.expiry.isAfter(now)) {
-                    li.remove();
-                    HttpConnection c = entry.connection;
-                    closelist.add(c);
-                } else break; // the list is sorted
-            }
-            mayContainEntries = !list.isEmpty();
-            return closelist;
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        java.util.stream.Stream<ExpiryEntry> stream() {
-            return list.stream();
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        void clear() {
-            list.clear();
-            mayContainEntries = false;
-        }
-    }
-
-    void cleanup(HttpConnection c, Throwable error) {
-        debug.log(Level.DEBUG,
-                  "%s : ConnectionPool.cleanup(%s)",
-                  String.valueOf(c.getConnectionFlow()),
-                  error);
-        synchronized(this) {
-            if (c instanceof PlainHttpConnection) {
-                removeFromPool(c, plainPool);
-            } else {
-                assert c.isSecure();
-                removeFromPool(c, sslPool);
-            }
-            expiryList.remove(c);
-        }
-        c.close();
-    }
-
-    /**
-     * An object that subscribes to the flow while the connection is in
-     * the pool. Anything that comes in will cause the connection to be closed
-     * and removed from the pool.
-     */
-    private final class CleanupTrigger implements
-            FlowTube.TubeSubscriber, FlowTube.TubePublisher,
-            Flow.Subscription {
-
-        private final HttpConnection connection;
-        private volatile boolean done;
-
-        public CleanupTrigger(HttpConnection connection) {
-            this.connection = connection;
-        }
-
-        public boolean isDone() { return done;}
-
-        private void triggerCleanup(Throwable error) {
-            done = true;
-            cleanup(connection, error);
-        }
-
-        @Override public void request(long n) {}
-        @Override public void cancel() {}
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            subscription.request(1);
-        }
-        @Override
-        public void onError(Throwable error) { triggerCleanup(error); }
-        @Override
-        public void onComplete() { triggerCleanup(null); }
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            triggerCleanup(new IOException("Data received while in pool"));
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            subscriber.onSubscribe(this);
-        }
-
-        @Override
-        public String toString() {
-            return "CleanupTrigger(" + connection.getConnectionFlow() + ")";
-        }
-
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/CookieFilter.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.CookieHandler;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.common.Log;
-
-class CookieFilter implements HeaderFilter {
-
-    public CookieFilter() {
-    }
-
-    @Override
-    public void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException {
-        HttpClientImpl client = e.client();
-        Optional<CookieHandler> cookieHandlerOpt = client.cookieHandler();
-        if (cookieHandlerOpt.isPresent()) {
-            CookieHandler cookieHandler = cookieHandlerOpt.get();
-            Map<String,List<String>> userheaders = r.getUserHeaders().map();
-            Map<String,List<String>> cookies = cookieHandler.get(r.uri(), userheaders);
-
-            // add the returned cookies
-            HttpHeadersImpl systemHeaders = r.getSystemHeaders();
-            if (cookies.isEmpty()) {
-                Log.logTrace("Request: no cookie to add for {0}",
-                             r.uri());
-            } else {
-                Log.logTrace("Request: adding cookies for {0}",
-                             r.uri());
-            }
-            for (String hdrname : cookies.keySet()) {
-                List<String> vals = cookies.get(hdrname);
-                for (String val : vals) {
-                    systemHeaders.addHeader(hdrname, val);
-                }
-            }
-        } else {
-            Log.logTrace("Request: No cookie manager found for {0}",
-                         r.uri());
-        }
-    }
-
-    @Override
-    public HttpRequestImpl response(Response r) throws IOException {
-        HttpHeaders hdrs = r.headers();
-        HttpRequestImpl request = r.request();
-        Exchange<?> e = r.exchange;
-        Log.logTrace("Response: processing cookies for {0}", request.uri());
-        Optional<CookieHandler> cookieHandlerOpt = e.client().cookieHandler();
-        if (cookieHandlerOpt.isPresent()) {
-            CookieHandler cookieHandler = cookieHandlerOpt.get();
-            Log.logTrace("Response: parsing cookies from {0}", hdrs.map());
-            cookieHandler.put(request.uri(), hdrs.map());
-        } else {
-            Log.logTrace("Response: No cookie manager found for {0}",
-                         request.uri());
-        }
-        return null;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,571 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLPermission;
-import java.security.AccessControlContext;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.function.Function;
-
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.common.Log;
-
-import static jdk.incubator.http.internal.common.Utils.permissionForProxy;
-
-/**
- * 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)
- *
- */
-final class Exchange<T> {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    final HttpRequestImpl request;
-    final HttpClientImpl client;
-    volatile ExchangeImpl<T> exchImpl;
-    volatile CompletableFuture<? extends ExchangeImpl<T>> exchangeCF;
-    volatile CompletableFuture<Void> bodyIgnored;
-
-    // used to record possible cancellation raised before the exchImpl
-    // has been established.
-    private volatile IOException failed;
-    final AccessControlContext acc;
-    final MultiExchange<T> multi;
-    final Executor parentExecutor;
-    boolean upgrading; // to HTTP/2
-    final PushGroup<T> pushGroup;
-    final String dbgTag;
-
-    Exchange(HttpRequestImpl request, MultiExchange<T> multi) {
-        this.request = request;
-        this.upgrading = false;
-        this.client = multi.client();
-        this.multi = multi;
-        this.acc = multi.acc;
-        this.parentExecutor = multi.executor;
-        this.pushGroup = multi.pushGroup;
-        this.dbgTag = "Exchange";
-    }
-
-    /* If different AccessControlContext to be used  */
-    Exchange(HttpRequestImpl request,
-             MultiExchange<T> multi,
-             AccessControlContext acc)
-    {
-        this.request = request;
-        this.acc = acc;
-        this.upgrading = false;
-        this.client = multi.client();
-        this.multi = multi;
-        this.parentExecutor = multi.executor;
-        this.pushGroup = multi.pushGroup;
-        this.dbgTag = "Exchange";
-    }
-
-    PushGroup<T> getPushGroup() {
-        return pushGroup;
-    }
-
-    Executor executor() {
-        return parentExecutor;
-    }
-
-    public HttpRequestImpl request() {
-        return request;
-    }
-
-    HttpClientImpl client() {
-        return client;
-    }
-
-
-    public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) {
-        // If we received a 407 while establishing the exchange
-        // there will be no body to read: bodyIgnored will be true,
-        // and exchImpl will be null (if we were trying to establish
-        // an HTTP/2 tunnel through an HTTP/1.1 proxy)
-        if (bodyIgnored != null) return MinimalFuture.completedFuture(null);
-
-        // The connection will not be returned to the pool in the case of WebSocket
-        return exchImpl.readBodyAsync(handler, !request.isWebSocket(), parentExecutor)
-                .whenComplete((r,t) -> exchImpl.completed());
-    }
-
-    /**
-     * Called after a redirect or similar kind of retry where a body might
-     * be sent but we don't want it. Should send a RESET in h2. For http/1.1
-     * we can consume small quantity of data, or close the connection in
-     * other cases.
-     */
-    public CompletableFuture<Void> ignoreBody() {
-        if (bodyIgnored != null) return bodyIgnored;
-        return exchImpl.ignoreBody();
-    }
-
-    /**
-     * Called when a new exchange is created to replace this exchange.
-     * At this point it is guaranteed that readBody/readBodyAsync will
-     * not be called.
-     */
-    public void released() {
-        ExchangeImpl<?> impl = exchImpl;
-        if (impl != null) impl.released();
-        // Don't set exchImpl to null here. We need to keep
-        // it alive until it's replaced by a Stream in wrapForUpgrade.
-        // Setting it to null here might get it GC'ed too early, because
-        // the Http1Response is now only weakly referenced by the Selector.
-    }
-
-    public void cancel() {
-        // cancel can be called concurrently before or at the same time
-        // that the exchange impl is being established.
-        // In that case we won't be able to propagate the cancellation
-        // right away
-        if (exchImpl != null) {
-            exchImpl.cancel();
-        } else {
-            // no impl - can't cancel impl yet.
-            // call cancel(IOException) instead which takes care
-            // of race conditions between impl/cancel.
-            cancel(new IOException("Request cancelled"));
-        }
-    }
-
-    public void cancel(IOException cause) {
-        // If the impl is non null, propagate the exception right away.
-        // Otherwise record it so that it can be propagated once the
-        // exchange impl has been established.
-        ExchangeImpl<?> impl = exchImpl;
-        if (impl != null) {
-            // propagate the exception to the impl
-            debug.log(Level.DEBUG, "Cancelling exchImpl: %s", exchImpl);
-            impl.cancel(cause);
-        } else {
-            // no impl yet. record the exception
-            failed = cause;
-            // now call checkCancelled to recheck the impl.
-            // if the failed state is set and the impl is not null, reset
-            // the failed state and propagate the exception to the impl.
-            checkCancelled();
-        }
-    }
-
-    // This method will raise an exception if one was reported and if
-    // it is possible to do so. If the exception can be raised, then
-    // the failed state will be reset. Otherwise, the failed state
-    // will persist until the exception can be raised and the failed state
-    // can be cleared.
-    // Takes care of possible race conditions.
-    private void checkCancelled() {
-        ExchangeImpl<?> impl = null;
-        IOException cause = null;
-        CompletableFuture<? extends ExchangeImpl<T>> cf = null;
-        if (failed != null) {
-            synchronized(this) {
-                cause = failed;
-                impl = exchImpl;
-                cf = exchangeCF;
-            }
-        }
-        if (cause == null) return;
-        if (impl != null) {
-            // The exception is raised by propagating it to the impl.
-            debug.log(Level.DEBUG, "Cancelling exchImpl: %s", impl);
-            impl.cancel(cause);
-            failed = null;
-        } else {
-            Log.logTrace("Exchange: request [{0}/timeout={1}ms] no impl is set."
-                         + "\n\tCan''t cancel yet with {2}",
-                         request.uri(),
-                         request.timeout().isPresent() ?
-                         // calling duration.toMillis() can throw an exception.
-                         // this is just debugging, we don't care if it overflows.
-                         (request.timeout().get().getSeconds() * 1000
-                          + request.timeout().get().getNano() / 1000000) : -1,
-                         cause);
-            if (cf != null) cf.completeExceptionally(cause);
-        }
-    }
-
-    public void h2Upgrade() {
-        upgrading = true;
-        request.setH2Upgrade(client.client2());
-    }
-
-    synchronized IOException getCancelCause() {
-        return failed;
-    }
-
-    // get/set the exchange impl, solving race condition issues with
-    // potential concurrent calls to cancel() or cancel(IOException)
-    private CompletableFuture<? extends ExchangeImpl<T>>
-    establishExchange(HttpConnection connection) {
-        if (debug.isLoggable(Level.DEBUG)) {
-            debug.log(Level.DEBUG,
-                    "establishing exchange for %s,%n\t proxy=%s",
-                    request,
-                    request.proxy());
-        }
-        // check if we have been cancelled first.
-        Throwable t = getCancelCause();
-        checkCancelled();
-        if (t != null) {
-            return MinimalFuture.failedFuture(t);
-        }
-
-        CompletableFuture<? extends ExchangeImpl<T>> cf, res;
-        cf = ExchangeImpl.get(this, connection);
-        // We should probably use a VarHandle to get/set exchangeCF
-        // instead - as we need CAS semantics.
-        synchronized (this) { exchangeCF = cf; };
-        res = cf.whenComplete((r,x) -> {
-            synchronized(Exchange.this) {
-                if (exchangeCF == cf) exchangeCF = null;
-            }
-        });
-        checkCancelled();
-        return res.thenCompose((eimpl) -> {
-                    // recheck for cancelled, in case of race conditions
-                    exchImpl = eimpl;
-                    IOException tt = getCancelCause();
-                    checkCancelled();
-                    if (tt != null) {
-                        return MinimalFuture.failedFuture(tt);
-                    } else {
-                        // Now we're good to go. Because exchImpl is no longer
-                        // null cancel() will be able to propagate directly to
-                        // the impl after this point ( if needed ).
-                        return MinimalFuture.completedFuture(eimpl);
-                    } });
-    }
-
-    // Completed HttpResponse will be null if response succeeded
-    // will be a non null responseAsync if expect continue returns an error
-
-    public CompletableFuture<Response> responseAsync() {
-        return responseAsyncImpl(null);
-    }
-
-    CompletableFuture<Response> responseAsyncImpl(HttpConnection connection) {
-        SecurityException e = checkPermissions();
-        if (e != null) {
-            return MinimalFuture.failedFuture(e);
-        } else {
-            return responseAsyncImpl0(connection);
-        }
-    }
-
-    // check whether the headersSentCF was completed exceptionally with
-    // ProxyAuthorizationRequired. If so the Response embedded in the
-    // exception is returned. Otherwise we proceed.
-    private CompletableFuture<Response> checkFor407(ExchangeImpl<T> ex, Throwable t,
-                                                    Function<ExchangeImpl<T>,CompletableFuture<Response>> andThen) {
-        t = Utils.getCompletionCause(t);
-        if (t instanceof ProxyAuthenticationRequired) {
-            bodyIgnored = MinimalFuture.completedFuture(null);
-            Response proxyResponse = ((ProxyAuthenticationRequired)t).proxyResponse;
-            Response syntheticResponse = new Response(request, this,
-                    proxyResponse.headers, proxyResponse.statusCode,
-                    proxyResponse.version, true);
-            return MinimalFuture.completedFuture(syntheticResponse);
-        } else if (t != null) {
-            return MinimalFuture.failedFuture(t);
-        } else {
-            return andThen.apply(ex);
-        }
-    }
-
-    // After sending the request headers, if no ProxyAuthorizationRequired
-    // was raised and the expectContinue flag is on, we need to wait
-    // for the 100-Continue response
-    private CompletableFuture<Response> expectContinue(ExchangeImpl<T> ex) {
-        assert request.expectContinue();
-        return ex.getResponseAsync(parentExecutor)
-                .thenCompose((Response r1) -> {
-            Log.logResponse(r1::toString);
-            int rcode = r1.statusCode();
-            if (rcode == 100) {
-                Log.logTrace("Received 100-Continue: sending body");
-                CompletableFuture<Response> cf =
-                        exchImpl.sendBodyAsync()
-                                .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
-                cf = wrapForUpgrade(cf);
-                cf = wrapForLog(cf);
-                return cf;
-            } else {
-                Log.logTrace("Expectation failed: Received {0}",
-                        rcode);
-                if (upgrading && rcode == 101) {
-                    IOException failed = new IOException(
-                            "Unable to handle 101 while waiting for 100");
-                    return MinimalFuture.failedFuture(failed);
-                }
-                return exchImpl.readBodyAsync(this::ignoreBody, false, parentExecutor)
-                        .thenApply(v ->  r1);
-            }
-        });
-    }
-
-    // After sending the request headers, if no ProxyAuthorizationRequired
-    // was raised and the expectContinue flag is off, we can immediately
-    // send the request body and proceed.
-    private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) {
-        assert !request.expectContinue();
-        CompletableFuture<Response> cf = ex.sendBodyAsync()
-                .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
-        cf = wrapForUpgrade(cf);
-        cf = wrapForLog(cf);
-        return cf;
-    }
-
-    CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) {
-        Function<ExchangeImpl<T>, CompletableFuture<Response>> after407Check;
-        bodyIgnored = null;
-        if (request.expectContinue()) {
-            request.addSystemHeader("Expect", "100-Continue");
-            Log.logTrace("Sending Expect: 100-Continue");
-            // wait for 100-Continue before sending body
-            after407Check = this::expectContinue;
-        } else {
-            // send request body and proceed.
-            after407Check = this::sendRequestBody;
-        }
-        // The ProxyAuthorizationRequired can be triggered either by
-        // establishExchange (case of HTTP/2 SSL tunelling through HTTP/1.1 proxy
-        // or by sendHeaderAsync (case of HTTP/1.1 SSL tunelling through HTTP/1.1 proxy
-        // Therefore we handle it with a call to this checkFor407(...) after these
-        // two places.
-        Function<ExchangeImpl<T>, CompletableFuture<Response>> afterExch407Check =
-                (ex) -> ex.sendHeadersAsync()
-                        .handle((r,t) -> this.checkFor407(r, t, after407Check))
-                        .thenCompose(Function.identity());
-        return establishExchange(connection)
-                .handle((r,t) -> this.checkFor407(r,t, afterExch407Check))
-                .thenCompose(Function.identity());
-    }
-
-    private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> cf) {
-        if (upgrading) {
-            return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
-        }
-        return cf;
-    }
-
-    private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
-        if (Log.requests()) {
-            return cf.thenApply(response -> {
-                Log.logResponse(response::toString);
-                return response;
-            });
-        }
-        return cf;
-    }
-
-    HttpResponse.BodySubscriber<T> ignoreBody(int status, HttpHeaders hdrs) {
-        return HttpResponse.BodySubscriber.discard((T)null);
-    }
-
-    // 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<Response>
-    checkForUpgradeAsync(Response resp,
-                         ExchangeImpl<T> ex) {
-
-        int rcode = resp.statusCode();
-        if (upgrading && (rcode == 101)) {
-            Http1Exchange<T> e = (Http1Exchange<T>)ex;
-            // check for 101 switching protocols
-            // 101 responses are not supposed to contain a body.
-            //    => should we fail if there is one?
-            debug.log(Level.DEBUG, "Upgrading async %s", e.connection());
-            return e.readBodyAsync(this::ignoreBody, false, parentExecutor)
-                .thenCompose((T v) -> {// v is null
-                    debug.log(Level.DEBUG, "Ignored body");
-                    // we pass e::getBuffer to allow the ByteBuffers to accumulate
-                    // while we build the Http2Connection
-                    return Http2Connection.createAsync(e.connection(),
-                                                 client.client2(),
-                                                 this, e::drainLeftOverBytes)
-                        .thenCompose((Http2Connection c) -> {
-                            boolean cached = c.offerConnection();
-                            Stream<T> s = c.getStream(1);
-
-                            if (s == null) {
-                                // s can be null if an exception occurred
-                                // asynchronously while sending the preface.
-                                Throwable t = c.getRecordedCause();
-                                IOException ioe;
-                                if (t != null) {
-                                    if (!cached)
-                                        c.close();
-                                    ioe = new IOException("Can't get stream 1: " + t, t);
-                                } else {
-                                    ioe = new IOException("Can't get stream 1");
-                                }
-                                return MinimalFuture.failedFuture(ioe);
-                            }
-                            exchImpl.released();
-                            Throwable t;
-                            // There's a race condition window where an external
-                            // thread (SelectorManager) might complete the
-                            // exchange in timeout at the same time where we're
-                            // trying to switch the exchange impl.
-                            // 'failed' will be reset to null after
-                            // exchImpl.cancel() has completed, so either we
-                            // will observe failed != null here, or we will
-                            // observe e.getCancelCause() != null, or the
-                            // timeout exception will be routed to 's'.
-                            // Either way, we need to relay it to s.
-                            synchronized (this) {
-                                exchImpl = s;
-                                t = failed;
-                            }
-                            // Check whether the HTTP/1.1 was cancelled.
-                            if (t == null) t = e.getCancelCause();
-                            // if HTTP/1.1 exchange was timed out, don't
-                            // try to go further.
-                            if (t instanceof HttpTimeoutException) {
-                                 s.cancelImpl(t);
-                                 return MinimalFuture.failedFuture(t);
-                            }
-                            debug.log(Level.DEBUG, "Getting response async %s", s);
-                            return s.getResponseAsync(null);
-                        });}
-                );
-        }
-        return MinimalFuture.completedFuture(resp);
-    }
-
-    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;
-    }
-
-    /**
-     * Returns the security permission required for the given details.
-     * If method is CONNECT, then uri must be of form "scheme://host:port"
-     */
-    private static URLPermission permissionForServer(URI uri,
-                                                     String method,
-                                                     Map<String, List<String>> headers) {
-        if (method.equals("CONNECT")) {
-            return new URLPermission(uri.toString(), "CONNECT");
-        } else {
-            return Utils.permissionForServer(uri, method, headers.keySet().stream());
-        }
-    }
-
-    /**
-     * Performs the necessary security permission checks required to retrieve
-     * the response. Returns a security exception representing the denied
-     * permission, or null if all checks pass or there is no security manager.
-     */
-    private SecurityException checkPermissions() {
-        String method = request.method();
-        SecurityManager sm = System.getSecurityManager();
-        if (sm == null || method.equals("CONNECT")) {
-            // tunneling will have a null acc, which is fine. The proxy
-            // permission check will have already been preformed.
-            return null;
-        }
-
-        HttpHeaders userHeaders = request.getUserHeaders();
-        URI u = getURIForSecurityCheck();
-        URLPermission p = permissionForServer(u, method, userHeaders.map());
-
-        try {
-            assert acc != null;
-            sm.checkPermission(p, acc);
-        } catch (SecurityException e) {
-            return e;
-        }
-        ProxySelector ps = client.proxySelector();
-        if (ps != null) {
-            if (!method.equals("CONNECT")) {
-                // a non-tunneling HTTP proxy. Need to check access
-                URLPermission proxyPerm = permissionForProxy(request.proxy());
-                if (proxyPerm != null) {
-                    try {
-                        sm.checkPermission(proxyPerm, acc);
-                    } catch (SecurityException e) {
-                        return e;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    HttpClient.Version version() {
-        return multi.version();
-    }
-
-    String dbgString() {
-        return dbgTag;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.function.Function;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * 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<T> {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    private static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("ExchangeImpl"::toString, DEBUG);
-
-    final Exchange<T> exchange;
-
-    ExchangeImpl(Exchange<T> e) {
-        // e == null means a http/2 pushed stream
-        this.exchange = e;
-    }
-
-    final Exchange<T> getExchange() {
-        return exchange;
-    }
-
-
-    /**
-     * Returns the {@link HttpConnection} instance to which this exchange is
-     * assigned.
-     */
-    abstract HttpConnection connection();
-
-    /**
-     * Initiates a new exchange and assigns it to a connection if one exists
-     * already. connection usually null.
-     */
-    static <U> CompletableFuture<? extends ExchangeImpl<U>>
-    get(Exchange<U> exchange, HttpConnection connection)
-    {
-        if (exchange.version() == HTTP_1_1) {
-            DEBUG_LOGGER.log(Level.DEBUG, "get: HTTP/1.1: new Http1Exchange");
-            return createHttp1Exchange(exchange, connection);
-        } else {
-            Http2ClientImpl c2 = exchange.client().client2(); // TODO: improve
-            HttpRequestImpl request = exchange.request();
-            CompletableFuture<Http2Connection> c2f = c2.getConnectionFor(request);
-            DEBUG_LOGGER.log(Level.DEBUG, "get: Trying to get HTTP/2 connection");
-            return c2f.handle((h2c, t) -> createExchangeImpl(h2c, t, exchange, connection))
-                    .thenCompose(Function.identity());
-        }
-    }
-
-    private static <U> CompletableFuture<? extends ExchangeImpl<U>>
-    createExchangeImpl(Http2Connection c,
-                       Throwable t,
-                       Exchange<U> exchange,
-                       HttpConnection connection)
-    {
-        DEBUG_LOGGER.log(Level.DEBUG, "handling HTTP/2 connection creation result");
-        boolean secure = exchange.request().secure();
-        if (t != null) {
-            DEBUG_LOGGER.log(Level.DEBUG,
-                             "handling HTTP/2 connection creation failed: %s",
-                             (Object)t);
-            t = Utils.getCompletionCause(t);
-            if (t instanceof Http2Connection.ALPNException) {
-                Http2Connection.ALPNException ee = (Http2Connection.ALPNException)t;
-                AbstractAsyncSSLConnection as = ee.getConnection();
-                DEBUG_LOGGER.log(Level.DEBUG, "downgrading to HTTP/1.1 with: %s", as);
-                CompletableFuture<? extends ExchangeImpl<U>> ex =
-                        createHttp1Exchange(exchange, as);
-                return ex;
-            } else {
-                DEBUG_LOGGER.log(Level.DEBUG, "HTTP/2 connection creation failed "
-                                  + "with unexpected exception: %s", (Object)t);
-                return MinimalFuture.failedFuture(t);
-            }
-        }
-        if (secure && c== null) {
-            DEBUG_LOGGER.log(Level.DEBUG, "downgrading to HTTP/1.1 ");
-            CompletableFuture<? extends ExchangeImpl<U>> ex =
-                    createHttp1Exchange(exchange, null);
-            return ex;
-        }
-        if (c == null) {
-            // no existing connection. Send request with HTTP 1 and then
-            // upgrade if successful
-            DEBUG_LOGGER.log(Level.DEBUG, "new Http1Exchange, try to upgrade");
-            return createHttp1Exchange(exchange, connection)
-                    .thenApply((e) -> {
-                        exchange.h2Upgrade();
-                        return e;
-                    });
-        } else {
-            DEBUG_LOGGER.log(Level.DEBUG, "creating HTTP/2 streams");
-            Stream<U> s = c.createStream(exchange);
-            CompletableFuture<? extends ExchangeImpl<U>> ex = MinimalFuture.completedFuture(s);
-            return ex;
-        }
-    }
-
-    private static <T> CompletableFuture<Http1Exchange<T>>
-    createHttp1Exchange(Exchange<T> ex, HttpConnection as)
-    {
-        try {
-            return MinimalFuture.completedFuture(new Http1Exchange<>(ex, as));
-        } catch (Throwable e) {
-            return MinimalFuture.failedFuture(e);
-        }
-    }
-
-    /* The following methods have separate HTTP/1.1 and HTTP/2 implementations */
-
-    abstract CompletableFuture<ExchangeImpl<T>> sendHeadersAsync();
-
-    /** Sends a request body, after request headers have been sent. */
-    abstract CompletableFuture<ExchangeImpl<T>> sendBodyAsync();
-
-    abstract CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler,
-                                                boolean returnConnectionToPool,
-                                                Executor executor);
-
-    /**
-     * Ignore/consume the body.
-     */
-    abstract CompletableFuture<Void> ignoreBody();
-
-    /** Gets the response headers. Completes before body is read. */
-    abstract CompletableFuture<Response> getResponseAsync(Executor executor);
-
-
-    /** Cancels a request.  Not currently exposed through API. */
-    abstract void cancel();
-
-    /**
-     * Cancels a request with a cause.  Not currently exposed through API.
-     */
-    abstract void cancel(IOException cause);
-
-    /**
-     * Called when the exchange is released, so that cleanup actions may be
-     * performed - such as deregistering callbacks.
-     * Typically released is called during upgrade, when an HTTP/2 stream
-     * takes over from an Http1Exchange, or when a new exchange is created
-     * during a multi exchange before the final response body was received.
-     */
-    abstract void released();
-
-    /**
-     * Called when the exchange is completed, so that cleanup actions may be
-     * performed - such as deregistering callbacks.
-     * Typically, completed is called at the end of the exchange, when the
-     * final response body has been received (or an error has caused the
-     * completion of the exchange).
-     */
-    abstract void completed();
-
-    /**
-     * Returns true if this exchange was canceled.
-     * @return true if this exchange was canceled.
-     */
-    abstract boolean isCanceled();
-
-    /**
-     * Returns the cause for which this exchange was canceled, if available.
-     * @return the cause for which this exchange was canceled, if available.
-     */
-    abstract Throwable getCancelCause();
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/FilterFactory.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.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 {
-                // Requires a public no arg constructor.
-                HeaderFilter headerFilter = clazz.getConstructor().newInstance();
-                l.add(headerFilter);
-            } catch (ReflectiveOperationException e) {
-                throw new InternalError(e);
-            }
-        }
-        return l;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderFilter.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.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, MultiExchange<?> e) 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(Response r) throws IOException;
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderParser.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.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;
-//        }
-//    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1AsyncReceiver.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,651 +0,0 @@
-/*
- * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.FlowTube.TubeSubscriber;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.ConnectionExpiredException;
-import jdk.incubator.http.internal.common.Utils;
-
-
-/**
- * A helper class that will queue up incoming data until the receiving
- * side is ready to handle it.
- */
-class Http1AsyncReceiver {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    /**
-     * A delegate that can asynchronously receive data from an upstream flow,
-     * parse, it, then possibly transform it and either store it (response
-     * headers) or possibly pass it to a downstream subscriber (response body).
-     * Usually, there will be one Http1AsyncDelegate in charge of receiving
-     * and parsing headers, and another one in charge of receiving, parsing,
-     * and forwarding body. Each will sequentially subscribe with the
-     * Http1AsyncReceiver in turn. There may be additional delegates which
-     * subscribe to the Http1AsyncReceiver, mainly for the purpose of handling
-     * errors while the connection is busy transmitting the request body and the
-     * Http1Exchange::readBody method hasn't been called yet, and response
-     * delegates haven't subscribed yet.
-     */
-    static interface Http1AsyncDelegate {
-        /**
-         * Receives and handles a byte buffer reference.
-         * @param ref A byte buffer reference coming from upstream.
-         * @return false, if the byte buffer reference should be kept in the queue.
-         *         Usually, this means that either the byte buffer reference
-         *         was handled and parsing is finished, or that the receiver
-         *         didn't handle the byte reference at all.
-         *         There may or may not be any remaining data in the
-         *         byte buffer, and the byte buffer reference must not have
-         *         been cleared.
-         *         true, if the byte buffer reference was fully read and
-         *         more data can be received.
-         */
-        public boolean tryAsyncReceive(ByteBuffer ref);
-
-        /**
-         * Called when an exception is raised.
-         * @param ex The raised Throwable.
-         */
-        public void onReadError(Throwable ex);
-
-        /**
-         * Must be called before any other method on the delegate.
-         * The subscription can be either used directly by the delegate
-         * to request more data (e.g. if the delegate is a header parser),
-         * or can be forwarded to a downstream subscriber (if the delegate
-         * is a body parser that wraps a response BodySubscriber).
-         * In all cases, it is the responsibility of the delegate to ensure
-         * that request(n) and demand.tryDecrement() are called appropriately.
-         * No data will be sent to {@code tryAsyncReceive} unless
-         * the subscription has some demand.
-         *
-         * @param s A subscription that allows the delegate to control the
-         *          data flow.
-         */
-        public void onSubscribe(AbstractSubscription s);
-
-        /**
-         * Returns the subscription that was passed to {@code onSubscribe}
-         * @return the subscription that was passed to {@code onSubscribe}..
-         */
-        public AbstractSubscription subscription();
-
-    }
-
-    /**
-     * A simple subclass of AbstractSubscription that ensures the
-     * SequentialScheduler will be run when request() is called and demand
-     * becomes positive again.
-     */
-    private static final class Http1AsyncDelegateSubscription
-            extends AbstractSubscription
-    {
-        private final Runnable onCancel;
-        private final SequentialScheduler scheduler;
-        Http1AsyncDelegateSubscription(SequentialScheduler scheduler,
-                                       Runnable onCancel) {
-            this.scheduler = scheduler;
-            this.onCancel = onCancel;
-        }
-        @Override
-        public void request(long n) {
-            final Demand demand = demand();
-            if (demand.increase(n)) {
-                scheduler.runOrSchedule();
-            }
-        }
-        @Override
-        public void cancel() { onCancel.run();}
-    }
-
-    private final ConcurrentLinkedDeque<ByteBuffer> queue
-            = new ConcurrentLinkedDeque<>();
-    private final SequentialScheduler scheduler =
-            SequentialScheduler.synchronizedScheduler(this::flush);
-    private final Executor executor;
-    private final Http1TubeSubscriber subscriber = new Http1TubeSubscriber();
-    private final AtomicReference<Http1AsyncDelegate> pendingDelegateRef;
-    private final AtomicLong received = new AtomicLong();
-    final AtomicBoolean canRequestMore = new AtomicBoolean();
-
-    private volatile Throwable error;
-    private volatile Http1AsyncDelegate delegate;
-    // This reference is only used to prevent early GC of the exchange.
-    private volatile Http1Exchange<?>  owner;
-    // Only used for checking whether we run on the selector manager thread.
-    private final HttpClientImpl client;
-    private boolean retry;
-
-    public Http1AsyncReceiver(Executor executor, Http1Exchange<?> owner) {
-        this.pendingDelegateRef = new AtomicReference<>();
-        this.executor = executor;
-        this.owner = owner;
-        this.client = owner.client;
-    }
-
-    // This is the main loop called by the SequentialScheduler.
-    // It attempts to empty the queue until the scheduler is stopped,
-    // or the delegate is unregistered, or the delegate is unable to
-    // process the data (because it's not ready or already done), which
-    // it signals by returning 'true';
-    private void flush() {
-        ByteBuffer buf;
-        try {
-            assert !client.isSelectorThread() :
-                    "Http1AsyncReceiver::flush should not run in the selector: "
-                    + Thread.currentThread().getName();
-
-            // First check whether we have a pending delegate that has
-            // just subscribed, and if so, create a Subscription for it
-            // and call onSubscribe.
-            handlePendingDelegate();
-
-            // Then start emptying the queue, if possible.
-            while ((buf = queue.peek()) != null) {
-                Http1AsyncDelegate delegate = this.delegate;
-                debug.log(Level.DEBUG, "Got %s bytes for delegate %s",
-                                       buf.remaining(), delegate);
-                if (!hasDemand(delegate)) {
-                    // The scheduler will be invoked again later when the demand
-                    // becomes positive.
-                    return;
-                }
-
-                assert delegate != null;
-                debug.log(Level.DEBUG, "Forwarding %s bytes to delegate %s",
-                          buf.remaining(), delegate);
-                // The delegate has demand: feed it the next buffer.
-                if (!delegate.tryAsyncReceive(buf)) {
-                    final long remaining = buf.remaining();
-                    debug.log(Level.DEBUG, () -> {
-                        // If the scheduler is stopped, the queue may already
-                        // be empty and the reference may already be released.
-                        String remstr = scheduler.isStopped() ? "" :
-                                " remaining in ref: "
-                                + remaining;
-                        remstr =  remstr
-                                + " total remaining: " + remaining();
-                        return "Delegate done: " + remaining;
-                    });
-                    canRequestMore.set(false);
-                    // The last buffer parsed may have remaining unparsed bytes.
-                    // Don't take it out of the queue.
-                    return; // done.
-                }
-
-                // removed parsed buffer from queue, and continue with next
-                // if available
-                ByteBuffer parsed = queue.remove();
-                canRequestMore.set(queue.isEmpty());
-                assert parsed == buf;
-            }
-
-            // queue is empty: let's see if we should request more
-            checkRequestMore();
-
-        } catch (Throwable t) {
-            Throwable x = error;
-            if (x == null) error = t; // will be handled in the finally block
-            debug.log(Level.DEBUG, "Unexpected error caught in flush()", t);
-        } finally {
-            // Handles any pending error.
-            // The most recently subscribed delegate will get the error.
-            checkForErrors();
-        }
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Handles any pending errors by calling delegate.onReadError().
-     * If the error can be forwarded to the delegate, stops the scheduler.
-     */
-    private void checkForErrors() {
-        // Handles any pending error.
-        // The most recently subscribed delegate will get the error.
-        // If the delegate is null, the error will be handled by the next
-        // delegate that subscribes.
-        // If the queue is not empty, wait until it it is empty before
-        // handling the error.
-        Http1AsyncDelegate delegate = pendingDelegateRef.get();
-        if (delegate == null) delegate = this.delegate;
-        Throwable x = error;
-        if (delegate != null && x != null && queue.isEmpty()) {
-            // forward error only after emptying the queue.
-            final Object captured = delegate;
-            debug.log(Level.DEBUG, () -> "flushing " + x
-                    + "\n\t delegate: " + captured
-                    + "\t\t queue.isEmpty: " + queue.isEmpty());
-            scheduler.stop();
-            delegate.onReadError(x);
-        }
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Figure out whether more data should be requested from the
-     * Http1TubeSubscriber.
-     */
-    private void checkRequestMore() {
-        Http1AsyncDelegate delegate = this.delegate;
-        boolean more = this.canRequestMore.get();
-        boolean hasDemand = hasDemand(delegate);
-        debug.log(Level.DEBUG, () -> "checkRequestMore: "
-                  + "canRequestMore=" + more + ", hasDemand=" + hasDemand
-                  + (delegate == null ? ", delegate=null" : ""));
-        if (hasDemand) {
-            subscriber.requestMore();
-        }
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Return true if the delegate is not null and has some demand.
-     * @param delegate The Http1AsyncDelegate delegate
-     * @return true if the delegate is not null and has some demand
-     */
-    private boolean hasDemand(Http1AsyncDelegate delegate) {
-        if (delegate == null) return false;
-        AbstractSubscription subscription = delegate.subscription();
-        long demand = subscription.demand().get();
-        debug.log(Level.DEBUG, "downstream subscription demand is %s", demand);
-        return demand > 0;
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Handles pending delegate subscription.
-     * Return true if there was some pending delegate subscription and a new
-     * delegate was subscribed, false otherwise.
-     *
-     * @return true if there was some pending delegate subscription and a new
-     *         delegate was subscribed, false otherwise.
-     */
-    private boolean handlePendingDelegate() {
-        Http1AsyncDelegate pending = pendingDelegateRef.get();
-        if (pending != null && pendingDelegateRef.compareAndSet(pending, null)) {
-            Http1AsyncDelegate delegate = this.delegate;
-            if (delegate != null) unsubscribe(delegate);
-            Runnable cancel = () -> {
-                debug.log(Level.DEBUG, "Downstream subscription cancelled by %s", pending);
-                // The connection should be closed, as some data may
-                // be left over in the stream.
-                try {
-                    setRetryOnError(false);
-                    onReadError(new IOException("subscription cancelled"));
-                    unsubscribe(pending);
-                } finally {
-                    Http1Exchange<?> exchg = owner;
-                    stop();
-                    if (exchg != null) exchg.connection().close();
-                }
-            };
-            // The subscription created by a delegate is only loosely
-            // coupled with the upstream subscription. This is partly because
-            // the header/body parser work with a flow of ByteBuffer, whereas
-            // we have a flow List<ByteBuffer> upstream.
-            Http1AsyncDelegateSubscription subscription =
-                    new Http1AsyncDelegateSubscription(scheduler, cancel);
-            pending.onSubscribe(subscription);
-            this.delegate = delegate = pending;
-            final Object captured = delegate;
-            debug.log(Level.DEBUG, () -> "delegate is now " + captured
-                  + ", demand=" + subscription.demand().get()
-                  + ", canRequestMore=" + canRequestMore.get()
-                  + ", queue.isEmpty=" + queue.isEmpty());
-            return true;
-        }
-        return false;
-    }
-
-    synchronized void setRetryOnError(boolean retry) {
-        this.retry = retry;
-    }
-
-    void clear() {
-        debug.log(Level.DEBUG, "cleared");
-        this.pendingDelegateRef.set(null);
-        this.delegate = null;
-        this.owner = null;
-    }
-
-    void subscribe(Http1AsyncDelegate delegate) {
-        synchronized(this) {
-            pendingDelegateRef.set(delegate);
-        }
-        if (queue.isEmpty()) {
-            canRequestMore.set(true);
-        }
-        debug.log(Level.DEBUG, () ->
-                "Subscribed pending " + delegate + " queue.isEmpty: "
-                + queue.isEmpty());
-        // Everything may have been received already. Make sure
-        // we parse it.
-        if (client.isSelectorThread()) {
-            scheduler.runOrSchedule(executor);
-        } else {
-            scheduler.runOrSchedule();
-        }
-    }
-
-    // Used for debugging only!
-    long remaining() {
-        return Utils.remaining(queue.toArray(Utils.EMPTY_BB_ARRAY));
-    }
-
-    void unsubscribe(Http1AsyncDelegate delegate) {
-        synchronized(this) {
-            if (this.delegate == delegate) {
-                debug.log(Level.DEBUG, "Unsubscribed %s", delegate);
-                this.delegate = null;
-            }
-        }
-    }
-
-    // Callback: Consumer of ByteBuffer
-    private void asyncReceive(ByteBuffer buf) {
-        debug.log(Level.DEBUG, "Putting %s bytes into the queue", buf.remaining());
-        received.addAndGet(buf.remaining());
-        queue.offer(buf);
-
-        // This callback is called from within the selector thread.
-        // Use an executor here to avoid doing the heavy lifting in the
-        // selector.
-        scheduler.runOrSchedule(executor);
-    }
-
-    // Callback: Consumer of Throwable
-    void onReadError(Throwable ex) {
-        Http1AsyncDelegate delegate;
-        Throwable recorded;
-        debug.log(Level.DEBUG, "onError: %s", (Object) ex);
-        synchronized (this) {
-            delegate = this.delegate;
-            recorded = error;
-            if (recorded == null) {
-                // retry is set to true by HttpExchange when the connection is
-                // already connected, which means it's been retrieved from
-                // the pool.
-                if (retry && (ex instanceof IOException)) {
-                    // could be either EOFException, or
-                    // IOException("connection reset by peer), or
-                    // SSLHandshakeException resulting from the server having
-                    // closed the SSL session.
-                    if (received.get() == 0) {
-                        // If we receive such an exception before having
-                        // received any byte, then in this case, we will
-                        // throw ConnectionExpiredException
-                        // to try & force a retry of the request.
-                        retry = false;
-                        ex = new ConnectionExpiredException(
-                                "subscription is finished", ex);
-                    }
-                }
-                error = ex;
-            }
-            final Throwable t = (recorded == null ? ex : recorded);
-            debug.log(Level.DEBUG, () -> "recorded " + t
-                    + "\n\t delegate: " + delegate
-                    + "\t\t queue.isEmpty: " + queue.isEmpty(), ex);
-        }
-        if (queue.isEmpty() || pendingDelegateRef.get() != null) {
-            // This callback is called from within the selector thread.
-            // Use an executor here to avoid doing the heavy lifting in the
-            // selector.
-            scheduler.runOrSchedule(executor);
-        }
-    }
-
-    void stop() {
-        debug.log(Level.DEBUG, "stopping");
-        scheduler.stop();
-        delegate = null;
-        owner  = null;
-    }
-
-    /**
-     * Returns the TubeSubscriber for reading from the connection flow.
-     * @return the TubeSubscriber for reading from the connection flow.
-     */
-    TubeSubscriber subscriber() {
-        return subscriber;
-    }
-
-    /**
-     * A simple tube subscriber for reading from the connection flow.
-     */
-    final class Http1TubeSubscriber implements TubeSubscriber {
-        volatile Flow.Subscription subscription;
-        volatile boolean completed;
-        volatile boolean dropped;
-
-        public void onSubscribe(Flow.Subscription subscription) {
-            // supports being called multiple time.
-            // doesn't cancel the previous subscription, since that is
-            // most probably the same as the new subscription.
-            assert this.subscription == null || dropped == false;
-            this.subscription = subscription;
-            dropped = false;
-            canRequestMore.set(true);
-            if (delegate != null) {
-                scheduler.runOrSchedule(executor);
-            }
-        }
-
-        void requestMore() {
-            Flow.Subscription s = subscription;
-            if (s == null) return;
-            if (canRequestMore.compareAndSet(true, false)) {
-                if (!completed && !dropped) {
-                    debug.log(Level.DEBUG,
-                        "Http1TubeSubscriber: requesting one more from upstream");
-                    s.request(1);
-                    return;
-                }
-            }
-            debug.log(Level.DEBUG, "Http1TubeSubscriber: no need to request more");
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            canRequestMore.set(item.isEmpty());
-            for (ByteBuffer buffer : item) {
-                asyncReceive(buffer);
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            onReadError(throwable);
-            completed = true;
-        }
-
-        @Override
-        public void onComplete() {
-            onReadError(new EOFException("EOF reached while reading"));
-            completed = true;
-        }
-
-        public void dropSubscription() {
-            debug.log(Level.DEBUG, "Http1TubeSubscriber: dropSubscription");
-            // we could probably set subscription to null here...
-            // then we might not need the 'dropped' boolean?
-            dropped = true;
-        }
-
-    }
-
-    // Drains the content of the queue into a single ByteBuffer.
-    // The scheduler must be permanently stopped before calling drain().
-    ByteBuffer drain(ByteBuffer initial) {
-        // Revisit: need to clean that up.
-        //
-        ByteBuffer b = initial = (initial == null ? Utils.EMPTY_BYTEBUFFER : initial);
-        assert scheduler.isStopped();
-
-        if (queue.isEmpty()) return b;
-
-        // sanity check: we shouldn't have queued the same
-        // buffer twice.
-        ByteBuffer[] qbb = queue.toArray(new ByteBuffer[queue.size()]);
-        assert java.util.stream.Stream.of(qbb)
-                .collect(Collectors.toSet())
-                .size() == qbb.length : debugQBB(qbb);
-
-        // compute the number of bytes in the queue, the number of bytes
-        // in the initial buffer
-        // TODO: will need revisiting - as it is not guaranteed that all
-        // data will fit in single BB!
-        int size = Utils.remaining(qbb, Integer.MAX_VALUE);
-        int remaining = b.remaining();
-        int free = b.capacity() - b.position() - remaining;
-        debug.log(Level.DEBUG,
-            "Flushing %s bytes from queue into initial buffer (remaining=%s, free=%s)",
-            size, remaining, free);
-
-        // check whether the initial buffer has enough space
-        if (size > free) {
-            debug.log(Level.DEBUG,
-                    "Allocating new buffer for initial: %s", (size + remaining));
-            // allocates a new buffer and copy initial to it
-            b = ByteBuffer.allocate(size + remaining);
-            Utils.copy(initial, b);
-            assert b.position() == remaining;
-            b.flip();
-            assert b.position() == 0;
-            assert b.limit() == remaining;
-            assert b.remaining() == remaining;
-        }
-
-        // store position and limit
-        int pos = b.position();
-        int limit = b.limit();
-        assert limit - pos == remaining;
-        assert b.capacity() >= remaining + size
-                : "capacity: " + b.capacity()
-                + ", remaining: " + b.remaining()
-                + ", size: " + size;
-
-        // prepare to copy the content of the queue
-        b.position(limit);
-        b.limit(pos + remaining + size);
-        assert b.remaining() >= size :
-                "remaining: " + b.remaining() + ", size: " + size;
-
-        // copy the content of the queue
-        int count = 0;
-        for (int i=0; i<qbb.length; i++) {
-            ByteBuffer b2 = qbb[i];
-            int r = b2.remaining();
-            assert b.remaining() >= r : "need at least " + r + " only "
-                    + b.remaining() + " available";
-            int copied = Utils.copy(b2, b);
-            assert copied == r : "copied="+copied+" available="+r;
-            assert b2.remaining() == 0;
-            count += copied;
-        }
-        assert count == size;
-        assert b.position() == pos + remaining + size :
-                "b.position="+b.position()+" != "+pos+"+"+remaining+"+"+size;
-
-        // reset limit and position
-        b.limit(limit+size);
-        b.position(pos);
-
-        // we can clear the refs
-        queue.clear();
-        final ByteBuffer bb = b;
-        debug.log(Level.DEBUG, () -> "Initial buffer now has " + bb.remaining()
-                + " pos=" + bb.position() + " limit=" + bb.limit());
-
-        return b;
-    }
-
-    private String debugQBB(ByteBuffer[] qbb) {
-        StringBuilder msg = new StringBuilder();
-        List<ByteBuffer> lbb = Arrays.asList(qbb);
-        Set<ByteBuffer> sbb = new HashSet<>(Arrays.asList(qbb));
-
-        int uniquebb = sbb.size();
-        msg.append("qbb: ").append(lbb.size())
-           .append(" (unique: ").append(uniquebb).append("), ")
-           .append("duplicates: ");
-        String sep = "";
-        for (ByteBuffer b : lbb) {
-            if (!sbb.remove(b)) {
-                msg.append(sep)
-                   .append(String.valueOf(b))
-                   .append("[remaining=")
-                   .append(b.remaining())
-                   .append(", position=")
-                   .append(b.position())
-                   .append(", capacity=")
-                   .append(b.capacity())
-                   .append("]");
-                sep = ", ";
-            }
-        }
-        return msg.toString();
-    }
-
-    volatile String dbgTag;
-    String dbgString() {
-        String tag = dbgTag;
-        if (tag == null) {
-            String flowTag = null;
-            Http1Exchange<?> exchg = owner;
-            Object flow = (exchg != null)
-                    ? exchg.connection().getConnectionFlow()
-                    : null;
-            flowTag = tag = flow == null ? null: (String.valueOf(flow));
-            if (flowTag != null) {
-                dbgTag = tag = flowTag + " Http1AsyncReceiver";
-            } else {
-                tag = "Http1AsyncReceiver";
-            }
-        }
-        return tag;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Exchange.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,616 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-
-/**
- * Encapsulates one HTTP/1.1 request/response exchange.
- */
-class Http1Exchange<T> extends ExchangeImpl<T> {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    private static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("Http1Exchange"::toString, DEBUG);
-
-    final HttpRequestImpl request; // main request
-    final Http1Request requestAction;
-    private volatile Http1Response<T> response;
-    final HttpConnection connection;
-    final HttpClientImpl client;
-    final Executor executor;
-    private final Http1AsyncReceiver asyncReceiver;
-
-    /** Records a possible cancellation raised before any operation
-     * has been initiated, or an error received while sending the request. */
-    private Throwable failed;
-    private final List<CompletableFuture<?>> operations; // used for cancel
-
-    /** Must be held when operating on any internal state or data. */
-    private final Object lock = new Object();
-
-    /** Holds the outgoing data, either the headers or a request body part. Or
-     * an error from the request body publisher. At most there can be ~2 pieces
-     * of outgoing data ( onComplete|onError can be invoked without demand ).*/
-    final ConcurrentLinkedDeque<DataPair> outgoing = new ConcurrentLinkedDeque<>();
-
-    /** The write publisher, responsible for writing the complete request ( both
-     * headers and body ( if any ). */
-    private final Http1Publisher writePublisher = new Http1Publisher();
-
-    /** Completed when the header have been published, or there is an error */
-    private final CompletableFuture<ExchangeImpl<T>> headersSentCF  = new MinimalFuture<>();
-     /** Completed when the body has been published, or there is an error */
-    private final CompletableFuture<ExchangeImpl<T>> bodySentCF = new MinimalFuture<>();
-
-    /** The subscriber to the request's body published. Maybe null. */
-    private volatile Http1BodySubscriber bodySubscriber;
-
-    enum State { INITIAL,
-                 HEADERS,
-                 BODY,
-                 ERROR,          // terminal state
-                 COMPLETING,
-                 COMPLETED }     // terminal state
-
-    private State state = State.INITIAL;
-
-    /** A carrier for either data or an error. Used to carry data, and communicate
-     * errors from the request ( both headers and body ) to the exchange. */
-    static class DataPair {
-        Throwable throwable;
-        List<ByteBuffer> data;
-        DataPair(List<ByteBuffer> data, Throwable throwable){
-            this.data = data;
-            this.throwable = throwable;
-        }
-        @Override
-        public String toString() {
-            return "DataPair [data=" + data + ", throwable=" + throwable + "]";
-        }
-    }
-
-    /** An abstract supertype for HTTP/1.1 body subscribers. There are two
-     * concrete implementations: {@link Http1Request.StreamSubscriber}, and
-     * {@link Http1Request.FixedContentSubscriber}, for receiving chunked and
-     * fixed length bodies, respectively. */
-    static abstract class Http1BodySubscriber implements Flow.Subscriber<ByteBuffer> {
-        protected volatile Flow.Subscription subscription;
-        protected volatile boolean complete;
-
-        /** Final sentinel in the stream of request body. */
-        static final List<ByteBuffer> COMPLETED = List.of(ByteBuffer.allocate(0));
-
-        void request(long n) {
-            DEBUG_LOGGER.log(Level.DEBUG, () ->
-                "Http1BodySubscriber requesting " + n + ", from " + subscription);
-            subscription.request(n);
-        }
-
-        static Http1BodySubscriber completeSubscriber() {
-            return new Http1BodySubscriber() {
-                @Override public void onSubscribe(Flow.Subscription subscription) { error(); }
-                @Override public void onNext(ByteBuffer item) { error(); }
-                @Override public void onError(Throwable throwable) { error(); }
-                @Override public void onComplete() { error(); }
-                private void error() {
-                    throw new InternalError("should not reach here");
-                }
-            };
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "HTTP/1.1 " + request.toString();
-    }
-
-    HttpRequestImpl request() {
-        return request;
-    }
-
-    Http1Exchange(Exchange<T> exchange, HttpConnection connection)
-        throws IOException
-    {
-        super(exchange);
-        this.request = exchange.request();
-        this.client = exchange.client();
-        this.executor = exchange.executor();
-        this.operations = new LinkedList<>();
-        operations.add(headersSentCF);
-        operations.add(bodySentCF);
-        if (connection != null) {
-            this.connection = connection;
-        } else {
-            InetSocketAddress addr = request.getAddress();
-            this.connection = HttpConnection.getConnection(addr, client, request, HTTP_1_1);
-        }
-        this.requestAction = new Http1Request(request, this);
-        this.asyncReceiver = new Http1AsyncReceiver(executor, this);
-        asyncReceiver.subscribe(new InitialErrorReceiver());
-    }
-
-    /** An initial receiver that handles no data, but cancels the request if
-     * it receives an error. Will be replaced when reading response body. */
-    final class InitialErrorReceiver implements Http1AsyncReceiver.Http1AsyncDelegate {
-        volatile AbstractSubscription s;
-        @Override
-        public boolean tryAsyncReceive(ByteBuffer ref) {
-            return false;  // no data has been processed, leave it in the queue
-        }
-
-        @Override
-        public void onReadError(Throwable ex) {
-            cancelImpl(ex);
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription s) {
-            this.s = s;
-        }
-
-        public AbstractSubscription subscription() {
-            return s;
-        }
-    }
-
-    @Override
-    HttpConnection connection() {
-        return connection;
-    }
-
-    private void connectFlows(HttpConnection connection) {
-        FlowTube tube =  connection.getConnectionFlow();
-        debug.log(Level.DEBUG, "%s connecting flows", tube);
-
-        // Connect the flow to our Http1TubeSubscriber:
-        //   asyncReceiver.subscriber().
-        tube.connectFlows(writePublisher,
-                          asyncReceiver.subscriber());
-    }
-
-    @Override
-    CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
-        // create the response before sending the request headers, so that
-        // the response can set the appropriate receivers.
-        debug.log(Level.DEBUG, "Sending headers only");
-        if (response == null) {
-            response = new Http1Response<>(connection, this, asyncReceiver);
-        }
-
-        debug.log(Level.DEBUG, "response created in advance");
-        // If the first attempt to read something triggers EOF, or
-        // IOException("channel reset by peer"), we're going to retry.
-        // Instruct the asyncReceiver to throw ConnectionExpiredException
-        // to force a retry.
-        asyncReceiver.setRetryOnError(true);
-
-        CompletableFuture<Void> connectCF;
-        if (!connection.connected()) {
-            debug.log(Level.DEBUG, "initiating connect async");
-            connectCF = connection.connectAsync();
-            synchronized (lock) {
-                operations.add(connectCF);
-            }
-        } else {
-            connectCF = new MinimalFuture<>();
-            connectCF.complete(null);
-        }
-
-        return connectCF
-                .thenCompose(unused -> {
-                    CompletableFuture<Void> cf = new MinimalFuture<>();
-                    try {
-                        connectFlows(connection);
-
-                        debug.log(Level.DEBUG, "requestAction.headers");
-                        List<ByteBuffer> data = requestAction.headers();
-                        synchronized (lock) {
-                            state = State.HEADERS;
-                        }
-                        debug.log(Level.DEBUG, "setting outgoing with headers");
-                        assert outgoing.isEmpty() : "Unexpected outgoing:" + outgoing;
-                        appendToOutgoing(data);
-                        cf.complete(null);
-                        return cf;
-                    } catch (Throwable t) {
-                        debug.log(Level.DEBUG, "Failed to send headers: %s", t);
-                        connection.close();
-                        cf.completeExceptionally(t);
-                        return cf;
-                    } })
-                .thenCompose(unused -> headersSentCF);
-    }
-
-    @Override
-    CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
-        assert headersSentCF.isDone();
-        try {
-            bodySubscriber = requestAction.continueRequest();
-            if (bodySubscriber == null) {
-                bodySubscriber = Http1BodySubscriber.completeSubscriber();
-                appendToOutgoing(Http1BodySubscriber.COMPLETED);
-            } else {
-                bodySubscriber.request(1);  // start
-            }
-        } catch (Throwable t) {
-            connection.close();
-            bodySentCF.completeExceptionally(t);
-        }
-        return bodySentCF;
-    }
-
-    @Override
-    CompletableFuture<Response> getResponseAsync(Executor executor) {
-        CompletableFuture<Response> cf = response.readHeadersAsync(executor);
-        Throwable cause;
-        synchronized (lock) {
-            operations.add(cf);
-            cause = failed;
-            failed = null;
-        }
-
-        if (cause != null) {
-            Log.logTrace("Http1Exchange: request [{0}/timeout={1}ms]"
-                            + "\n\tCompleting exceptionally with {2}\n",
-                         request.uri(),
-                         request.timeout().isPresent() ?
-                            // calling duration.toMillis() can throw an exception.
-                            // this is just debugging, we don't care if it overflows.
-                            (request.timeout().get().getSeconds() * 1000
-                             + request.timeout().get().getNano() / 1000000) : -1,
-                         cause);
-            boolean acknowledged = cf.completeExceptionally(cause);
-            debug.log(Level.DEBUG,
-                      () -> acknowledged
-                            ? ("completed response with " + cause)
-                            : ("response already completed, ignoring " + cause));
-        }
-        return cf;
-    }
-
-    @Override
-    CompletableFuture<T> readBodyAsync(BodyHandler<T> handler,
-                                       boolean returnConnectionToPool,
-                                       Executor executor)
-    {
-        BodySubscriber<T> bs = handler.apply(response.responseCode(),
-                                             response.responseHeaders());
-        CompletableFuture<T> bodyCF = response.readBody(bs,
-                                                        returnConnectionToPool,
-                                                        executor);
-        return bodyCF;
-    }
-
-    @Override
-    CompletableFuture<Void> ignoreBody() {
-        return response.ignoreBody(executor);
-    }
-
-    ByteBuffer drainLeftOverBytes() {
-        synchronized (lock) {
-            asyncReceiver.stop();
-            return asyncReceiver.drain(Utils.EMPTY_BYTEBUFFER);
-        }
-    }
-
-    void released() {
-        Http1Response<T> resp = this.response;
-        if (resp != null) resp.completed();
-        asyncReceiver.clear();
-    }
-
-    void completed() {
-        Http1Response<T> resp = this.response;
-        if (resp != null) resp.completed();
-    }
-
-    /**
-     * Cancel checks to see if request and responseAsync finished already.
-     * If not it closes the connection and completes all pending operations
-     */
-    @Override
-    void cancel() {
-        cancelImpl(new IOException("Request cancelled"));
-    }
-
-    /**
-     * Cancel checks to see if request and responseAsync finished already.
-     * If not it closes the connection and completes all pending operations
-     */
-    @Override
-    void cancel(IOException cause) {
-        cancelImpl(cause);
-    }
-
-    private void cancelImpl(Throwable cause) {
-        LinkedList<CompletableFuture<?>> toComplete = null;
-        int count = 0;
-        synchronized (lock) {
-            if (failed == null)
-                failed = cause;
-            if (requestAction != null && requestAction.finished()
-                    && response != null && response.finished()) {
-                return;
-            }
-            connection.close();   // TODO: ensure non-blocking if holding the lock
-            writePublisher.writeScheduler.stop();
-            if (operations.isEmpty()) {
-                Log.logTrace("Http1Exchange: request [{0}/timeout={1}ms] no pending operation."
-                                + "\n\tCan''t cancel yet with {2}",
-                             request.uri(),
-                             request.timeout().isPresent() ?
-                                // calling duration.toMillis() can throw an exception.
-                                // this is just debugging, we don't care if it overflows.
-                                (request.timeout().get().getSeconds() * 1000
-                                 + request.timeout().get().getNano() / 1000000) : -1,
-                             cause);
-            } else {
-                for (CompletableFuture<?> cf : operations) {
-                    if (!cf.isDone()) {
-                        if (toComplete == null) toComplete = new LinkedList<>();
-                        toComplete.add(cf);
-                        count++;
-                    }
-                }
-                operations.clear();
-            }
-        }
-        Log.logError("Http1Exchange.cancel: count=" + count);
-        if (toComplete != null) {
-            // We might be in the selector thread in case of timeout, when
-            // the SelectorManager calls purgeTimeoutsAndReturnNextDeadline()
-            // There may or may not be other places that reach here
-            // from the SelectorManager thread, so just make sure we
-            // don't complete any CF from within the selector manager
-            // thread.
-            Executor exec = client.isSelectorThread()
-                            ? executor
-                            : this::runInline;
-            while (!toComplete.isEmpty()) {
-                CompletableFuture<?> cf = toComplete.poll();
-                exec.execute(() -> {
-                    if (cf.completeExceptionally(cause)) {
-                        debug.log(Level.DEBUG, "completed cf with %s",
-                                 (Object) cause);
-                    }
-                });
-            }
-        }
-    }
-
-    private void runInline(Runnable run) {
-        assert !client.isSelectorThread();
-        run.run();
-    }
-
-    /** Returns true if this exchange was canceled. */
-    boolean isCanceled() {
-        synchronized (lock) {
-            return failed != null;
-        }
-    }
-
-    /** Returns the cause for which this exchange was canceled, if available. */
-    Throwable getCancelCause() {
-        synchronized (lock) {
-            return failed;
-        }
-    }
-
-    /** Convenience for {@link #appendToOutgoing(DataPair)}, with just a Throwable. */
-    void appendToOutgoing(Throwable throwable) {
-        appendToOutgoing(new DataPair(null, throwable));
-    }
-
-    /** Convenience for {@link #appendToOutgoing(DataPair)}, with just data. */
-    void appendToOutgoing(List<ByteBuffer> item) {
-        appendToOutgoing(new DataPair(item, null));
-    }
-
-    private void appendToOutgoing(DataPair dp) {
-        debug.log(Level.DEBUG, "appending to outgoing " + dp);
-        outgoing.add(dp);
-        writePublisher.writeScheduler.runOrSchedule();
-    }
-
-    /** Tells whether, or not, there is any outgoing data that can be published,
-     * or if there is an error. */
-    private boolean hasOutgoing() {
-        return !outgoing.isEmpty();
-    }
-
-    // Invoked only by the publisher
-    // ALL tasks should execute off the Selector-Manager thread
-    /** Returns the next portion of the HTTP request, or the error. */
-    private DataPair getOutgoing() {
-        final Executor exec = client.theExecutor();
-        final DataPair dp = outgoing.pollFirst();
-
-        if (dp == null)  // publisher has not published anything yet
-            return null;
-
-        synchronized (lock) {
-            if (dp.throwable != null) {
-                state = State.ERROR;
-                exec.execute(() -> {
-                    connection.close();
-                    headersSentCF.completeExceptionally(dp.throwable);
-                    bodySentCF.completeExceptionally(dp.throwable);
-                });
-                return dp;
-            }
-
-            switch (state) {
-                case HEADERS:
-                    state = State.BODY;
-                    // completeAsync, since dependent tasks should run in another thread
-                    debug.log(Level.DEBUG, "initiating completion of headersSentCF");
-                    headersSentCF.completeAsync(() -> this, exec);
-                    break;
-                case BODY:
-                    if (dp.data == Http1BodySubscriber.COMPLETED) {
-                        state = State.COMPLETING;
-                        debug.log(Level.DEBUG, "initiating completion of bodySentCF");
-                        bodySentCF.completeAsync(() -> this, exec);
-                    } else {
-                        debug.log(Level.DEBUG, "requesting more body from the subscriber");
-                        exec.execute(() -> bodySubscriber.request(1));
-                    }
-                    break;
-                case INITIAL:
-                case ERROR:
-                case COMPLETING:
-                case COMPLETED:
-                default:
-                    assert false : "Unexpected state:" + state;
-            }
-
-            return dp;
-        }
-    }
-
-    /** A Publisher of HTTP/1.1 headers and request body. */
-    final class Http1Publisher implements FlowTube.TubePublisher {
-
-        final System.Logger  debug = Utils.getDebugLogger(this::dbgString);
-        volatile Flow.Subscriber<? super List<ByteBuffer>> subscriber;
-        volatile boolean cancelled;
-        final Http1WriteSubscription subscription = new Http1WriteSubscription();
-        final Demand demand = new Demand();
-        final SequentialScheduler writeScheduler =
-                SequentialScheduler.synchronizedScheduler(new WriteTask());
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
-            assert state == State.INITIAL;
-            Objects.requireNonNull(s);
-            assert subscriber == null;
-
-            subscriber = s;
-            debug.log(Level.DEBUG, "got subscriber: %s", s);
-            s.onSubscribe(subscription);
-        }
-
-        volatile String dbgTag;
-        String dbgString() {
-            String tag = dbgTag;
-            Object flow = connection.getConnectionFlow();
-            if (tag == null && flow != null) {
-                dbgTag = tag = "Http1Publisher(" + flow + ")";
-            } else if (tag == null) {
-                tag = "Http1Publisher(?)";
-            }
-            return tag;
-        }
-
-        final class WriteTask implements Runnable {
-            @Override
-            public void run() {
-                assert state != State.COMPLETED : "Unexpected state:" + state;
-                debug.log(Level.DEBUG, "WriteTask");
-                if (subscriber == null) {
-                    debug.log(Level.DEBUG, "no subscriber yet");
-                    return;
-                }
-                debug.log(Level.DEBUG, () -> "hasOutgoing = " + hasOutgoing());
-                while (hasOutgoing() && demand.tryDecrement()) {
-                    DataPair dp = getOutgoing();
-
-                    if (dp.throwable != null) {
-                        debug.log(Level.DEBUG, "onError");
-                        // Do not call the subscriber's onError, it is not required.
-                        writeScheduler.stop();
-                    } else {
-                        List<ByteBuffer> data = dp.data;
-                        if (data == Http1BodySubscriber.COMPLETED) {
-                            synchronized (lock) {
-                                assert state == State.COMPLETING : "Unexpected state:" + state;
-                                state = State.COMPLETED;
-                            }
-                            debug.log(Level.DEBUG,
-                                     "completed, stopping %s", writeScheduler);
-                            writeScheduler.stop();
-                            // Do nothing more. Just do not publish anything further.
-                            // The next Subscriber will eventually take over.
-
-                        } else {
-                            debug.log(Level.DEBUG, () ->
-                                    "onNext with " + Utils.remaining(data) + " bytes");
-                            subscriber.onNext(data);
-                        }
-                    }
-                }
-            }
-        }
-
-        final class Http1WriteSubscription implements Flow.Subscription {
-
-            @Override
-            public void request(long n) {
-                if (cancelled)
-                    return;  //no-op
-                demand.increase(n);
-                debug.log(Level.DEBUG,
-                        "subscription request(%d), demand=%s", n, demand);
-                writeScheduler.runOrSchedule(client.theExecutor());
-            }
-
-            @Override
-            public void cancel() {
-                debug.log(Level.DEBUG, "subscription cancelled");
-                if (cancelled)
-                    return;  //no-op
-                cancelled = true;
-                writeScheduler.stop();
-            }
-        }
-    }
-
-    String dbgString() {
-        return "Http1Exchange";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1HeaderParser.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-
-class Http1HeaderParser {
-
-    private static final char CR = '\r';
-    private static final char LF = '\n';
-    private static final char HT = '\t';
-    private static final char SP = ' ';
-
-    private StringBuilder sb = new StringBuilder();
-    private String statusLine;
-    private int responseCode;
-    private HttpHeaders headers;
-    private Map<String,List<String>> privateMap = new HashMap<>();
-
-    enum State { STATUS_LINE,
-                 STATUS_LINE_FOUND_CR,
-                 STATUS_LINE_END,
-                 STATUS_LINE_END_CR,
-                 HEADER,
-                 HEADER_FOUND_CR,
-                 HEADER_FOUND_LF,
-                 HEADER_FOUND_CR_LF,
-                 HEADER_FOUND_CR_LF_CR,
-                 FINISHED }
-
-    private State state = State.STATUS_LINE;
-
-    /** Returns the status-line. */
-    String statusLine() { return statusLine; }
-
-    /** Returns the response code. */
-    int responseCode() { return responseCode; }
-
-    /** Returns the headers, possibly empty. */
-    HttpHeaders headers() { assert state == State.FINISHED; return headers; }
-
-    /**
-     * Parses HTTP/1.X status-line and headers from the given bytes. Must be
-     * called successive times, with additional data, until returns true.
-     *
-     * All given ByteBuffers will be consumed, until ( possibly ) the last one
-     * ( when true is returned ), which may not be fully consumed.
-     *
-     * @param input the ( partial ) header data
-     * @return true iff the end of the headers block has been reached
-     */
-    boolean parse(ByteBuffer input) throws ProtocolException {
-        requireNonNull(input, "null input");
-
-        while (input.hasRemaining() && state != State.FINISHED) {
-            switch (state) {
-                case STATUS_LINE:
-                    readResumeStatusLine(input);
-                    break;
-                case STATUS_LINE_FOUND_CR:
-                    readStatusLineFeed(input);
-                    break;
-                case STATUS_LINE_END:
-                    maybeStartHeaders(input);
-                    break;
-                case STATUS_LINE_END_CR:
-                    maybeEndHeaders(input);
-                    break;
-                case HEADER:
-                    readResumeHeader(input);
-                    break;
-                // fallthrough
-                case HEADER_FOUND_CR:
-                case HEADER_FOUND_LF:
-                    resumeOrLF(input);
-                    break;
-                case HEADER_FOUND_CR_LF:
-                    resumeOrSecondCR(input);
-                    break;
-                case HEADER_FOUND_CR_LF_CR:
-                    resumeOrEndHeaders(input);
-                    break;
-                default:
-                    throw new InternalError(
-                            "Unexpected state: " + String.valueOf(state));
-            }
-        }
-
-        return state == State.FINISHED;
-    }
-
-    private void readResumeStatusLine(ByteBuffer input) {
-        char c = 0;
-        while (input.hasRemaining() && (c =(char)input.get()) != CR) {
-            sb.append(c);
-        }
-
-        if (c == CR) {
-            state = State.STATUS_LINE_FOUND_CR;
-        }
-    }
-
-    private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
-        char c = (char)input.get();
-        if (c != LF) {
-            throw protocolException("Bad trailing char, \"%s\", when parsing status-line, \"%s\"",
-                                    c, sb.toString());
-        }
-
-        statusLine = sb.toString();
-        sb = new StringBuilder();
-        if (!statusLine.startsWith("HTTP/1.")) {
-            throw protocolException("Invalid status line: \"%s\"", statusLine);
-        }
-        if (statusLine.length() < 12) {
-            throw protocolException("Invalid status line: \"%s\"", statusLine);
-        }
-        responseCode = Integer.parseInt(statusLine.substring(9, 12));
-
-        state = State.STATUS_LINE_END;
-    }
-
-    private void maybeStartHeaders(ByteBuffer input) {
-        assert state == State.STATUS_LINE_END;
-        assert sb.length() == 0;
-        char c = (char)input.get();
-        if (c == CR) {
-            state = State.STATUS_LINE_END_CR;
-        } else {
-            sb.append(c);
-            state = State.HEADER;
-        }
-    }
-
-    private void maybeEndHeaders(ByteBuffer input) throws ProtocolException {
-        assert state == State.STATUS_LINE_END_CR;
-        assert sb.length() == 0;
-        char c = (char)input.get();
-        if (c == LF) {
-            headers = ImmutableHeaders.of(privateMap);
-            privateMap = null;
-            state = State.FINISHED;  // no headers
-        } else {
-            throw protocolException("Unexpected \"%s\", after status-line CR", c);
-        }
-    }
-
-    private void readResumeHeader(ByteBuffer input) {
-        assert state == State.HEADER;
-        assert input.hasRemaining();
-        while (input.hasRemaining()) {
-            char c = (char)input.get();
-            if (c == CR) {
-                state = State.HEADER_FOUND_CR;
-                break;
-            } else if (c == LF) {
-                state = State.HEADER_FOUND_LF;
-                break;
-            }
-
-            if (c == HT)
-                c = SP;
-            sb.append(c);
-        }
-    }
-
-    private void addHeaderFromString(String headerString) {
-        assert sb.length() == 0;
-        int idx = headerString.indexOf(':');
-        if (idx == -1)
-            return;
-        String name = headerString.substring(0, idx).trim();
-        if (name.isEmpty())
-            return;
-        String value = headerString.substring(idx + 1, headerString.length()).trim();
-
-        privateMap.computeIfAbsent(name.toLowerCase(Locale.US),
-                                   k -> new ArrayList<>()).add(value);
-    }
-
-    private void resumeOrLF(ByteBuffer input) {
-        assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF;
-        char c = (char)input.get();
-        if (c == LF && state == State.HEADER_FOUND_CR) {
-            // header value will be flushed by
-            // resumeOrSecondCR if next line does not
-            // begin by SP or HT
-            state = State.HEADER_FOUND_CR_LF;
-        } else if (c == SP || c == HT) {
-            sb.append(SP); // parity with MessageHeaders
-            state = State.HEADER;
-        } else {
-            sb = new StringBuilder();
-            sb.append(c);
-            state = State.HEADER;
-        }
-    }
-
-    private void resumeOrSecondCR(ByteBuffer input) {
-        assert state == State.HEADER_FOUND_CR_LF;
-        char c = (char)input.get();
-        if (c == CR) {
-            if (sb.length() > 0) {
-                // no continuation line - flush
-                // previous header value.
-                String headerString = sb.toString();
-                sb = new StringBuilder();
-                addHeaderFromString(headerString);
-            }
-            state = State.HEADER_FOUND_CR_LF_CR;
-        } else if (c == SP || c == HT) {
-            assert sb.length() != 0;
-            sb.append(SP); // continuation line
-            state = State.HEADER;
-        } else {
-            if (sb.length() > 0) {
-                // no continuation line - flush
-                // previous header value.
-                String headerString = sb.toString();
-                sb = new StringBuilder();
-                addHeaderFromString(headerString);
-            }
-            sb.append(c);
-            state = State.HEADER;
-        }
-    }
-
-    private void resumeOrEndHeaders(ByteBuffer input) throws ProtocolException {
-        assert state == State.HEADER_FOUND_CR_LF_CR;
-        char c = (char)input.get();
-        if (c == LF) {
-            state = State.FINISHED;
-            headers = ImmutableHeaders.of(privateMap);
-            privateMap = null;
-        } else {
-            throw protocolException("Unexpected \"%s\", after CR LF CR", c);
-        }
-    }
-
-    private ProtocolException protocolException(String format, Object... args) {
-        return new ProtocolException(format(format, args));
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Request.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,388 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.net.InetSocketAddress;
-import java.util.Objects;
-import java.util.concurrent.Flow;
-import java.util.function.BiPredicate;
-import jdk.incubator.http.Http1Exchange.Http1BodySubscriber;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-/**
- *  An HTTP/1.1 request.
- */
-class Http1Request {
-    private final HttpRequestImpl request;
-    private final Http1Exchange<?> http1Exchange;
-    private final HttpConnection connection;
-    private final HttpRequest.BodyPublisher requestPublisher;
-    private final HttpHeaders userHeaders;
-    private final HttpHeadersImpl systemHeaders;
-    private volatile boolean streaming;
-    private volatile long contentLength;
-
-    Http1Request(HttpRequestImpl request,
-                 Http1Exchange<?> http1Exchange)
-        throws IOException
-    {
-        this.request = request;
-        this.http1Exchange = http1Exchange;
-        this.connection = http1Exchange.connection();
-        this.requestPublisher = request.requestPublisher;  // may be null
-        this.userHeaders = request.getUserHeaders();
-        this.systemHeaders = request.getSystemHeaders();
-    }
-
-    private void logHeaders(String completeHeaders) {
-        if (Log.headers()) {
-            //StringBuilder sb = new StringBuilder(256);
-            //sb.append("REQUEST HEADERS:\n");
-            //Log.dumpHeaders(sb, "    ", systemHeaders);
-            //Log.dumpHeaders(sb, "    ", userHeaders);
-            //Log.logHeaders(sb.toString());
-
-            String s = completeHeaders.replaceAll("\r\n", "\n");
-            Log.logHeaders("REQUEST HEADERS:\n" + s);
-        }
-    }
-
-
-    private void collectHeaders0(StringBuilder sb) {
-        BiPredicate<String,List<String>> filter =
-                connection.headerFilter(request);
-
-        // If we're sending this request through a tunnel,
-        // then don't send any preemptive proxy-* headers that
-        // the authentication filter may have saved in its
-        // cache.
-        collectHeaders1(sb, systemHeaders, filter);
-
-        // If we're sending this request through a tunnel,
-        // don't send any user-supplied proxy-* headers
-        // to the target server.
-        collectHeaders1(sb, userHeaders, filter);
-        sb.append("\r\n");
-    }
-
-    private void collectHeaders1(StringBuilder sb, HttpHeaders headers,
-                                 BiPredicate<String, List<String>> filter) {
-        for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
-            String key = entry.getKey();
-            List<String> values = entry.getValue();
-            if (!filter.test(key, values)) continue;
-            for (String value : values) {
-                sb.append(key).append(": ").append(value).append("\r\n");
-            }
-        }
-    }
-
-    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 hostString() {
-        URI uri = request.uri();
-        int port = uri.getPort();
-        String host = uri.getHost();
-
-        boolean defaultPort;
-        if (port == -1) {
-            defaultPort = true;
-        } else if (request.secure()) {
-            defaultPort = port == 443;
-        } else {
-            defaultPort = port == 80;
-        }
-
-        if (defaultPort) {
-            return host;
-        } else {
-            return host + ":" + Integer.toString(port);
-        }
-    }
-
-    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);
-            }
-        }
-        if (request.method().equals("CONNECT")) {
-            // use authority for connect itself
-            return authorityString(request.authority());
-        }
-
-        return uri == null? authorityString(request.authority()) : uri.toString();
-    }
-
-    private boolean finished;
-
-    synchronized boolean finished() {
-        return  finished;
-    }
-
-    synchronized void setFinished() {
-        finished = true;
-    }
-
-    List<ByteBuffer> headers() {
-        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");
-
-        URI uri = request.uri();
-        if (uri != null) {
-            systemHeaders.setHeader("Host", hostString());
-        }
-        if (requestPublisher == null) {
-            // Not a user request, or maybe a method, e.g. GET, with no body.
-            contentLength = 0;
-        } else {
-            contentLength = requestPublisher.contentLength();
-        }
-
-        if (contentLength == 0) {
-            systemHeaders.setHeader("Content-Length", "0");
-        } else if (contentLength > 0) {
-            systemHeaders.setHeader("Content-Length", Long.toString(contentLength));
-            streaming = false;
-        } else {
-            streaming = true;
-            systemHeaders.setHeader("Transfer-encoding", "chunked");
-        }
-        collectHeaders0(sb);
-        String hs = sb.toString();
-        logHeaders(hs);
-        ByteBuffer b = ByteBuffer.wrap(hs.getBytes(US_ASCII));
-        return List.of(b);
-    }
-
-    Http1BodySubscriber continueRequest()  {
-        Http1BodySubscriber subscriber;
-        if (streaming) {
-            subscriber = new StreamSubscriber();
-            requestPublisher.subscribe(subscriber);
-        } else {
-            if (contentLength == 0)
-                return null;
-
-            subscriber = new FixedContentSubscriber();
-            requestPublisher.subscribe(subscriber);
-        }
-        return subscriber;
-    }
-
-    class StreamSubscriber extends Http1BodySubscriber {
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (this.subscription != null) {
-                Throwable t = new IllegalStateException("already subscribed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                this.subscription = subscription;
-            }
-        }
-
-        @Override
-        public void onNext(ByteBuffer item) {
-            Objects.requireNonNull(item);
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                int chunklen = item.remaining();
-                ArrayList<ByteBuffer> l = new ArrayList<>(3);
-                l.add(getHeader(chunklen));
-                l.add(item);
-                l.add(ByteBuffer.wrap(CRLF));
-                http1Exchange.appendToOutgoing(l);
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            if (complete)
-                return;
-
-            subscription.cancel();
-            http1Exchange.appendToOutgoing(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                ArrayList<ByteBuffer> l = new ArrayList<>(2);
-                l.add(ByteBuffer.wrap(EMPTY_CHUNK_BYTES));
-                l.add(ByteBuffer.wrap(CRLF));
-                complete = true;
-                //setFinished();
-                http1Exchange.appendToOutgoing(l);
-                http1Exchange.appendToOutgoing(COMPLETED);
-                setFinished();  // TODO: before or after,? does it matter?
-
-            }
-        }
-    }
-
-    class FixedContentSubscriber extends Http1BodySubscriber {
-
-        private volatile long contentWritten;
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (this.subscription != null) {
-                Throwable t = new IllegalStateException("already subscribed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                this.subscription = subscription;
-            }
-        }
-
-        @Override
-        public void onNext(ByteBuffer item) {
-            debug.log(Level.DEBUG, "onNext");
-            Objects.requireNonNull(item);
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                long writing = item.remaining();
-                long written = (contentWritten += writing);
-
-                if (written > contentLength) {
-                    subscription.cancel();
-                    String msg = connection.getConnectionFlow()
-                                  + " [" + Thread.currentThread().getName() +"] "
-                                  + "Too many bytes in request body. Expected: "
-                                  + contentLength + ", got: " + written;
-                    http1Exchange.appendToOutgoing(new IOException(msg));
-                } else {
-                    http1Exchange.appendToOutgoing(List.of(item));
-                }
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            debug.log(Level.DEBUG, "onError");
-            if (complete)  // TODO: error?
-                return;
-
-            subscription.cancel();
-            http1Exchange.appendToOutgoing(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            debug.log(Level.DEBUG, "onComplete");
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                complete = true;
-                long written = contentWritten;
-                if (contentLength > written) {
-                    subscription.cancel();
-                    Throwable t = new IOException(connection.getConnectionFlow()
-                                         + " [" + Thread.currentThread().getName() +"] "
-                                         + "Too few bytes returned by the publisher ("
-                                                  + written + "/"
-                                                  + contentLength + ")");
-                    http1Exchange.appendToOutgoing(t);
-                } else {
-                    http1Exchange.appendToOutgoing(COMPLETED);
-                }
-            }
-        }
-    }
-
-    private static final byte[] CRLF = {'\r', '\n'};
-    private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};
-
-    /** 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);
-    }
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::toString, DEBUG);
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Response.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,516 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import jdk.incubator.http.ResponseContent.BodyParser;
-import jdk.incubator.http.internal.common.Log;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Handles a HTTP/1.1 response (headers + body).
- * There can be more than one of these per Http exchange.
- */
-class Http1Response<T> {
-
-    private volatile ResponseContent content;
-    private final HttpRequestImpl request;
-    private Response response;
-    private final HttpConnection connection;
-    private HttpHeaders headers;
-    private int responseCode;
-    private final Http1Exchange<T> exchange;
-    private boolean return2Cache; // return connection to cache when finished
-    private final HeadersReader headersReader; // used to read the headers
-    private final BodyReader bodyReader; // used to read the body
-    private final Http1AsyncReceiver asyncReceiver;
-    private volatile EOFException eof;
-    // max number of bytes of (fixed length) body to ignore on redirect
-    private final static int MAX_IGNORE = 1024;
-
-    // Revisit: can we get rid of this?
-    static enum State {INITIAL, READING_HEADERS, READING_BODY, DONE}
-    private volatile State readProgress = State.INITIAL;
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this.getClass()::getSimpleName, DEBUG);
-
-
-    Http1Response(HttpConnection conn,
-                  Http1Exchange<T> exchange,
-                  Http1AsyncReceiver asyncReceiver) {
-        this.readProgress = State.INITIAL;
-        this.request = exchange.request();
-        this.exchange = exchange;
-        this.connection = conn;
-        this.asyncReceiver = asyncReceiver;
-        headersReader = new HeadersReader(this::advance);
-        bodyReader = new BodyReader(this::advance);
-    }
-
-   public CompletableFuture<Response> readHeadersAsync(Executor executor) {
-        debug.log(Level.DEBUG, () -> "Reading Headers: (remaining: "
-                + asyncReceiver.remaining() +") "  + readProgress);
-        // with expect continue we will resume reading headers + body.
-        asyncReceiver.unsubscribe(bodyReader);
-        bodyReader.reset();
-        Http1HeaderParser hd = new Http1HeaderParser();
-        readProgress = State.READING_HEADERS;
-        headersReader.start(hd);
-        asyncReceiver.subscribe(headersReader);
-        CompletableFuture<State> cf = headersReader.completion();
-        assert cf != null : "parsing not started";
-
-        Function<State, Response> lambda = (State completed) -> {
-                assert completed == State.READING_HEADERS;
-                debug.log(Level.DEBUG, () ->
-                            "Reading Headers: creating Response object;"
-                            + " state is now " + readProgress);
-                asyncReceiver.unsubscribe(headersReader);
-                responseCode = hd.responseCode();
-                headers = hd.headers();
-
-                response = new Response(request,
-                                        exchange.getExchange(),
-                                        headers,
-                                        responseCode,
-                                        HTTP_1_1);
-                return response;
-            };
-
-        if (executor != null) {
-            return cf.thenApplyAsync(lambda, executor);
-        } else {
-            return cf.thenApply(lambda);
-        }
-    }
-
-    private boolean finished;
-
-    synchronized void completed() {
-        finished = true;
-    }
-
-    synchronized boolean finished() {
-        return finished;
-    }
-
-    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;
-    }
-
-    /**
-     * Read up to MAX_IGNORE bytes discarding
-     */
-    public CompletableFuture<Void> ignoreBody(Executor executor) {
-        int clen = (int)headers.firstValueAsLong("Content-Length").orElse(-1);
-        if (clen == -1 || clen > MAX_IGNORE) {
-            connection.close();
-            return MinimalFuture.completedFuture(null); // not treating as error
-        } else {
-            return readBody(HttpResponse.BodySubscriber.discard((Void)null), true, executor);
-        }
-    }
-
-    public <U> CompletableFuture<U> readBody(HttpResponse.BodySubscriber<U> p,
-                                         boolean return2Cache,
-                                         Executor executor) {
-        this.return2Cache = return2Cache;
-        final HttpResponse.BodySubscriber<U> pusher = p;
-        final CompletionStage<U> bodyCF = p.getBody();
-        final CompletableFuture<U> cf = MinimalFuture.of(bodyCF);
-
-        int clen0 = (int)headers.firstValueAsLong("Content-Length").orElse(-1);
-
-        final int clen = fixupContentLen(clen0);
-
-        // expect-continue reads headers and body twice.
-        // if we reach here, we must reset the headersReader state.
-        asyncReceiver.unsubscribe(headersReader);
-        headersReader.reset();
-
-        executor.execute(() -> {
-            try {
-                HttpClientImpl client = connection.client();
-                content = new ResponseContent(
-                        connection, clen, headers, pusher,
-                        this::onFinished
-                );
-                if (cf.isCompletedExceptionally()) {
-                    // if an error occurs during subscription
-                    connection.close();
-                    return;
-                }
-                // increment the reference count on the HttpClientImpl
-                // to prevent the SelectorManager thread from exiting until
-                // the body is fully read.
-                client.reference();
-                bodyReader.start(content.getBodyParser(
-                    (t) -> {
-                        try {
-                            if (t != null) {
-                                pusher.onError(t);
-                                connection.close();
-                                if (!cf.isDone())
-                                    cf.completeExceptionally(t);
-                            }
-                        } finally {
-                            // decrement the reference count on the HttpClientImpl
-                            // to allow the SelectorManager thread to exit if no
-                            // other operation is pending and the facade is no
-                            // longer referenced.
-                            client.unreference();
-                            bodyReader.onComplete(t);
-                        }
-                    }));
-                CompletableFuture<State> bodyReaderCF = bodyReader.completion();
-                asyncReceiver.subscribe(bodyReader);
-                assert bodyReaderCF != null : "parsing not started";
-                // Make sure to keep a reference to asyncReceiver from
-                // within this
-                CompletableFuture<?> trailingOp = bodyReaderCF.whenComplete((s,t) ->  {
-                    t = Utils.getCompletionCause(t);
-                    try {
-                        if (t != null) {
-                            debug.log(Level.DEBUG, () ->
-                                    "Finished reading body: " + s);
-                            assert s == State.READING_BODY;
-                        }
-                        if (t != null && !cf.isDone()) {
-                            pusher.onError(t);
-                            cf.completeExceptionally(t);
-                        }
-                    } catch (Throwable x) {
-                        // not supposed to happen
-                        asyncReceiver.onReadError(x);
-                    }
-                });
-                connection.addTrailingOperation(trailingOp);
-            } catch (Throwable t) {
-               debug.log(Level.DEBUG, () -> "Failed reading body: " + t);
-                try {
-                    if (!cf.isDone()) {
-                        pusher.onError(t);
-                        cf.completeExceptionally(t);
-                    }
-                } finally {
-                    asyncReceiver.onReadError(t);
-                }
-            }
-        });
-        return cf;
-    }
-
-
-    private void onFinished() {
-        asyncReceiver.clear();
-        if (return2Cache) {
-            Log.logTrace("Attempting to return connection to the pool: {0}", connection);
-            // TODO: need to do something here?
-            // connection.setAsyncCallbacks(null, null, null);
-
-            // don't return the connection to the cache if EOF happened.
-            debug.log(Level.DEBUG, () -> connection.getConnectionFlow()
-                                   + ": return to HTTP/1.1 pool");
-            connection.closeOrReturnToCache(eof == null ? headers : null);
-        }
-    }
-
-    HttpHeaders responseHeaders() {
-        return headers;
-    }
-
-    int responseCode() {
-        return responseCode;
-    }
-
-// ================ Support for plugging into Http1Receiver   =================
-// ============================================================================
-
-    // Callback: Error receiver: Consumer of Throwable.
-    void onReadError(Throwable t) {
-        Log.logError(t);
-        Receiver<?> receiver = receiver(readProgress);
-        if (t instanceof EOFException) {
-            debug.log(Level.DEBUG, "onReadError: received EOF");
-            eof = (EOFException) t;
-        }
-        CompletableFuture<?> cf = receiver == null ? null : receiver.completion();
-        debug.log(Level.DEBUG, () -> "onReadError: cf is "
-                + (cf == null  ? "null"
-                : (cf.isDone() ? "already completed"
-                               : "not yet completed")));
-        if (cf != null && !cf.isDone()) cf.completeExceptionally(t);
-        else { debug.log(Level.DEBUG, "onReadError", t); }
-        debug.log(Level.DEBUG, () -> "closing connection: cause is " + t);
-        connection.close();
-    }
-
-    // ========================================================================
-
-    private State advance(State previous) {
-        assert readProgress == previous;
-        switch(previous) {
-            case READING_HEADERS:
-                asyncReceiver.unsubscribe(headersReader);
-                return readProgress = State.READING_BODY;
-            case READING_BODY:
-                asyncReceiver.unsubscribe(bodyReader);
-                return readProgress = State.DONE;
-            default:
-                throw new InternalError("can't advance from " + previous);
-        }
-    }
-
-    Receiver<?> receiver(State state) {
-        switch(state) {
-            case READING_HEADERS: return headersReader;
-            case READING_BODY: return bodyReader;
-            default: return null;
-        }
-
-    }
-
-    static abstract class Receiver<T>
-            implements Http1AsyncReceiver.Http1AsyncDelegate {
-        abstract void start(T parser);
-        abstract CompletableFuture<State> completion();
-        // accepts a buffer from upstream.
-        // this should be implemented as a simple call to
-        // accept(ref, parser, cf)
-        public abstract boolean tryAsyncReceive(ByteBuffer buffer);
-        public abstract void onReadError(Throwable t);
-        // handle a byte buffer received from upstream.
-        // this method should set the value of Http1Response.buffer
-        // to ref.get() before beginning parsing.
-        abstract void handle(ByteBuffer buf, T parser,
-                             CompletableFuture<State> cf);
-        // resets this objects state so that it can be reused later on
-        // typically puts the reference to parser and completion to null
-        abstract void reset();
-
-        // accepts a byte buffer received from upstream
-        // returns true if the buffer is fully parsed and more data can
-        // be accepted, false otherwise.
-        final boolean accept(ByteBuffer buf, T parser,
-                CompletableFuture<State> cf) {
-            if (cf == null || parser == null || cf.isDone()) return false;
-            handle(buf, parser, cf);
-            return !cf.isDone();
-        }
-        public abstract void onSubscribe(AbstractSubscription s);
-        public abstract AbstractSubscription subscription();
-
-    }
-
-    // Invoked with each new ByteBuffer when reading headers...
-    final class HeadersReader extends Receiver<Http1HeaderParser> {
-        final Consumer<State> onComplete;
-        volatile Http1HeaderParser parser;
-        volatile CompletableFuture<State> cf;
-        volatile long count; // bytes parsed (for debug)
-        volatile AbstractSubscription subscription;
-
-        HeadersReader(Consumer<State> onComplete) {
-            this.onComplete = onComplete;
-        }
-
-        @Override
-        public AbstractSubscription subscription() {
-            return subscription;
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription s) {
-            this.subscription = s;
-            s.request(1);
-        }
-
-        @Override
-        void reset() {
-            cf = null;
-            parser = null;
-            count = 0;
-            subscription = null;
-        }
-
-        // Revisit: do we need to support restarting?
-        @Override
-        final void start(Http1HeaderParser hp) {
-            count = 0;
-            cf = new MinimalFuture<>();
-            parser = hp;
-        }
-
-        @Override
-        CompletableFuture<State> completion() {
-            return cf;
-        }
-
-        @Override
-        public final boolean tryAsyncReceive(ByteBuffer ref) {
-            boolean hasDemand = subscription.demand().tryDecrement();
-            assert hasDemand;
-            boolean needsMore = accept(ref, parser, cf);
-            if (needsMore) subscription.request(1);
-            return needsMore;
-        }
-
-        @Override
-        public final void onReadError(Throwable t) {
-            Http1Response.this.onReadError(t);
-        }
-
-        @Override
-        final void handle(ByteBuffer b,
-                          Http1HeaderParser parser,
-                          CompletableFuture<State> cf) {
-            assert cf != null : "parsing not started";
-            assert parser != null : "no parser";
-            try {
-                count += b.remaining();
-                debug.log(Level.DEBUG, () -> "Sending " + b.remaining()
-                        + "/" + b.capacity() + " bytes to header parser");
-                if (parser.parse(b)) {
-                    count -= b.remaining();
-                    debug.log(Level.DEBUG, () ->
-                            "Parsing headers completed. bytes=" + count);
-                    onComplete.accept(State.READING_HEADERS);
-                    cf.complete(State.READING_HEADERS);
-                }
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG,
-                        () -> "Header parser failed to handle buffer: " + t);
-                cf.completeExceptionally(t);
-            }
-        }
-    }
-
-    // Invoked with each new ByteBuffer when reading bodies...
-    final class BodyReader extends Receiver<BodyParser> {
-        final Consumer<State> onComplete;
-        volatile BodyParser parser;
-        volatile CompletableFuture<State> cf;
-        volatile AbstractSubscription subscription;
-        BodyReader(Consumer<State> onComplete) {
-            this.onComplete = onComplete;
-        }
-
-        @Override
-        void reset() {
-            parser = null;
-            cf = null;
-            subscription = null;
-        }
-
-        // Revisit: do we need to support restarting?
-        @Override
-        final void start(BodyParser parser) {
-            cf = new MinimalFuture<>();
-            this.parser = parser;
-        }
-
-        @Override
-        CompletableFuture<State> completion() {
-            return cf;
-        }
-
-        @Override
-        public final boolean tryAsyncReceive(ByteBuffer b) {
-            return accept(b, parser, cf);
-        }
-
-        @Override
-        public final void onReadError(Throwable t) {
-            Http1Response.this.onReadError(t);
-        }
-
-        @Override
-        public AbstractSubscription subscription() {
-            return subscription;
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription s) {
-            this.subscription = s;
-            parser.onSubscribe(s);
-        }
-
-        @Override
-        final void handle(ByteBuffer b,
-                          BodyParser parser,
-                          CompletableFuture<State> cf) {
-            assert cf != null : "parsing not started";
-            assert parser != null : "no parser";
-            try {
-                debug.log(Level.DEBUG, () -> "Sending " + b.remaining()
-                        + "/" + b.capacity() + " bytes to body parser");
-                parser.accept(b);
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG,
-                        () -> "Body parser failed to handle buffer: " + t);
-                if (!cf.isDone()) {
-                    cf.completeExceptionally(t);
-                }
-            }
-        }
-
-        final void onComplete(Throwable closedExceptionally) {
-            if (cf.isDone()) return;
-            if (closedExceptionally != null) {
-                cf.completeExceptionally(closedExceptionally);
-            } else {
-                onComplete.accept(State.READING_BODY);
-                cf.complete(State.READING_BODY);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return super.toString() + "/parser=" + String.valueOf(parser);
-        }
-
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CompletableFuture;
-
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.frame.SettingsFrame;
-import static jdk.incubator.http.internal.frame.SettingsFrame.INITIAL_WINDOW_SIZE;
-import static jdk.incubator.http.internal.frame.SettingsFrame.ENABLE_PUSH;
-import static jdk.incubator.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE;
-import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_CONCURRENT_STREAMS;
-import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_FRAME_SIZE;
-
-/**
- *  Http2 specific aspects of HttpClientImpl
- */
-class Http2ClientImpl {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final static System.Logger debug =
-            Utils.getDebugLogger("Http2ClientImpl"::toString, DEBUG);
-
-    private final HttpClientImpl client;
-
-    Http2ClientImpl(HttpClientImpl client) {
-        this.client = client;
-    }
-
-    /* Map key is "scheme:host:port" */
-    private final Map<String,Http2Connection> connections = new ConcurrentHashMap<>();
-
-    private final Set<String> failures = Collections.synchronizedSet(new HashSet<>());
-
-    /**
-     * When HTTP/2 requested only. The following describes the aggregate behavior including the
-     * calling code. In all cases, the HTTP2 connection cache
-     * is checked first for a suitable connection and that is returned if available.
-     * If not, a new connection is opened, except in https case when a previous negotiate failed.
-     * In that case, we want to continue using http/1.1. When a connection is to be opened and
-     * if multiple requests are sent in parallel then each will open a new connection.
-     *
-     * If negotiation/upgrade succeeds then
-     * one connection will be put in the cache and the others will be closed
-     * after the initial request completes (not strictly necessary for h2, only for h2c)
-     *
-     * If negotiate/upgrade fails, then any opened connections remain open (as http/1.1)
-     * and will be used and cached in the http/1 cache. Note, this method handles the
-     * https failure case only (by completing the CF with an ALPN exception, handled externally)
-     * The h2c upgrade is handled externally also.
-     *
-     * Specific CF behavior of this method.
-     * 1. completes with ALPN exception: h2 negotiate failed for first time. failure recorded.
-     * 2. completes with other exception: failure not recorded. Caller must handle
-     * 3. completes normally with null: no connection in cache for h2c or h2 failed previously
-     * 4. completes normally with connection: h2 or h2c connection in cache. Use it.
-     */
-    CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req) {
-        URI uri = req.uri();
-        InetSocketAddress proxy = req.proxy();
-        String key = Http2Connection.keyFor(uri, proxy);
-
-        synchronized (this) {
-            Http2Connection connection = connections.get(key);
-            if (connection != null) { // fast path if connection already exists
-                return MinimalFuture.completedFuture(connection);
-            }
-
-            if (!req.secure() || failures.contains(key)) {
-                // secure: negotiate failed before. Use http/1.1
-                // !secure: no connection available in cache. Attempt upgrade
-                return MinimalFuture.completedFuture(null);
-            }
-        }
-        return Http2Connection
-                .createAsync(req, this)
-                .whenComplete((conn, t) -> {
-                    synchronized (Http2ClientImpl.this) {
-                        if (conn != null) {
-                            offerConnection(conn);
-                        } else {
-                            Throwable cause = Utils.getCompletionCause(t);
-                            if (cause instanceof Http2Connection.ALPNException)
-                                failures.add(key);
-                        }
-                    }
-                });
-    }
-
-    /*
-     * Cache the given connection, if no connection to the same
-     * destination exists. If one exists, then we let the initial stream
-     * complete but allow it to close itself upon completion.
-     * This situation should not arise with https because the request
-     * has not been sent as part of the initial alpn negotiation
-     */
-    boolean offerConnection(Http2Connection c) {
-        String key = c.key();
-        Http2Connection c1 = connections.putIfAbsent(key, c);
-        if (c1 != null) {
-            c.setSingleStream(true);
-            return false;
-        }
-        return true;
-    }
-
-    void deleteConnection(Http2Connection c) {
-        connections.remove(c.key());
-    }
-
-    void stop() {
-        debug.log(Level.DEBUG, "stopping");
-        connections.values().forEach(this::close);
-        connections.clear();
-    }
-
-    private void close(Http2Connection h2c) {
-        try { h2c.close(); } catch (Throwable t) {}
-    }
-
-    HttpClientImpl client() {
-        return client;
-    }
-
-    /** Returns the client settings as a base64 (url) encoded string */
-    String getSettingsString() {
-        SettingsFrame sf = getClientSettings();
-        byte[] settings = sf.toByteArray(); // without the header
-        Base64.Encoder encoder = Base64.getUrlEncoder()
-                                       .withoutPadding();
-        return encoder.encodeToString(settings);
-    }
-
-    private static final int K = 1024;
-
-    private static int getParameter(String property, int min, int max, int defaultValue) {
-        int value =  Utils.getIntegerNetProperty(property, defaultValue);
-        // use default value if misconfigured
-        if (value < min || value > max) {
-            Log.logError("Property value for {0}={1} not in [{2}..{3}]: " +
-                    "using default={4}", property, value, min, max, defaultValue);
-            value = defaultValue;
-        }
-        return value;
-    }
-
-    // used for the connection window, to have a connection window size
-    // bigger than the initial stream window size.
-    int getConnectionWindowSize(SettingsFrame clientSettings) {
-        // Maximum size is 2^31-1. Don't allow window size to be less
-        // than the stream window size. HTTP/2 specify a default of 64 * K -1,
-        // but we use 2^26 by default for better performance.
-        int streamWindow = clientSettings.getParameter(INITIAL_WINDOW_SIZE);
-
-        // The default is the max between the stream window size
-        // and the connection window size.
-        int defaultValue = Math.min(Integer.MAX_VALUE,
-                Math.max(streamWindow, K*K*32));
-
-        return getParameter(
-                "jdk.httpclient.connectionWindowSize",
-                streamWindow, Integer.MAX_VALUE, defaultValue);
-    }
-
-    SettingsFrame getClientSettings() {
-        SettingsFrame frame = new SettingsFrame();
-        // default defined for HTTP/2 is 4 K, we use 16 K.
-        frame.setParameter(HEADER_TABLE_SIZE, getParameter(
-                "jdk.httpclient.hpack.maxheadertablesize",
-                0, Integer.MAX_VALUE, 16 * K));
-        // O: does not accept push streams. 1: accepts push streams.
-        frame.setParameter(ENABLE_PUSH, getParameter(
-                "jdk.httpclient.enablepush",
-                0, 1, 1));
-        // HTTP/2 recommends to set the number of concurrent streams
-        // no lower than 100. We use 100. 0 means no stream would be
-        // accepted. That would render the client to be non functional,
-        // so we won't let 0 be configured for our Http2ClientImpl.
-        frame.setParameter(MAX_CONCURRENT_STREAMS, getParameter(
-                "jdk.httpclient.maxstreams",
-                1, Integer.MAX_VALUE, 100));
-        // Maximum size is 2^31-1. Don't allow window size to be less
-        // than the minimum frame size as this is likely to be a
-        // configuration error. HTTP/2 specify a default of 64 * K -1,
-        // but we use 16 M  for better performance.
-        frame.setParameter(INITIAL_WINDOW_SIZE, getParameter(
-                "jdk.httpclient.windowsize",
-                16 * K, Integer.MAX_VALUE, 16*K*K));
-        // HTTP/2 specify a minimum size of 16 K, a maximum size of 2^24-1,
-        // and a default of 16 K. We use 16 K as default.
-        frame.setParameter(MAX_FRAME_SIZE, getParameter(
-                "jdk.httpclient.maxframesize",
-                16 * K, 16 * K * K -1, 16 * K));
-        return frame;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1288 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Flow;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLException;
-import jdk.incubator.http.HttpConnection.HttpPublisher;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.FlowTube.TubeSubscriber;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.frame.ContinuationFrame;
-import jdk.incubator.http.internal.frame.DataFrame;
-import jdk.incubator.http.internal.frame.ErrorFrame;
-import jdk.incubator.http.internal.frame.FramesDecoder;
-import jdk.incubator.http.internal.frame.FramesEncoder;
-import jdk.incubator.http.internal.frame.GoAwayFrame;
-import jdk.incubator.http.internal.frame.HeaderFrame;
-import jdk.incubator.http.internal.frame.HeadersFrame;
-import jdk.incubator.http.internal.frame.Http2Frame;
-import jdk.incubator.http.internal.frame.MalformedFrame;
-import jdk.incubator.http.internal.frame.OutgoingHeaders;
-import jdk.incubator.http.internal.frame.PingFrame;
-import jdk.incubator.http.internal.frame.PushPromiseFrame;
-import jdk.incubator.http.internal.frame.ResetFrame;
-import jdk.incubator.http.internal.frame.SettingsFrame;
-import jdk.incubator.http.internal.frame.WindowUpdateFrame;
-import jdk.incubator.http.internal.hpack.Encoder;
-import jdk.incubator.http.internal.hpack.Decoder;
-import jdk.incubator.http.internal.hpack.DecodingCallback;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static jdk.incubator.http.internal.frame.SettingsFrame.*;
-
-
-/**
- * An Http2Connection. Encapsulates the socket(channel) and any SSLEngine used
- * over it. Contains an HttpConnection which hides the SocketChannel SSL stuff.
- *
- * Http2Connections belong to a Http2ClientImpl, (one of) which belongs
- * to a HttpClientImpl.
- *
- * Creation cases:
- * 1) upgraded HTTP/1.1 plain tcp connection
- * 2) prior knowledge directly created plain tcp connection
- * 3) directly created HTTP/2 SSL connection which uses ALPN.
- *
- * Sending is done by writing directly to underlying HttpConnection object which
- * is operating in async mode. No flow control applies on output at this level
- * and all writes are just executed as puts to an output Q belonging to HttpConnection
- * Flow control is implemented by HTTP/2 protocol itself.
- *
- * Hpack header compression
- * and outgoing stream creation is also done here, because these operations
- * must be synchronized at the socket level. Stream objects send frames simply
- * by placing them on the connection's output Queue. sendFrame() is called
- * from a higher level (Stream) thread.
- *
- * asyncReceive(ByteBuffer) is always called from the selector thread. It assembles
- * incoming Http2Frames, and directs them to the appropriate Stream.incoming()
- * or handles them directly itself. This thread performs hpack decompression
- * and incoming stream creation (Server push). Incoming frames destined for a
- * stream are provided by calling Stream.incoming().
- */
-class Http2Connection  {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    static final boolean DEBUG_HPACK = Utils.DEBUG_HPACK; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    final static System.Logger  DEBUG_LOGGER =
-            Utils.getDebugLogger("Http2Connection"::toString, DEBUG);
-    private final System.Logger debugHpack =
-                  Utils.getHpackLogger(this::dbgString, DEBUG_HPACK);
-    static final ByteBuffer EMPTY_TRIGGER = ByteBuffer.allocate(0);
-
-    private boolean singleStream; // used only for stream 1, then closed
-
-    /*
-     *  ByteBuffer pooling strategy for HTTP/2 protocol:
-     *
-     * In general there are 4 points where ByteBuffers are used:
-     *  - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing encrypted data
-     *    in case of SSL connection.
-     *
-     * 1. Outgoing frames encoded to ByteBuffers.
-     *    Outgoing ByteBuffers are created with requited size and frequently small (except DataFrames, etc)
-     *    At this place no pools at all. All outgoing buffers should be collected by GC.
-     *
-     * 2. Incoming ByteBuffers (decoded to frames).
-     *    Here, total elimination of BB pool is not a good idea.
-     *    We don't know how many bytes we will receive through network.
-     * So here we allocate buffer of reasonable size. The following life of the BB:
-     * - If all frames decoded from the BB are other than DataFrame and HeaderFrame (and HeaderFrame subclasses)
-     *     BB is returned to pool,
-     * - If we decoded DataFrame from the BB. In that case DataFrame refers to subbuffer obtained by slice() method.
-     *     Such BB is never returned to pool and will be GCed.
-     * - If we decoded HeadersFrame from the BB. Then header decoding is performed inside processFrame method and
-     *     the buffer could be release to pool.
-     *
-     * 3. SLL encrypted buffers. Here another pool was introduced and all net buffers are to/from the pool,
-     *    because of we can't predict size encrypted packets.
-     *
-     */
-
-
-    // A small class that allows to control frames with respect to the state of
-    // the connection preface. Any data received before the connection
-    // preface is sent will be buffered.
-    private final class FramesController {
-        volatile boolean prefaceSent;
-        volatile List<ByteBuffer> pending;
-
-        boolean processReceivedData(FramesDecoder decoder, ByteBuffer buf)
-                throws IOException
-        {
-            // if preface is not sent, buffers data in the pending list
-            if (!prefaceSent) {
-                debug.log(Level.DEBUG, "Preface is not sent: buffering %d",
-                          buf.remaining());
-                synchronized (this) {
-                    if (!prefaceSent) {
-                        if (pending == null) pending = new ArrayList<>();
-                        pending.add(buf);
-                        debug.log(Level.DEBUG, () -> "there are now "
-                              + Utils.remaining(pending)
-                              + " bytes buffered waiting for preface to be sent");
-                        return false;
-                    }
-                }
-            }
-
-            // Preface is sent. Checks for pending data and flush it.
-            // We rely on this method being called from within the Http2TubeSubscriber
-            // scheduler, so we know that no other thread could execute this method
-            // concurrently while we're here.
-            // This ensures that later incoming buffers will not
-            // be processed before we have flushed the pending queue.
-            // No additional synchronization is therefore necessary here.
-            List<ByteBuffer> pending = this.pending;
-            this.pending = null;
-            if (pending != null) {
-                // flush pending data
-                debug.log(Level.DEBUG, () -> "Processing buffered data: "
-                      + Utils.remaining(pending));
-                for (ByteBuffer b : pending) {
-                    decoder.decode(b);
-                }
-            }
-            // push the received buffer to the frames decoder.
-            if (buf != EMPTY_TRIGGER) {
-                debug.log(Level.DEBUG, "Processing %d", buf.remaining());
-                decoder.decode(buf);
-            }
-            return true;
-        }
-
-        // Mark that the connection preface is sent
-        void markPrefaceSent() {
-            assert !prefaceSent;
-            synchronized (this) {
-                prefaceSent = true;
-            }
-        }
-    }
-
-    volatile boolean closed;
-
-    //-------------------------------------
-    final HttpConnection connection;
-    private final Http2ClientImpl client2;
-    private final Map<Integer,Stream<?>> streams = new ConcurrentHashMap<>();
-    private int nextstreamid;
-    private int nextPushStream = 2;
-    private final Encoder hpackOut;
-    private final Decoder hpackIn;
-    final SettingsFrame clientSettings;
-    private volatile SettingsFrame serverSettings;
-    private final String key; // for HttpClientImpl.connections map
-    private final FramesDecoder framesDecoder;
-    private final FramesEncoder framesEncoder = new FramesEncoder();
-
-    /**
-     * Send Window controller for both connection and stream windows.
-     * Each of this connection's Streams MUST use this controller.
-     */
-    private final WindowController windowController = new WindowController();
-    private final FramesController framesController = new FramesController();
-    private final Http2TubeSubscriber subscriber = new Http2TubeSubscriber();
-    final ConnectionWindowUpdateSender windowUpdater;
-    private volatile Throwable cause;
-    private volatile Supplier<ByteBuffer> initial;
-
-    static final int DEFAULT_FRAME_SIZE = 16 * 1024;
-
-
-    // TODO: need list of control frames from other threads
-    // that need to be sent
-
-    private Http2Connection(HttpConnection connection,
-                            Http2ClientImpl client2,
-                            int nextstreamid,
-                            String key) {
-        this.connection = connection;
-        this.client2 = client2;
-        this.nextstreamid = nextstreamid;
-        this.key = key;
-        this.clientSettings = this.client2.getClientSettings();
-        this.framesDecoder = new FramesDecoder(this::processFrame,
-                clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE));
-        // serverSettings will be updated by server
-        this.serverSettings = SettingsFrame.getDefaultSettings();
-        this.hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE));
-        this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
-        debugHpack.log(Level.DEBUG, () -> "For the record:" + super.toString());
-        debugHpack.log(Level.DEBUG, "Decoder created: %s", hpackIn);
-        debugHpack.log(Level.DEBUG, "Encoder created: %s", hpackOut);
-        this.windowUpdater = new ConnectionWindowUpdateSender(this,
-                client2.getConnectionWindowSize(clientSettings));
-    }
-
-    /**
-     * Case 1) Create from upgraded HTTP/1.1 connection.
-     * Is ready to use. Can be SSL. exchange is the Exchange
-     * that initiated the connection, whose response will be delivered
-     * on a Stream.
-     */
-    private Http2Connection(HttpConnection connection,
-                    Http2ClientImpl client2,
-                    Exchange<?> exchange,
-                    Supplier<ByteBuffer> initial)
-        throws IOException, InterruptedException
-    {
-        this(connection,
-                client2,
-                3, // stream 1 is registered during the upgrade
-                keyFor(connection));
-        Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
-
-        Stream<?> initialStream = createStream(exchange);
-        initialStream.registerStream(1);
-        windowController.registerStream(1, getInitialSendWindowSize());
-        initialStream.requestSent();
-        // Upgrading:
-        //    set callbacks before sending preface - makes sure anything that
-        //    might be sent by the server will come our way.
-        this.initial = initial;
-        connectFlows(connection);
-        sendConnectionPreface();
-    }
-
-    // Used when upgrading an HTTP/1.1 connection to HTTP/2 after receiving
-    // agreement from the server. Async style but completes immediately, because
-    // the connection is already connected.
-    static CompletableFuture<Http2Connection> createAsync(HttpConnection connection,
-                                                          Http2ClientImpl client2,
-                                                          Exchange<?> exchange,
-                                                          Supplier<ByteBuffer> initial)
-    {
-        return MinimalFuture.supply(() -> new Http2Connection(connection, client2, exchange, initial));
-    }
-
-    // Requires TLS handshake. So, is really async
-    static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,
-                                                          Http2ClientImpl h2client) {
-        assert request.secure();
-        AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)
-        HttpConnection.getConnection(request.getAddress(),
-                                     h2client.client(),
-                                     request,
-                                     HttpClient.Version.HTTP_2);
-
-        return connection.connectAsync()
-                  .thenCompose(unused -> checkSSLConfig(connection))
-                  .thenCompose(notused-> {
-                      CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
-                      try {
-                          Http2Connection hc = new Http2Connection(request, h2client, connection);
-                          cf.complete(hc);
-                      } catch (IOException e) {
-                          cf.completeExceptionally(e);
-                      }
-                      return cf; } );
-    }
-
-    /**
-     * Cases 2) 3)
-     *
-     * request is request to be sent.
-     */
-    private Http2Connection(HttpRequestImpl request,
-                            Http2ClientImpl h2client,
-                            HttpConnection connection)
-        throws IOException
-    {
-        this(connection,
-             h2client,
-             1,
-             keyFor(request.uri(), request.proxy()));
-
-        Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
-
-        // safe to resume async reading now.
-        connectFlows(connection);
-        sendConnectionPreface();
-    }
-
-    private void connectFlows(HttpConnection connection) {
-        FlowTube tube =  connection.getConnectionFlow();
-        // Connect the flow to our Http2TubeSubscriber:
-        tube.connectFlows(connection.publisher(), subscriber);
-    }
-
-    final HttpClientImpl client() {
-        return client2.client();
-    }
-
-    /**
-     * Throws an IOException if h2 was not negotiated
-     */
-    private static CompletableFuture<?> checkSSLConfig(AbstractAsyncSSLConnection aconn) {
-        assert aconn.isSecure();
-
-        Function<String, CompletableFuture<Void>> checkAlpnCF = (alpn) -> {
-            CompletableFuture<Void> cf = new MinimalFuture<>();
-            SSLEngine engine = aconn.getEngine();
-            assert Objects.equals(alpn, engine.getApplicationProtocol());
-
-            DEBUG_LOGGER.log(Level.DEBUG, "checkSSLConfig: alpn: %s", alpn );
-
-            if (alpn == null || !alpn.equals("h2")) {
-                String msg;
-                if (alpn == null) {
-                    Log.logSSL("ALPN not supported");
-                    msg = "ALPN not supported";
-                } else {
-                    switch (alpn) {
-                        case "":
-                            Log.logSSL(msg = "No ALPN negotiated");
-                            break;
-                        case "http/1.1":
-                            Log.logSSL( msg = "HTTP/1.1 ALPN returned");
-                            break;
-                        default:
-                            Log.logSSL(msg = "Unexpected ALPN: " + alpn);
-                            cf.completeExceptionally(new IOException(msg));
-                    }
-                }
-                cf.completeExceptionally(new ALPNException(msg, aconn));
-                return cf;
-            }
-            cf.complete(null);
-            return cf;
-        };
-
-        return aconn.getALPN()
-                .whenComplete((r,t) -> {
-                    if (t != null && t instanceof SSLException) {
-                        // something went wrong during the initial handshake
-                        // close the connection
-                        aconn.close();
-                    }
-                })
-                .thenCompose(checkAlpnCF);
-    }
-
-    synchronized boolean singleStream() {
-        return singleStream;
-    }
-
-    synchronized void setSingleStream(boolean use) {
-        singleStream = use;
-    }
-
-    static String keyFor(HttpConnection connection) {
-        boolean isProxy = connection.isProxied();
-        boolean isSecure = connection.isSecure();
-        InetSocketAddress addr = connection.address();
-
-        return keyString(isSecure, isProxy, addr.getHostString(), addr.getPort());
-    }
-
-    static String keyFor(URI uri, InetSocketAddress proxy) {
-        boolean isSecure = uri.getScheme().equalsIgnoreCase("https");
-        boolean isProxy = proxy != null;
-
-        String host;
-        int port;
-
-        if (proxy != null) {
-            host = proxy.getHostString();
-            port = proxy.getPort();
-        } else {
-            host = uri.getHost();
-            port = uri.getPort();
-        }
-        return keyString(isSecure, isProxy, host, port);
-    }
-
-    // {C,S}:{H:P}:host:port
-    // C indicates clear text connection "http"
-    // S indicates secure "https"
-    // H indicates host (direct) connection
-    // P indicates proxy
-    // Eg: "S:H:foo.com:80"
-    static String keyString(boolean secure, boolean proxy, String host, int port) {
-        if (secure && port == -1)
-            port = 443;
-        else if (!secure && port == -1)
-            port = 80;
-        return (secure ? "S:" : "C:") + (proxy ? "P:" : "H:") + host + ":" + port;
-    }
-
-    String key() {
-        return this.key;
-    }
-
-    boolean offerConnection() {
-        return client2.offerConnection(this);
-    }
-
-    private HttpPublisher publisher() {
-        return connection.publisher();
-    }
-
-    private void decodeHeaders(HeaderFrame frame, DecodingCallback decoder)
-            throws IOException
-    {
-        debugHpack.log(Level.DEBUG, "decodeHeaders(%s)", decoder);
-
-        boolean endOfHeaders = frame.getFlag(HeaderFrame.END_HEADERS);
-
-        List<ByteBuffer> buffers = frame.getHeaderBlock();
-        int len = buffers.size();
-        for (int i = 0; i < len; i++) {
-            ByteBuffer b = buffers.get(i);
-            hpackIn.decode(b, endOfHeaders && (i == len - 1), decoder);
-        }
-    }
-
-    final int getInitialSendWindowSize() {
-        return serverSettings.getParameter(INITIAL_WINDOW_SIZE);
-    }
-
-    void close() {
-        Log.logTrace("Closing HTTP/2 connection: to {0}", connection.address());
-        GoAwayFrame f = new GoAwayFrame(0,
-                                        ErrorFrame.NO_ERROR,
-                                        "Requested by user".getBytes(UTF_8));
-        // TODO: set last stream. For now zero ok.
-        sendFrame(f);
-    }
-
-    long count;
-    final void asyncReceive(ByteBuffer buffer) {
-        // We don't need to read anything and
-        // we don't want to send anything back to the server
-        // until the connection preface has been sent.
-        // Therefore we're going to wait if needed before reading
-        // (and thus replying) to anything.
-        // Starting to reply to something (e.g send an ACK to a
-        // SettingsFrame sent by the server) before the connection
-        // preface is fully sent might result in the server
-        // sending a GOAWAY frame with 'invalid_preface'.
-        //
-        // Note: asyncReceive is only called from the Http2TubeSubscriber
-        //       sequential scheduler.
-        try {
-            Supplier<ByteBuffer> bs = initial;
-            // ensure that we always handle the initial buffer first,
-            // if any.
-            if (bs != null) {
-                initial = null;
-                ByteBuffer b = bs.get();
-                if (b.hasRemaining()) {
-                    long c = ++count;
-                    debug.log(Level.DEBUG, () -> "H2 Receiving Initial("
-                        + c +"): " + b.remaining());
-                    framesController.processReceivedData(framesDecoder, b);
-                }
-            }
-            ByteBuffer b = buffer;
-            // the Http2TubeSubscriber scheduler ensures that the order of incoming
-            // buffers is preserved.
-            if (b == EMPTY_TRIGGER) {
-                debug.log(Level.DEBUG, "H2 Received EMPTY_TRIGGER");
-                boolean prefaceSent = framesController.prefaceSent;
-                assert prefaceSent;
-                // call framesController.processReceivedData to potentially
-                // trigger the processing of all the data buffered there.
-                framesController.processReceivedData(framesDecoder, buffer);
-                debug.log(Level.DEBUG, "H2 processed buffered data");
-            } else {
-                long c = ++count;
-                debug.log(Level.DEBUG, "H2 Receiving(%d): %d", c, b.remaining());
-                framesController.processReceivedData(framesDecoder, buffer);
-                debug.log(Level.DEBUG, "H2 processed(%d)", c);
-            }
-        } catch (Throwable e) {
-            String msg = Utils.stackTrace(e);
-            Log.logTrace(msg);
-            shutdown(e);
-        }
-    }
-
-    Throwable getRecordedCause() {
-        return cause;
-    }
-
-    void shutdown(Throwable t) {
-        debug.log(Level.DEBUG, () -> "Shutting down h2c (closed="+closed+"): " + t);
-        if (closed == true) return;
-        synchronized (this) {
-            if (closed == true) return;
-            closed = true;
-        }
-        Log.logError(t);
-        Throwable initialCause = this.cause;
-        if (initialCause == null) this.cause = t;
-        client2.deleteConnection(this);
-        List<Stream<?>> c = new LinkedList<>(streams.values());
-        for (Stream<?> s : c) {
-            s.cancelImpl(t);
-        }
-        connection.close();
-    }
-
-    /**
-     * Streams initiated by a client MUST use odd-numbered stream
-     * identifiers; those initiated by the server MUST use even-numbered
-     * stream identifiers.
-     */
-    private static final boolean isSeverInitiatedStream(int streamid) {
-        return (streamid & 0x1) == 0;
-    }
-
-    /**
-     * Handles stream 0 (common) frames that apply to whole connection and passes
-     * other stream specific frames to that Stream object.
-     *
-     * Invokes Stream.incoming() which is expected to process frame without
-     * blocking.
-     */
-    void processFrame(Http2Frame frame) throws IOException {
-        Log.logFrames(frame, "IN");
-        int streamid = frame.streamid();
-        if (frame instanceof MalformedFrame) {
-            Log.logError(((MalformedFrame) frame).getMessage());
-            if (streamid == 0) {
-                framesDecoder.close("Malformed frame on stream 0");
-                protocolError(((MalformedFrame) frame).getErrorCode(),
-                        ((MalformedFrame) frame).getMessage());
-            } else {
-                debug.log(Level.DEBUG, () -> "Reset stream: "
-                          + ((MalformedFrame) frame).getMessage());
-                resetStream(streamid, ((MalformedFrame) frame).getErrorCode());
-            }
-            return;
-        }
-        if (streamid == 0) {
-            handleConnectionFrame(frame);
-        } else {
-            if (frame instanceof SettingsFrame) {
-                // The stream identifier for a SETTINGS frame MUST be zero
-                framesDecoder.close(
-                        "The stream identifier for a SETTINGS frame MUST be zero");
-                protocolError(GoAwayFrame.PROTOCOL_ERROR);
-                return;
-            }
-
-            Stream<?> stream = getStream(streamid);
-            if (stream == null) {
-                // Should never receive a frame with unknown stream id
-
-                if (frame instanceof HeaderFrame) {
-                    // always decode the headers as they may affect
-                    // connection-level HPACK decoding state
-                    HeaderDecoder decoder = new LoggingHeaderDecoder(new HeaderDecoder());
-                    decodeHeaders((HeaderFrame) frame, decoder);
-                }
-
-                if (!(frame instanceof ResetFrame)) {
-                    if (isSeverInitiatedStream(streamid)) {
-                        if (streamid < nextPushStream) {
-                            // trailing data on a cancelled push promise stream,
-                            // reset will already have been sent, ignore
-                            Log.logTrace("Ignoring cancelled push promise frame " + frame);
-                        } else {
-                            resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
-                        }
-                    } else if (streamid >= nextstreamid) {
-                        // otherwise the stream has already been reset/closed
-                        resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
-                    }
-                }
-                return;
-            }
-            if (frame instanceof PushPromiseFrame) {
-                PushPromiseFrame pp = (PushPromiseFrame)frame;
-                handlePushPromise(stream, pp);
-            } else if (frame instanceof HeaderFrame) {
-                // decode headers (or continuation)
-                decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer());
-                stream.incoming(frame);
-            } else {
-                stream.incoming(frame);
-            }
-        }
-    }
-
-    private <T> void handlePushPromise(Stream<T> parent, PushPromiseFrame pp)
-        throws IOException
-    {
-        // always decode the headers as they may affect connection-level HPACK
-        // decoding state
-        HeaderDecoder decoder = new LoggingHeaderDecoder(new HeaderDecoder());
-        decodeHeaders(pp, decoder);
-
-        HttpRequestImpl parentReq = parent.request;
-        int promisedStreamid = pp.getPromisedStream();
-        if (promisedStreamid != nextPushStream) {
-            resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);
-            return;
-        } else {
-            nextPushStream += 2;
-        }
-
-        HttpHeadersImpl headers = decoder.headers();
-        HttpRequestImpl pushReq = HttpRequestImpl.createPushRequest(parentReq, headers);
-        Exchange<T> pushExch = new Exchange<>(pushReq, parent.exchange.multi);
-        Stream.PushedStream<T> pushStream = createPushStream(parent, pushExch);
-        pushExch.exchImpl = pushStream;
-        pushStream.registerStream(promisedStreamid);
-        parent.incoming_pushPromise(pushReq, pushStream);
-    }
-
-    private void handleConnectionFrame(Http2Frame frame)
-        throws IOException
-    {
-        switch (frame.type()) {
-          case SettingsFrame.TYPE:
-              handleSettings((SettingsFrame)frame);
-              break;
-          case PingFrame.TYPE:
-              handlePing((PingFrame)frame);
-              break;
-          case GoAwayFrame.TYPE:
-              handleGoAway((GoAwayFrame)frame);
-              break;
-          case WindowUpdateFrame.TYPE:
-              handleWindowUpdate((WindowUpdateFrame)frame);
-              break;
-          default:
-            protocolError(ErrorFrame.PROTOCOL_ERROR);
-        }
-    }
-
-    void resetStream(int streamid, int code) throws IOException {
-        Log.logError(
-            "Resetting stream {0,number,integer} with error code {1,number,integer}",
-            streamid, code);
-        ResetFrame frame = new ResetFrame(streamid, code);
-        sendFrame(frame);
-        closeStream(streamid);
-    }
-
-    void closeStream(int streamid) {
-        debug.log(Level.DEBUG, "Closed stream %d", streamid);
-        Stream<?> s = streams.remove(streamid);
-        if (s != null) {
-            // decrement the reference count on the HttpClientImpl
-            // to allow the SelectorManager thread to exit if no
-            // other operation is pending and the facade is no
-            // longer referenced.
-            client().unreference();
-        }
-        // ## Remove s != null. It is a hack for delayed cancellation,reset
-        if (s != null && !(s instanceof Stream.PushedStream)) {
-            // Since PushStreams have no request body, then they have no
-            // corresponding entry in the window controller.
-            windowController.removeStream(streamid);
-        }
-        if (singleStream() && streams.isEmpty()) {
-            // should be only 1 stream, but there might be more if server push
-            close();
-        }
-    }
-
-    /**
-     * Increments this connection's send Window by the amount in the given frame.
-     */
-    private void handleWindowUpdate(WindowUpdateFrame f)
-        throws IOException
-    {
-        int amount = f.getUpdate();
-        if (amount <= 0) {
-            // ## temporarily disable to workaround a bug in Jetty where it
-            // ## sends Window updates with a 0 update value.
-            //protocolError(ErrorFrame.PROTOCOL_ERROR);
-        } else {
-            boolean success = windowController.increaseConnectionWindow(amount);
-            if (!success) {
-                protocolError(ErrorFrame.FLOW_CONTROL_ERROR);  // overflow
-            }
-        }
-    }
-
-    private void protocolError(int errorCode)
-        throws IOException
-    {
-        protocolError(errorCode, null);
-    }
-
-    private void protocolError(int errorCode, String msg)
-        throws IOException
-    {
-        GoAwayFrame frame = new GoAwayFrame(0, errorCode);
-        sendFrame(frame);
-        shutdown(new IOException("protocol error" + (msg == null?"":(": " + msg))));
-    }
-
-    private void handleSettings(SettingsFrame frame)
-        throws IOException
-    {
-        assert frame.streamid() == 0;
-        if (!frame.getFlag(SettingsFrame.ACK)) {
-            int oldWindowSize = serverSettings.getParameter(INITIAL_WINDOW_SIZE);
-            int newWindowSize = frame.getParameter(INITIAL_WINDOW_SIZE);
-            int diff = newWindowSize - oldWindowSize;
-            if (diff != 0) {
-                windowController.adjustActiveStreams(diff);
-            }
-            serverSettings = frame;
-            sendFrame(new SettingsFrame(SettingsFrame.ACK));
-        }
-    }
-
-    private void handlePing(PingFrame frame)
-        throws IOException
-    {
-        frame.setFlag(PingFrame.ACK);
-        sendUnorderedFrame(frame);
-    }
-
-    private void handleGoAway(GoAwayFrame frame)
-        throws IOException
-    {
-        shutdown(new IOException(
-                        String.valueOf(connection.channel().getLocalAddress())
-                        +": GOAWAY received"));
-    }
-
-    /**
-     * Max frame size we are allowed to send
-     */
-    public int getMaxSendFrameSize() {
-        int param = serverSettings.getParameter(MAX_FRAME_SIZE);
-        if (param == -1) {
-            param = DEFAULT_FRAME_SIZE;
-        }
-        return param;
-    }
-
-    /**
-     * Max frame size we will receive
-     */
-    public int getMaxReceiveFrameSize() {
-        return clientSettings.getParameter(MAX_FRAME_SIZE);
-    }
-
-    private static final String CLIENT_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
-
-    private static final byte[] PREFACE_BYTES =
-        CLIENT_PREFACE.getBytes(StandardCharsets.ISO_8859_1);
-
-    /**
-     * Sends Connection preface and Settings frame with current preferred
-     * values
-     */
-    private void sendConnectionPreface() throws IOException {
-        Log.logTrace("{0}: start sending connection preface to {1}",
-                     connection.channel().getLocalAddress(),
-                     connection.address());
-        SettingsFrame sf = new SettingsFrame(clientSettings);
-        int initialWindowSize = sf.getParameter(INITIAL_WINDOW_SIZE);
-        ByteBuffer buf = framesEncoder.encodeConnectionPreface(PREFACE_BYTES, sf);
-        Log.logFrames(sf, "OUT");
-        // send preface bytes and SettingsFrame together
-        HttpPublisher publisher = publisher();
-        publisher.enqueue(List.of(buf));
-        publisher.signalEnqueued();
-        // mark preface sent.
-        framesController.markPrefaceSent();
-        Log.logTrace("PREFACE_BYTES sent");
-        Log.logTrace("Settings Frame sent");
-
-        // send a Window update for the receive buffer we are using
-        // minus the initial 64 K specified in protocol
-        final int len = windowUpdater.initialWindowSize - initialWindowSize;
-        if (len > 0) {
-            windowUpdater.sendWindowUpdate(len);
-        }
-        // there will be an ACK to the windows update - which should
-        // cause any pending data stored before the preface was sent to be
-        // flushed (see PrefaceController).
-        Log.logTrace("finished sending connection preface");
-        debug.log(Level.DEBUG, "Triggering processing of buffered data"
-                  + " after sending connection preface");
-        subscriber.onNext(List.of(EMPTY_TRIGGER));
-    }
-
-    /**
-     * Returns an existing Stream with given id, or null if doesn't exist
-     */
-    @SuppressWarnings("unchecked")
-    <T> Stream<T> getStream(int streamid) {
-        return (Stream<T>)streams.get(streamid);
-    }
-
-    /**
-     * Creates Stream with given id.
-     */
-    final <T> Stream<T> createStream(Exchange<T> exchange) {
-        Stream<T> stream = new Stream<>(this, exchange, windowController);
-        return stream;
-    }
-
-    <T> Stream.PushedStream<T> createPushStream(Stream<T> parent, Exchange<T> pushEx) {
-        PushGroup<T> pg = parent.exchange.getPushGroup();
-        return new Stream.PushedStream<>(pg, this, pushEx);
-    }
-
-    <T> void putStream(Stream<T> stream, int streamid) {
-        // increment the reference count on the HttpClientImpl
-        // to prevent the SelectorManager thread from exiting until
-        // the stream is closed.
-        client().reference();
-        streams.put(streamid, stream);
-    }
-
-    /**
-     * Encode the headers into a List<ByteBuffer> and then create HEADERS
-     * and CONTINUATION frames from the list and return the List<Http2Frame>.
-     */
-    private List<HeaderFrame> encodeHeaders(OutgoingHeaders<Stream<?>> frame) {
-        List<ByteBuffer> buffers = encodeHeadersImpl(
-                getMaxSendFrameSize(),
-                frame.getAttachment().getRequestPseudoHeaders(),
-                frame.getUserHeaders(),
-                frame.getSystemHeaders());
-
-        List<HeaderFrame> frames = new ArrayList<>(buffers.size());
-        Iterator<ByteBuffer> bufIterator = buffers.iterator();
-        HeaderFrame oframe = new HeadersFrame(frame.streamid(), frame.getFlags(), bufIterator.next());
-        frames.add(oframe);
-        while(bufIterator.hasNext()) {
-            oframe = new ContinuationFrame(frame.streamid(), bufIterator.next());
-            frames.add(oframe);
-        }
-        oframe.setFlag(HeaderFrame.END_HEADERS);
-        return frames;
-    }
-
-    // Dedicated cache for headers encoding ByteBuffer.
-    // There can be no concurrent access to this  buffer as all access to this buffer
-    // and its content happen within a single critical code block section protected
-    // by the sendLock. / (see sendFrame())
-    // private final ByteBufferPool headerEncodingPool = new ByteBufferPool();
-
-    private ByteBuffer getHeaderBuffer(int maxFrameSize) {
-        ByteBuffer buf = ByteBuffer.allocate(maxFrameSize);
-        buf.limit(maxFrameSize);
-        return buf;
-    }
-
-    /*
-     * Encodes all the headers from the given HttpHeaders into the given List
-     * of buffers.
-     *
-     * From https://tools.ietf.org/html/rfc7540#section-8.1.2 :
-     *
-     *     ...Just as in HTTP/1.x, header field names are strings of ASCII
-     *     characters that are compared in a case-insensitive fashion.  However,
-     *     header field names MUST be converted to lowercase prior to their
-     *     encoding in HTTP/2...
-     */
-    private List<ByteBuffer> encodeHeadersImpl(int maxFrameSize, HttpHeaders... headers) {
-        ByteBuffer buffer = getHeaderBuffer(maxFrameSize);
-        List<ByteBuffer> buffers = new ArrayList<>();
-        for(HttpHeaders header : headers) {
-            for (Map.Entry<String, List<String>> e : header.map().entrySet()) {
-                String lKey = e.getKey().toLowerCase();
-                List<String> values = e.getValue();
-                for (String value : values) {
-                    hpackOut.header(lKey, value);
-                    while (!hpackOut.encode(buffer)) {
-                        buffer.flip();
-                        buffers.add(buffer);
-                        buffer =  getHeaderBuffer(maxFrameSize);
-                    }
-                }
-            }
-        }
-        buffer.flip();
-        buffers.add(buffer);
-        return buffers;
-    }
-
-    private List<ByteBuffer> encodeHeaders(OutgoingHeaders<Stream<?>> oh, Stream<?> stream) {
-        oh.streamid(stream.streamid);
-        if (Log.headers()) {
-            StringBuilder sb = new StringBuilder("HEADERS FRAME (stream=");
-            sb.append(stream.streamid).append(")\n");
-            Log.dumpHeaders(sb, "    ", oh.getAttachment().getRequestPseudoHeaders());
-            Log.dumpHeaders(sb, "    ", oh.getSystemHeaders());
-            Log.dumpHeaders(sb, "    ", oh.getUserHeaders());
-            Log.logHeaders(sb.toString());
-        }
-        List<HeaderFrame> frames = encodeHeaders(oh);
-        return encodeFrames(frames);
-    }
-
-    private List<ByteBuffer> encodeFrames(List<HeaderFrame> frames) {
-        if (Log.frames()) {
-            frames.forEach(f -> Log.logFrames(f, "OUT"));
-        }
-        return framesEncoder.encodeFrames(frames);
-    }
-
-    private Stream<?> registerNewStream(OutgoingHeaders<Stream<?>> oh) {
-        Stream<?> stream = oh.getAttachment();
-        int streamid = nextstreamid;
-        nextstreamid += 2;
-        stream.registerStream(streamid);
-        // set outgoing window here. This allows thread sending
-        // body to proceed.
-        windowController.registerStream(streamid, getInitialSendWindowSize());
-        return stream;
-    }
-
-    private final Object sendlock = new Object();
-
-    void sendFrame(Http2Frame frame) {
-        try {
-            HttpPublisher publisher = publisher();
-            synchronized (sendlock) {
-                if (frame instanceof OutgoingHeaders) {
-                    @SuppressWarnings("unchecked")
-                    OutgoingHeaders<Stream<?>> oh = (OutgoingHeaders<Stream<?>>) frame;
-                    Stream<?> stream = registerNewStream(oh);
-                    // provide protection from inserting unordered frames between Headers and Continuation
-                    publisher.enqueue(encodeHeaders(oh, stream));
-                } else {
-                    publisher.enqueue(encodeFrame(frame));
-                }
-            }
-            publisher.signalEnqueued();
-        } catch (IOException e) {
-            if (!closed) {
-                Log.logError(e);
-                shutdown(e);
-            }
-        }
-    }
-
-    private List<ByteBuffer> encodeFrame(Http2Frame frame) {
-        Log.logFrames(frame, "OUT");
-        return framesEncoder.encodeFrame(frame);
-    }
-
-    void sendDataFrame(DataFrame frame) {
-        try {
-            HttpPublisher publisher = publisher();
-            publisher.enqueue(encodeFrame(frame));
-            publisher.signalEnqueued();
-        } catch (IOException e) {
-            if (!closed) {
-                Log.logError(e);
-                shutdown(e);
-            }
-        }
-    }
-
-    /*
-     * Direct call of the method bypasses synchronization on "sendlock" and
-     * allowed only of control frames: WindowUpdateFrame, PingFrame and etc.
-     * prohibited for such frames as DataFrame, HeadersFrame, ContinuationFrame.
-     */
-    void sendUnorderedFrame(Http2Frame frame) {
-        try {
-            HttpPublisher publisher = publisher();
-            publisher.enqueueUnordered(encodeFrame(frame));
-            publisher.signalEnqueued();
-        } catch (IOException e) {
-            if (!closed) {
-                Log.logError(e);
-                shutdown(e);
-            }
-        }
-    }
-
-    /**
-     * A simple tube subscriber for reading from the connection flow.
-     */
-    final class Http2TubeSubscriber implements TubeSubscriber {
-        volatile Flow.Subscription subscription;
-        volatile boolean completed;
-        volatile boolean dropped;
-        volatile Throwable error;
-        final ConcurrentLinkedQueue<ByteBuffer> queue
-                = new ConcurrentLinkedQueue<>();
-        final SequentialScheduler scheduler =
-                SequentialScheduler.synchronizedScheduler(this::processQueue);
-
-        final void processQueue() {
-            try {
-                while (!queue.isEmpty() && !scheduler.isStopped()) {
-                    ByteBuffer buffer = queue.poll();
-                    debug.log(Level.DEBUG,
-                              "sending %d to Http2Connection.asyncReceive",
-                              buffer.remaining());
-                    asyncReceive(buffer);
-                }
-            } catch (Throwable t) {
-                Throwable x = error;
-                if (x == null) error = t;
-            } finally {
-                Throwable x = error;
-                if (x != null) {
-                    debug.log(Level.DEBUG, "Stopping scheduler", x);
-                    scheduler.stop();
-                    Http2Connection.this.shutdown(x);
-                }
-            }
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            // supports being called multiple time.
-            // doesn't cancel the previous subscription, since that is
-            // most probably the same as the new subscription.
-            assert this.subscription == null || dropped == false;
-            this.subscription = subscription;
-            dropped = false;
-            // TODO FIXME: request(1) should be done by the delegate.
-            if (!completed) {
-                debug.log(Level.DEBUG, "onSubscribe: requesting Long.MAX_VALUE for reading");
-                subscription.request(Long.MAX_VALUE);
-            } else {
-                debug.log(Level.DEBUG, "onSubscribe: already completed");
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            debug.log(Level.DEBUG, () -> "onNext: got " + Utils.remaining(item)
-                    + " bytes in " + item.size() + " buffers");
-            queue.addAll(item);
-            scheduler.runOrSchedule(client().theExecutor());
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            debug.log(Level.DEBUG, () -> "onError: " + throwable);
-            error = throwable;
-            completed = true;
-            scheduler.runOrSchedule(client().theExecutor());
-        }
-
-        @Override
-        public void onComplete() {
-            debug.log(Level.DEBUG, "EOF");
-            error = new EOFException("EOF reached while reading");
-            completed = true;
-            scheduler.runOrSchedule(client().theExecutor());
-        }
-
-        @Override
-        public void dropSubscription() {
-            debug.log(Level.DEBUG, "dropSubscription");
-            // we could probably set subscription to null here...
-            // then we might not need the 'dropped' boolean?
-            dropped = true;
-        }
-    }
-
-    @Override
-    public final String toString() {
-        return dbgString();
-    }
-
-    final String dbgString() {
-        return "Http2Connection("
-                    + connection.getConnectionFlow() + ")";
-    }
-
-    final class LoggingHeaderDecoder extends HeaderDecoder {
-
-        private final HeaderDecoder delegate;
-        private final System.Logger debugHpack =
-                Utils.getHpackLogger(this::dbgString, DEBUG_HPACK);
-
-        LoggingHeaderDecoder(HeaderDecoder delegate) {
-            this.delegate = delegate;
-        }
-
-        String dbgString() {
-            return Http2Connection.this.dbgString() + "/LoggingHeaderDecoder";
-        }
-
-        @Override
-        public void onDecoded(CharSequence name, CharSequence value) {
-            delegate.onDecoded(name, value);
-        }
-
-        @Override
-        public void onIndexed(int index,
-                              CharSequence name,
-                              CharSequence value) {
-            debugHpack.log(Level.DEBUG, "onIndexed(%s, %s, %s)%n",
-                           index, name, value);
-            delegate.onIndexed(index, name, value);
-        }
-
-        @Override
-        public void onLiteral(int index,
-                              CharSequence name,
-                              CharSequence value,
-                              boolean valueHuffman) {
-            debugHpack.log(Level.DEBUG, "onLiteral(%s, %s, %s, %s)%n",
-                              index, name, value, valueHuffman);
-            delegate.onLiteral(index, name, value, valueHuffman);
-        }
-
-        @Override
-        public void onLiteral(CharSequence name,
-                              boolean nameHuffman,
-                              CharSequence value,
-                              boolean valueHuffman) {
-            debugHpack.log(Level.DEBUG, "onLiteral(%s, %s, %s, %s)%n",
-                           name, nameHuffman, value, valueHuffman);
-            delegate.onLiteral(name, nameHuffman, value, valueHuffman);
-        }
-
-        @Override
-        public void onLiteralNeverIndexed(int index,
-                                          CharSequence name,
-                                          CharSequence value,
-                                          boolean valueHuffman) {
-            debugHpack.log(Level.DEBUG, "onLiteralNeverIndexed(%s, %s, %s, %s)%n",
-                           index, name, value, valueHuffman);
-            delegate.onLiteralNeverIndexed(index, name, value, valueHuffman);
-        }
-
-        @Override
-        public void onLiteralNeverIndexed(CharSequence name,
-                                          boolean nameHuffman,
-                                          CharSequence value,
-                                          boolean valueHuffman) {
-            debugHpack.log(Level.DEBUG, "onLiteralNeverIndexed(%s, %s, %s, %s)%n",
-                           name, nameHuffman, value, valueHuffman);
-            delegate.onLiteralNeverIndexed(name, nameHuffman, value, valueHuffman);
-        }
-
-        @Override
-        public void onLiteralWithIndexing(int index,
-                                          CharSequence name,
-                                          CharSequence value,
-                                          boolean valueHuffman) {
-            debugHpack.log(Level.DEBUG, "onLiteralWithIndexing(%s, %s, %s, %s)%n",
-                           index, name, value, valueHuffman);
-            delegate.onLiteralWithIndexing(index, name, value, valueHuffman);
-        }
-
-        @Override
-        public void onLiteralWithIndexing(CharSequence name,
-                                          boolean nameHuffman,
-                                          CharSequence value,
-                                          boolean valueHuffman) {
-            debugHpack.log(Level.DEBUG, "onLiteralWithIndexing(%s, %s, %s, %s)%n",
-                              name, nameHuffman, value, valueHuffman);
-            delegate.onLiteralWithIndexing(name, nameHuffman, value, valueHuffman);
-        }
-
-        @Override
-        public void onSizeUpdate(int capacity) {
-            debugHpack.log(Level.DEBUG, "onSizeUpdate(%s)%n", capacity);
-            delegate.onSizeUpdate(capacity);
-        }
-
-        @Override
-        HttpHeadersImpl headers() {
-            return delegate.headers();
-        }
-    }
-
-    static class HeaderDecoder implements DecodingCallback {
-        HttpHeadersImpl headers;
-
-        HeaderDecoder() {
-            this.headers = new HttpHeadersImpl();
-        }
-
-        @Override
-        public void onDecoded(CharSequence name, CharSequence value) {
-            headers.addHeader(name.toString(), value.toString());
-        }
-
-        HttpHeadersImpl headers() {
-            return headers;
-        }
-    }
-
-    static final class ConnectionWindowUpdateSender extends WindowUpdateSender {
-
-        final int initialWindowSize;
-        public ConnectionWindowUpdateSender(Http2Connection connection,
-                                            int initialWindowSize) {
-            super(connection, initialWindowSize);
-            this.initialWindowSize = initialWindowSize;
-        }
-
-        @Override
-        int getStreamId() {
-            return 0;
-        }
-    }
-
-    /**
-     * Thrown when https handshake negotiates http/1.1 alpn instead of h2
-     */
-    static final class ALPNException extends IOException {
-        private static final long serialVersionUID = 0L;
-        final transient AbstractAsyncSSLConnection connection;
-
-        ALPNException(String msg, AbstractAsyncSSLConnection connection) {
-            super(msg);
-            this.connection = connection;
-        }
-
-        AbstractAsyncSSLConnection getConnection() {
-            return connection;
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java	Tue Feb 06 11:39:55 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java	Tue Feb 06 14:10:28 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -31,7 +31,6 @@
 import java.net.InetSocketAddress;
 import java.net.Proxy;
 import java.net.ProxySelector;
-import java.net.URI;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
@@ -41,6 +40,7 @@
 import javax.net.ssl.SSLParameters;
 import jdk.incubator.http.HttpResponse.BodyHandler;
 import jdk.incubator.http.HttpResponse.PushPromiseHandler;
+import jdk.incubator.http.internal.HttpClientBuilderImpl;
 
 /**
  * A container for configuration information common to multiple {@link
@@ -111,9 +111,10 @@
         /**
          * A proxy selector that always return {@link Proxy#NO_PROXY} implying
          * a direct connection.
-         * This is a convenience object that can be passed to {@link #proxy(ProxySelector)}
-         * in order to build an instance of {@link HttpClient} that uses no
-         * proxy.
+         *
+         * <p> This is a convenience object that can be passed to
+         * {@link #proxy(ProxySelector)} in order to build an instance of
+         * {@link HttpClient} that uses no proxy.
          */
         public static final ProxySelector NO_PROXY = ProxySelector.of(null);
 
@@ -207,7 +208,7 @@
          * will use HTTP/2. If the upgrade fails, then the response will be
          * handled using HTTP/1.1
          *
-         * @implNote Constraints may also affect the selection of protocol version. 
+         * @implNote Constraints may also affect the selection of protocol version.
          * For example, if HTTP/2 is requested through a proxy, and if the implementation
          * does not support this mode, then HTTP/1.1 may be used
          *
@@ -335,9 +336,9 @@
      * Returns the preferred HTTP protocol version for this client. The default
      * value is {@link HttpClient.Version#HTTP_2}
      *
-     * @implNote Constraints may also affect the selection of protocol version. 
-     * For example, if HTTP/2 is requested through a proxy, and if the implementation
-     * does not support this mode, then HTTP/1.1 may be used
+     * @implNote Constraints may also affect the selection of protocol version.
+     * For example, if HTTP/2 is requested through a proxy, and if the
+     * implementation does not support this mode, then HTTP/1.1 may be used
      *
      * @return the HTTP protocol version requested
      */
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientBuilderImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ProxySelector;
-import java.util.concurrent.Executor;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.internal.common.Utils;
-import static java.util.Objects.requireNonNull;
-
-class HttpClientBuilderImpl extends HttpClient.Builder {
-
-    CookieHandler cookieHandler;
-    HttpClient.Redirect followRedirects;
-    ProxySelector proxy;
-    Authenticator authenticator;
-    HttpClient.Version version;
-    Executor executor;
-    // Security parameters
-    SSLContext sslContext;
-    SSLParameters sslParams;
-    int priority = -1;
-
-    @Override
-    public HttpClientBuilderImpl cookieHandler(CookieHandler cookieHandler) {
-        requireNonNull(cookieHandler);
-        this.cookieHandler = cookieHandler;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl sslContext(SSLContext sslContext) {
-        requireNonNull(sslContext);
-        this.sslContext = sslContext;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl sslParameters(SSLParameters sslParameters) {
-        requireNonNull(sslParameters);
-        this.sslParams = Utils.copySSLParameters(sslParameters);
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl executor(Executor s) {
-        requireNonNull(s);
-        this.executor = s;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl followRedirects(HttpClient.Redirect policy) {
-        requireNonNull(policy);
-        this.followRedirects = policy;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl version(HttpClient.Version version) {
-        requireNonNull(version);
-        this.version = version;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl priority(int priority) {
-        if (priority < 1 || priority > 256) {
-            throw new IllegalArgumentException("priority must be between 1 and 256");
-        }
-        this.priority = priority;
-        return this;
-    }
-
-    @Override
-    public HttpClientBuilderImpl proxy(ProxySelector proxy) {
-        requireNonNull(proxy);
-        this.proxy = proxy;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl authenticator(Authenticator a) {
-        requireNonNull(a);
-        this.authenticator = a;
-        return this;
-    }
-
-    @Override
-    public HttpClient build() {
-        return HttpClientImpl.create(this);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientFacade.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.ref.Reference;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.PushPromiseHandler;
-
-/**
- * An HttpClientFacade is a simple class that wraps an HttpClient implementation
- * and delegates everything to its implementation delegate.
- */
-final class HttpClientFacade extends HttpClient {
-
-    final HttpClientImpl impl;
-
-    /**
-     * Creates an HttpClientFacade.
-     */
-    HttpClientFacade(HttpClientImpl impl) {
-        this.impl = impl;
-    }
-
-    @Override
-    public Optional<CookieHandler> cookieHandler() {
-        return impl.cookieHandler();
-    }
-
-    @Override
-    public Redirect followRedirects() {
-        return impl.followRedirects();
-    }
-
-    @Override
-    public Optional<ProxySelector> proxy() {
-        return impl.proxy();
-    }
-
-    @Override
-    public SSLContext sslContext() {
-        return impl.sslContext();
-    }
-
-    @Override
-    public SSLParameters sslParameters() {
-        return impl.sslParameters();
-    }
-
-    @Override
-    public Optional<Authenticator> authenticator() {
-        return impl.authenticator();
-    }
-
-    @Override
-    public HttpClient.Version version() {
-        return impl.version();
-    }
-
-    @Override
-    public Optional<Executor> executor() {
-        return impl.executor();
-    }
-
-    @Override
-    public <T> HttpResponse<T>
-    send(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)
-        throws IOException, InterruptedException
-    {
-        try {
-            return impl.send(req, responseBodyHandler);
-        } finally {
-            Reference.reachabilityFence(this);
-        }
-    }
-
-    @Override
-    public <T> CompletableFuture<HttpResponse<T>>
-    sendAsync(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler) {
-        try {
-            return impl.sendAsync(req, responseBodyHandler);
-        } finally {
-            Reference.reachabilityFence(this);
-        }
-    }
-
-    @Override
-    public <T> CompletableFuture<HttpResponse<T>>
-    sendAsync(HttpRequest req,
-              BodyHandler<T> responseBodyHandler,
-              PushPromiseHandler<T> pushPromiseHandler){
-        try {
-            return impl.sendAsync(req, responseBodyHandler, pushPromiseHandler);
-        } finally {
-            Reference.reachabilityFence(this);
-        }
-    }
-
-    @Override
-    public WebSocket.Builder newWebSocketBuilder() {
-        try {
-            return impl.newWebSocketBuilder();
-        } finally {
-            Reference.reachabilityFence(this);
-        }
-    }
-
-    @Override
-    public String toString() {
-        // Used by tests to get the client's id.
-        return impl.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1013 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.lang.ref.WeakReference;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ProxySelector;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivilegedAction;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Stream;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.PushPromiseHandler;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Pair;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.websocket.BuilderImpl;
-import jdk.internal.misc.InnocuousThread;
-
-/**
- * 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 {
-
-    static final boolean DEBUG = Utils.DEBUG;  // Revisit: temporary dev flag.
-    static final boolean DEBUGELAPSED = Utils.TESTING || DEBUG;  // Revisit: temporary dev flag.
-    static final boolean DEBUGTIMEOUT = false; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    final System.Logger  debugelapsed = Utils.getDebugLogger(this::dbgString, DEBUGELAPSED);
-    final System.Logger  debugtimeout = Utils.getDebugLogger(this::dbgString, DEBUGTIMEOUT);
-    static final AtomicLong CLIENT_IDS = new AtomicLong();
-
-    // Define the default factory as a static inner class
-    // that embeds all the necessary logic to avoid
-    // the risk of using a lambda that might keep a reference on the
-    // HttpClient instance from which it was created (helps with
-    // heapdump analysis).
-    private static final class DefaultThreadFactory implements ThreadFactory {
-        private final String namePrefix;
-        private final AtomicInteger nextId = new AtomicInteger();
-
-        DefaultThreadFactory(long clientID) {
-            namePrefix = "HttpClient-" + clientID + "-Worker-";
-        }
-
-        @Override
-        public Thread newThread(Runnable r) {
-            String name = namePrefix + nextId.getAndIncrement();
-            Thread t;
-            if (System.getSecurityManager() == null) {
-                t = new Thread(null, r, name, 0, false);
-            } else {
-                t = InnocuousThread.newThread(name, r);
-            }
-            t.setDaemon(true);
-            return t;
-        }
-    }
-
-    private final CookieHandler cookieHandler;
-    private final Redirect followRedirects;
-    private final Optional<ProxySelector> userProxySelector;
-    private final ProxySelector proxySelector;
-    private final Authenticator authenticator;
-    private final Version version;
-    private final ConnectionPool connections;
-    private final Executor executor;
-    private final boolean isDefaultExecutor;
-    // Security parameters
-    private final SSLContext sslContext;
-    private final SSLParameters sslParams;
-    private final SelectorManager selmgr;
-    private final FilterFactory filters;
-    private final Http2ClientImpl client2;
-    private final long id;
-    private final String dbgTag;
-
-    // This reference is used to keep track of the facade HttpClient
-    // that was returned to the application code.
-    // It makes it possible to know when the application no longer
-    // holds any reference to the HttpClient.
-    // Unfortunately, this information is not enough to know when
-    // to exit the SelectorManager thread. Because of the asynchronous
-    // nature of the API, we also need to wait until all pending operations
-    // have completed.
-    private final WeakReference<HttpClientFacade> facadeRef;
-
-    // This counter keeps track of the number of operations pending
-    // on the HttpClient. The SelectorManager thread will wait
-    // until there are no longer any pending operations and the
-    // facadeRef is cleared before exiting.
-    //
-    // The pendingOperationCount is incremented every time a send/sendAsync
-    // operation is invoked on the HttpClient, and is decremented when
-    // the HttpResponse<T> object is returned to the user.
-    // However, at this point, the body may not have been fully read yet.
-    // This is the case when the response T is implemented as a streaming
-    // subscriber (such as an InputStream).
-    //
-    // To take care of this issue the pendingOperationCount will additionally
-    // be incremented/decremented in the following cases:
-    //
-    // 1. For HTTP/2  it is incremented when a stream is added to the
-    //    Http2Connection streams map, and decreased when the stream is removed
-    //    from the map. This should also take care of push promises.
-    // 2. For WebSocket the count is increased when creating a
-    //    DetachedConnectionChannel for the socket, and decreased
-    //    when the the channel is closed.
-    //    In addition, the HttpClient facade is passed to the WebSocket builder,
-    //    (instead of the client implementation delegate).
-    // 3. For HTTP/1.1 the count is incremented before starting to parse the body
-    //    response, and decremented when the parser has reached the end of the
-    //    response body flow.
-    //
-    // This should ensure that the selector manager thread remains alive until
-    // the response has been fully received or the web socket is closed.
-    private final AtomicLong pendingOperationCount = new AtomicLong();
-    private final AtomicLong pendingWebSocketCount = new AtomicLong();
-    private final AtomicLong pendingHttpRequestCount = new AtomicLong();
-
-    /** A Set of, deadline first, ordered timeout events. */
-    private final TreeSet<TimeoutEvent> timeouts;
-
-    /**
-     * This is a bit tricky:
-     * 1. an HttpClientFacade has a final HttpClientImpl field.
-     * 2. an HttpClientImpl has a final WeakReference<HttpClientFacade> field,
-     *    where the referent is the facade created for that instance.
-     * 3. We cannot just create the HttpClientFacade in the HttpClientImpl
-     *    constructor, because it would be only weakly referenced and could
-     *    be GC'ed before we can return it.
-     * The solution is to use an instance of SingleFacadeFactory which will
-     * allow the caller of new HttpClientImpl(...) to retrieve the facade
-     * after the HttpClientImpl has been created.
-     */
-    private static final class SingleFacadeFactory {
-        HttpClientFacade facade;
-        HttpClientFacade createFacade(HttpClientImpl impl) {
-            assert facade == null;
-            return (facade = new HttpClientFacade(impl));
-        }
-    }
-
-    static HttpClientFacade create(HttpClientBuilderImpl builder) {
-        SingleFacadeFactory facadeFactory = new SingleFacadeFactory();
-        HttpClientImpl impl = new HttpClientImpl(builder, facadeFactory);
-        impl.start();
-        assert facadeFactory.facade != null;
-        assert impl.facadeRef.get() == facadeFactory.facade;
-        return facadeFactory.facade;
-    }
-
-    private HttpClientImpl(HttpClientBuilderImpl builder,
-                           SingleFacadeFactory facadeFactory) {
-        id = CLIENT_IDS.incrementAndGet();
-        dbgTag = "HttpClientImpl(" + id +")";
-        if (builder.sslContext == null) {
-            try {
-                sslContext = SSLContext.getDefault();
-            } catch (NoSuchAlgorithmException ex) {
-                throw new InternalError(ex);
-            }
-        } else {
-            sslContext = builder.sslContext;
-        }
-        Executor ex = builder.executor;
-        if (ex == null) {
-            ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
-            isDefaultExecutor = true;
-        } else {
-            ex = builder.executor;
-            isDefaultExecutor = false;
-        }
-        facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
-        client2 = new Http2ClientImpl(this);
-        executor = ex;
-        cookieHandler = builder.cookieHandler;
-        followRedirects = builder.followRedirects == null ?
-                Redirect.NEVER : builder.followRedirects;
-        this.userProxySelector = Optional.ofNullable(builder.proxy);
-        this.proxySelector = userProxySelector
-                .orElseGet(HttpClientImpl::getDefaultProxySelector);
-        debug.log(Level.DEBUG, "proxySelector is %s (user-supplied=%s)",
-                this.proxySelector, userProxySelector.isPresent());
-        authenticator = builder.authenticator;
-        if (builder.version == null) {
-            version = HttpClient.Version.HTTP_2;
-        } else {
-            version = builder.version;
-        }
-        if (builder.sslParams == null) {
-            sslParams = getDefaultParams(sslContext);
-        } else {
-            sslParams = builder.sslParams;
-        }
-        connections = new ConnectionPool(id);
-        connections.start();
-        timeouts = new TreeSet<>();
-        try {
-            selmgr = new SelectorManager(this);
-        } catch (IOException e) {
-            // unlikely
-            throw new InternalError(e);
-        }
-        selmgr.setDaemon(true);
-        filters = new FilterFactory();
-        initFilters();
-        assert facadeRef.get() != null;
-    }
-
-    private void start() {
-        selmgr.start();
-    }
-
-    // Called from the SelectorManager thread, just before exiting.
-    // Clears the HTTP/1.1 and HTTP/2 cache, ensuring that the connections
-    // that may be still lingering there are properly closed (and their
-    // possibly still opened SocketChannel released).
-    private void stop() {
-        // Clears HTTP/1.1 cache and close its connections
-        connections.stop();
-        // Clears HTTP/2 cache and close its connections.
-        client2.stop();
-    }
-
-    private static SSLParameters getDefaultParams(SSLContext ctx) {
-        SSLParameters params = ctx.getSupportedSSLParameters();
-        params.setProtocols(new String[]{"TLSv1.2"});
-        return params;
-    }
-
-    private static ProxySelector getDefaultProxySelector() {
-        PrivilegedAction<ProxySelector> action = ProxySelector::getDefault;
-        return AccessController.doPrivileged(action);
-    }
-
-    // Returns the facade that was returned to the application code.
-    // May be null if that facade is no longer referenced.
-    final HttpClientFacade facade() {
-        return facadeRef.get();
-    }
-
-    // Increments the pendingOperationCount.
-    final long reference() {
-        pendingHttpRequestCount.incrementAndGet();
-        return pendingOperationCount.incrementAndGet();
-    }
-
-    // Decrements the pendingOperationCount.
-    final long unreference() {
-        final long count = pendingOperationCount.decrementAndGet();
-        final long httpCount = pendingHttpRequestCount.decrementAndGet();
-        final long webSocketCount = pendingWebSocketCount.get();
-        if (count == 0 && facade() == null) {
-            selmgr.wakeupSelector();
-        }
-        assert httpCount >= 0 : "count of HTTP operations < 0";
-        assert webSocketCount >= 0 : "count of WS operations < 0";
-        assert count >= 0 : "count of pending operations < 0";
-        return count;
-    }
-
-    // Increments the pendingOperationCount.
-    final long webSocketOpen() {
-        pendingWebSocketCount.incrementAndGet();
-        return pendingOperationCount.incrementAndGet();
-    }
-
-    // Decrements the pendingOperationCount.
-    final long webSocketClose() {
-        final long count = pendingOperationCount.decrementAndGet();
-        final long webSocketCount = pendingWebSocketCount.decrementAndGet();
-        final long httpCount = pendingHttpRequestCount.get();
-        if (count == 0 && facade() == null) {
-            selmgr.wakeupSelector();
-        }
-        assert httpCount >= 0 : "count of HTTP operations < 0";
-        assert webSocketCount >= 0 : "count of WS operations < 0";
-        assert count >= 0 : "count of pending operations < 0";
-        return count;
-    }
-
-    // Returns the pendingOperationCount.
-    final long referenceCount() {
-        return pendingOperationCount.get();
-    }
-
-    // Called by the SelectorManager thread to figure out whether it's time
-    // to terminate.
-    final boolean isReferenced() {
-        HttpClient facade = facade();
-        return facade != null || referenceCount() > 0;
-    }
-
-    /**
-     * Wait for activity on given exchange.
-     * The following occurs in the SelectorManager thread.
-     *
-     *  1) add to selector
-     *  2) If selector fires for this exchange then
-     *     call AsyncEvent.handle()
-     *
-     * If exchange needs to change interest ops, then call registerEvent() again.
-     */
-    void registerEvent(AsyncEvent exchange) throws IOException {
-        selmgr.register(exchange);
-    }
-
-    /**
-     * Only used from RawChannel to disconnect the channel from
-     * the selector
-     */
-    void cancelRegistration(SocketChannel s) {
-        selmgr.cancel(s);
-    }
-
-    /**
-     * Allows an AsyncEvent to modify its interestOps.
-     * @param event The modified event.
-     */
-    void eventUpdated(AsyncEvent event) throws ClosedChannelException {
-        assert !(event instanceof AsyncTriggerEvent);
-        selmgr.eventUpdated(event);
-    }
-
-    boolean isSelectorThread() {
-        return Thread.currentThread() == selmgr;
-    }
-
-    Http2ClientImpl client2() {
-        return client2;
-    }
-
-    private void debugCompleted(String tag, long startNanos, HttpRequest req) {
-        if (debugelapsed.isLoggable(Level.DEBUG)) {
-            debugelapsed.log(Level.DEBUG, () -> tag + " elapsed "
-                    + (System.nanoTime() - startNanos)/1000_000L
-                    + " millis for " + req.method()
-                    + " to " + req.uri());
-        }
-    }
-
-    @Override
-    public <T> HttpResponse<T>
-    send(HttpRequest req, BodyHandler<T> responseHandler)
-        throws IOException, InterruptedException
-    {
-        try {
-            return sendAsync(req, responseHandler).get();
-        } catch (ExecutionException e) {
-            Throwable t = e.getCause();
-            if (t instanceof Error)
-                throw (Error)t;
-            if (t instanceof RuntimeException)
-                throw (RuntimeException)t;
-            else if (t instanceof IOException)
-                throw Utils.getIOException(t);
-            else
-                throw new InternalError("Unexpected exception", t);
-        }
-    }
-
-    @Override
-    public <T> CompletableFuture<HttpResponse<T>>
-    sendAsync(HttpRequest userRequest, BodyHandler<T> responseHandler)
-    {
-        return sendAsync(userRequest, responseHandler, null);
-    }
-
-
-    @Override
-    public <T> CompletableFuture<HttpResponse<T>>
-    sendAsync(HttpRequest userRequest,
-              BodyHandler<T> responseHandler,
-              PushPromiseHandler<T> pushPromiseHandler)
-    {
-        AccessControlContext acc = null;
-        if (System.getSecurityManager() != null)
-            acc = AccessController.getContext();
-
-        // Clone the, possibly untrusted, HttpRequest
-        HttpRequestImpl requestImpl = new HttpRequestImpl(userRequest, proxySelector, acc);
-        if (requestImpl.method().equals("CONNECT"))
-            throw new IllegalArgumentException("Unsupported method CONNECT");
-
-        long start = DEBUGELAPSED ? System.nanoTime() : 0;
-        reference();
-        try {
-            debugelapsed.log(Level.DEBUG, "ClientImpl (async) send %s", userRequest);
-
-            MultiExchange<T> mex = new MultiExchange<>(userRequest,
-                                                            requestImpl,
-                                                            this,
-                                                            responseHandler,
-                                                            pushPromiseHandler,
-                                                            acc);
-            CompletableFuture<HttpResponse<T>> res =
-                    mex.responseAsync().whenComplete((b,t) -> unreference());
-            if (DEBUGELAPSED) {
-                res = res.whenComplete(
-                        (b,t) -> debugCompleted("ClientImpl (async)", start, userRequest));
-            }
-            // makes sure that any dependent actions happen in the executor
-            if (acc != null) {
-                res.whenCompleteAsync((r, t) -> { /* do nothing */},
-                                      new PrivilegedExecutor(executor, acc));
-            }
-
-            return res;
-        } catch(Throwable t) {
-            unreference();
-            debugCompleted("ClientImpl (async)", start, userRequest);
-            throw t;
-        }
-    }
-
-    // Main loop for this client's selector
-    private final static class SelectorManager extends Thread {
-
-        // For testing purposes we have an internal System property that
-        // can control the frequency at which the selector manager will wake
-        // up when there are no pending operations.
-        // Increasing the frequency (shorter delays) might allow the selector
-        // to observe that the facade is no longer referenced and might allow
-        // the selector thread to terminate more timely - for when nothing is
-        // ongoing it will only check for that condition every NODEADLINE ms.
-        // To avoid misuse of the property, the delay that can be specified
-        // is comprised between [MIN_NODEADLINE, MAX_NODEADLINE], and its default
-        // value if unspecified (or <= 0) is DEF_NODEADLINE = 3000ms
-        // The property is -Djdk.httpclient.internal.selector.timeout=<millis>
-        private static final int MIN_NODEADLINE = 1000; // ms
-        private static final int MAX_NODEADLINE = 1000 * 1200; // ms
-        private static final int DEF_NODEADLINE = 3000; // ms
-        private static final long NODEADLINE; // default is DEF_NODEADLINE ms
-        static {
-            // ensure NODEADLINE is initialized with some valid value.
-            long deadline =  Utils.getIntegerNetProperty(
-                "jdk.httpclient.internal.selector.timeout",
-                DEF_NODEADLINE); // millis
-            if (deadline <= 0) deadline = DEF_NODEADLINE;
-            deadline = Math.max(deadline, MIN_NODEADLINE);
-            NODEADLINE = Math.min(deadline, MAX_NODEADLINE);
-        }
-
-        private final Selector selector;
-        private volatile boolean closed;
-        private final List<AsyncEvent> registrations;
-        private final System.Logger debug;
-        private final System.Logger debugtimeout;
-        HttpClientImpl owner;
-        ConnectionPool pool;
-
-        SelectorManager(HttpClientImpl ref) throws IOException {
-            super(null, null, "HttpClient-" + ref.id + "-SelectorManager", 0, false);
-            owner = ref;
-            debug = ref.debug;
-            debugtimeout = ref.debugtimeout;
-            pool = ref.connectionPool();
-            registrations = new ArrayList<>();
-            selector = Selector.open();
-        }
-
-        void eventUpdated(AsyncEvent e) throws ClosedChannelException {
-            if (Thread.currentThread() == this) {
-                SelectionKey key = e.channel().keyFor(selector);
-                SelectorAttachment sa = (SelectorAttachment) key.attachment();
-                if (sa != null) sa.register(e);
-            } else {
-                register(e);
-            }
-        }
-
-        // This returns immediately. So caller not allowed to send/receive
-        // on connection.
-        synchronized void register(AsyncEvent e) {
-            registrations.add(e);
-            selector.wakeup();
-        }
-
-        synchronized void cancel(SocketChannel e) {
-            SelectionKey key = e.keyFor(selector);
-            if (key != null) {
-                key.cancel();
-            }
-            selector.wakeup();
-        }
-
-        void wakeupSelector() {
-            selector.wakeup();
-        }
-
-        synchronized void shutdown() {
-            debug.log(Level.DEBUG, "SelectorManager shutting down");
-            closed = true;
-            try {
-                selector.close();
-            } catch (IOException ignored) {
-            } finally {
-                owner.stop();
-            }
-        }
-
-        @Override
-        public void run() {
-            List<Pair<AsyncEvent,IOException>> errorList = new ArrayList<>();
-            List<AsyncEvent> readyList = new ArrayList<>();
-            try {
-                while (!Thread.currentThread().isInterrupted()) {
-                    synchronized (this) {
-                        assert errorList.isEmpty();
-                        assert readyList.isEmpty();
-                        for (AsyncEvent event : registrations) {
-                            if (event instanceof AsyncTriggerEvent) {
-                                readyList.add(event);
-                                continue;
-                            }
-                            SelectableChannel chan = event.channel();
-                            SelectionKey key = null;
-                            try {
-                                key = chan.keyFor(selector);
-                                SelectorAttachment sa;
-                                if (key == null || !key.isValid()) {
-                                    if (key != null) {
-                                        // key is canceled.
-                                        // invoke selectNow() to purge it
-                                        // before registering the new event.
-                                        selector.selectNow();
-                                    }
-                                    sa = new SelectorAttachment(chan, selector);
-                                } else {
-                                    sa = (SelectorAttachment) key.attachment();
-                                }
-                                // may throw IOE if channel closed: that's OK
-                                sa.register(event);
-                                if (!chan.isOpen()) {
-                                    throw new IOException("Channel closed");
-                                }
-                            } catch (IOException e) {
-                                Log.logTrace("HttpClientImpl: " + e);
-                                debug.log(Level.DEBUG, () ->
-                                        "Got " + e.getClass().getName()
-                                                 + " while handling"
-                                                 + " registration events");
-                                chan.close();
-                                // let the event abort deal with it
-                                errorList.add(new Pair<>(event, e));
-                                if (key != null) {
-                                    key.cancel();
-                                    selector.selectNow();
-                                }
-                            }
-                        }
-                        registrations.clear();
-                        selector.selectedKeys().clear();
-                    }
-
-                    for (AsyncEvent event : readyList) {
-                        assert event instanceof AsyncTriggerEvent;
-                        event.handle();
-                    }
-                    readyList.clear();
-
-                    for (Pair<AsyncEvent,IOException> error : errorList) {
-                        // an IOException was raised and the channel closed.
-                        handleEvent(error.first, error.second);
-                    }
-                    errorList.clear();
-
-                    // Check whether client is still alive, and if not,
-                    // gracefully stop this thread
-                    if (!owner.isReferenced()) {
-                        Log.logTrace("HttpClient no longer referenced. Exiting...");
-                        return;
-                    }
-
-                    // Timeouts will have milliseconds granularity. It is important
-                    // to handle them in a timely fashion.
-                    long nextTimeout = owner.purgeTimeoutsAndReturnNextDeadline();
-                    debugtimeout.log(Level.DEBUG, "next timeout: %d", nextTimeout);
-
-                    // Keep-alive have seconds granularity. It's not really an
-                    // issue if we keep connections linger a bit more in the keep
-                    // alive cache.
-                    long nextExpiry = pool.purgeExpiredConnectionsAndReturnNextDeadline();
-                    debugtimeout.log(Level.DEBUG, "next expired: %d", nextExpiry);
-
-                    assert nextTimeout >= 0;
-                    assert nextExpiry >= 0;
-
-                    // Don't wait for ever as it might prevent the thread to
-                    // stop gracefully. millis will be 0 if no deadline was found.
-                    if (nextTimeout <= 0) nextTimeout = NODEADLINE;
-
-                    // Clip nextExpiry at NODEADLINE limit. The default
-                    // keep alive is 1200 seconds (half an hour) - we don't
-                    // want to wait that long.
-                    if (nextExpiry <= 0) nextExpiry = NODEADLINE;
-                    else nextExpiry = Math.min(NODEADLINE, nextExpiry);
-
-                    // takes the least of the two.
-                    long millis = Math.min(nextExpiry, nextTimeout);
-
-                    debugtimeout.log(Level.DEBUG, "Next deadline is %d",
-                                     (millis == 0 ? NODEADLINE : millis));
-                    //debugPrint(selector);
-                    int n = selector.select(millis == 0 ? NODEADLINE : millis);
-                    if (n == 0) {
-                        // Check whether client is still alive, and if not,
-                        // gracefully stop this thread
-                        if (!owner.isReferenced()) {
-                            Log.logTrace("HttpClient no longer referenced. Exiting...");
-                            return;
-                        }
-                        owner.purgeTimeoutsAndReturnNextDeadline();
-                        continue;
-                    }
-                    Set<SelectionKey> keys = selector.selectedKeys();
-
-                    assert errorList.isEmpty();
-                    for (SelectionKey key : keys) {
-                        SelectorAttachment sa = (SelectorAttachment) key.attachment();
-                        if (!key.isValid()) {
-                            IOException ex = sa.chan.isOpen()
-                                    ? new IOException("Invalid key")
-                                    : new ClosedChannelException();
-                            sa.pending.forEach(e -> errorList.add(new Pair<>(e,ex)));
-                            sa.pending.clear();
-                            continue;
-                        }
-
-                        int eventsOccurred;
-                        try {
-                            eventsOccurred = key.readyOps();
-                        } catch (CancelledKeyException ex) {
-                            IOException io = Utils.getIOException(ex);
-                            sa.pending.forEach(e -> errorList.add(new Pair<>(e,io)));
-                            sa.pending.clear();
-                            continue;
-                        }
-                        sa.events(eventsOccurred).forEach(readyList::add);
-                        sa.resetInterestOps(eventsOccurred);
-                    }
-                    selector.selectNow(); // complete cancellation
-                    selector.selectedKeys().clear();
-
-                    for (AsyncEvent event : readyList) {
-                        handleEvent(event, null); // will be delegated to executor
-                    }
-                    readyList.clear();
-                    errorList.forEach((p) -> handleEvent(p.first, p.second));
-                    errorList.clear();
-                }
-            } catch (Throwable e) {
-                //e.printStackTrace();
-                if (!closed) {
-                    // This terminates thread. So, better just print stack trace
-                    String err = Utils.stackTrace(e);
-                    Log.logError("HttpClientImpl: fatal error: " + err);
-                }
-                debug.log(Level.DEBUG, "shutting down", e);
-                if (Utils.ASSERTIONSENABLED && !debug.isLoggable(Level.DEBUG)) {
-                    e.printStackTrace(System.err); // always print the stack
-                }
-            } finally {
-                shutdown();
-            }
-        }
-
-//        void debugPrint(Selector selector) {
-//            System.err.println("Selector: debugprint start");
-//            Set<SelectionKey> keys = selector.keys();
-//            for (SelectionKey key : keys) {
-//                SelectableChannel c = key.channel();
-//                int ops = key.interestOps();
-//                System.err.printf("selector chan:%s ops:%d\n", c, ops);
-//            }
-//            System.err.println("Selector: debugprint end");
-//        }
-
-        /** Handles the given event. The given ioe may be null. */
-        void handleEvent(AsyncEvent event, IOException ioe) {
-            if (closed || ioe != null) {
-                event.abort(ioe);
-            } else {
-                event.handle();
-            }
-        }
-    }
-
-    /**
-     * Tracks multiple user level registrations associated with one NIO
-     * registration (SelectionKey). In this implementation, registrations
-     * are one-off and when an event is posted the registration is cancelled
-     * until explicitly registered again.
-     *
-     * <p> No external synchronization required as this class is only used
-     * by the SelectorManager thread. One of these objects required per
-     * connection.
-     */
-    private static class SelectorAttachment {
-        private final SelectableChannel chan;
-        private final Selector selector;
-        private final Set<AsyncEvent> pending;
-        private final static System.Logger debug =
-                Utils.getDebugLogger("SelectorAttachment"::toString, DEBUG);
-        private int interestOps;
-
-        SelectorAttachment(SelectableChannel chan, Selector selector) {
-            this.pending = new HashSet<>();
-            this.chan = chan;
-            this.selector = selector;
-        }
-
-        void register(AsyncEvent e) throws ClosedChannelException {
-            int newOps = e.interestOps();
-            boolean reRegister = (interestOps & newOps) != newOps;
-            interestOps |= newOps;
-            pending.add(e);
-            if (reRegister) {
-                // first time registration happens here also
-                chan.register(selector, interestOps, this);
-            }
-        }
-
-        /**
-         * Returns a Stream<AsyncEvents> containing only events that are
-         * registered with the given {@code interestOps}.
-         */
-        Stream<AsyncEvent> events(int interestOps) {
-            return pending.stream()
-                    .filter(ev -> (ev.interestOps() & interestOps) != 0);
-        }
-
-        /**
-         * Removes any events with the given {@code interestOps}, and if no
-         * events remaining, cancels the associated SelectionKey.
-         */
-        void resetInterestOps(int interestOps) {
-            int newOps = 0;
-
-            Iterator<AsyncEvent> itr = pending.iterator();
-            while (itr.hasNext()) {
-                AsyncEvent event = itr.next();
-                int evops = event.interestOps();
-                if (event.repeating()) {
-                    newOps |= evops;
-                    continue;
-                }
-                if ((evops & interestOps) != 0) {
-                    itr.remove();
-                } else {
-                    newOps |= evops;
-                }
-            }
-
-            this.interestOps = newOps;
-            SelectionKey key = chan.keyFor(selector);
-            if (newOps == 0 && pending.isEmpty()) {
-                key.cancel();
-            } else {
-                try {
-                    key.interestOps(newOps);
-                } catch (CancelledKeyException x) {
-                    // channel may have been closed
-                    debug.log(Level.DEBUG, "key cancelled for " + chan);
-                    abortPending(x);
-                }
-            }
-        }
-
-        void abortPending(Throwable x) {
-            if (!pending.isEmpty()) {
-                AsyncEvent[] evts = pending.toArray(new AsyncEvent[0]);
-                pending.clear();
-                IOException io = Utils.getIOException(x);
-                for (AsyncEvent event : evts) {
-                    event.abort(io);
-                }
-            }
-        }
-    }
-
-    /*package-private*/ SSLContext theSSLContext() {
-        return sslContext;
-    }
-
-    @Override
-    public SSLContext sslContext() {
-        return sslContext;
-    }
-
-    @Override
-    public SSLParameters sslParameters() {
-        return Utils.copySSLParameters(sslParams);
-    }
-
-    @Override
-    public Optional<Authenticator> authenticator() {
-        return Optional.ofNullable(authenticator);
-    }
-
-    /*package-private*/ final Executor theExecutor() {
-        return executor;
-    }
-
-    @Override
-    public final Optional<Executor> executor() {
-        return isDefaultExecutor ? Optional.empty() : Optional.of(executor);
-    }
-
-    ConnectionPool connectionPool() {
-        return connections;
-    }
-
-    @Override
-    public Redirect followRedirects() {
-        return followRedirects;
-    }
-
-
-    @Override
-    public Optional<CookieHandler> cookieHandler() {
-        return Optional.ofNullable(cookieHandler);
-    }
-
-    @Override
-    public Optional<ProxySelector> proxy() {
-        return this.userProxySelector;
-    }
-
-    // Return the effective proxy that this client uses.
-    ProxySelector proxySelector() {
-        return proxySelector;
-    }
-
-    @Override
-    public WebSocket.Builder newWebSocketBuilder() {
-        // Make sure to pass the HttpClientFacade to the WebSocket builder.
-        // This will ensure that the facade is not released before the
-        // WebSocket has been created, at which point the pendingOperationCount
-        // will have been incremented by the DetachedConnectionChannel
-        // (see PlainHttpConnection.detachChannel())
-        return new BuilderImpl(this.facade(), proxySelector);
-    }
-
-    @Override
-    public Version version() {
-        return version;
-    }
-
-    String dbgString() {
-        return dbgTag;
-    }
-
-    @Override
-    public String toString() {
-        // Used by tests to get the client's id and compute the
-        // name of the SelectorManager thread.
-        return super.toString() + ("(" + id + ")");
-    }
-
-    private void initFilters() {
-        addFilter(AuthenticationFilter.class);
-        addFilter(RedirectFilter.class);
-        if (this.cookieHandler != null) {
-            addFilter(CookieFilter.class);
-        }
-    }
-
-    private 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) {
-        Log.logTrace("Registering timer {0}", event);
-        timeouts.add(event);
-        selmgr.wakeupSelector();
-    }
-
-    synchronized void cancelTimer(TimeoutEvent event) {
-        Log.logTrace("Canceling timer {0}", event);
-        timeouts.remove(event);
-    }
-
-    /**
-     * Purges ( handles ) timer events that have passed their deadline, and
-     * returns the amount of time, in milliseconds, until the next earliest
-     * event. A return value of 0 means that there are no events.
-     */
-    private long purgeTimeoutsAndReturnNextDeadline() {
-        long diff = 0L;
-        List<TimeoutEvent> toHandle = null;
-        int remaining = 0;
-        // enter critical section to retrieve the timeout event to handle
-        synchronized(this) {
-            if (timeouts.isEmpty()) return 0L;
-
-            Instant now = Instant.now();
-            Iterator<TimeoutEvent> itr = timeouts.iterator();
-            while (itr.hasNext()) {
-                TimeoutEvent event = itr.next();
-                diff = now.until(event.deadline(), ChronoUnit.MILLIS);
-                if (diff <= 0) {
-                    itr.remove();
-                    toHandle = (toHandle == null) ? new ArrayList<>() : toHandle;
-                    toHandle.add(event);
-                } else {
-                    break;
-                }
-            }
-            remaining = timeouts.size();
-        }
-
-        // can be useful for debugging
-        if (toHandle != null && Log.trace()) {
-            Log.logTrace("purgeTimeoutsAndReturnNextDeadline: handling "
-                    +  toHandle.size() + " events, "
-                    + "remaining " + remaining
-                    + ", next deadline: " + (diff < 0 ? 0L : diff));
-        }
-
-        // handle timeout events out of critical section
-        if (toHandle != null) {
-            Throwable failed = null;
-            for (TimeoutEvent event : toHandle) {
-                try {
-                   Log.logTrace("Firing timer {0}", event);
-                   event.handle();
-                } catch (Error | RuntimeException e) {
-                    // Not expected. Handle remaining events then throw...
-                    // If e is an OOME or SOE it might simply trigger a new
-                    // error from here - but in this case there's not much we
-                    // could do anyway. Just let it flow...
-                    if (failed == null) failed = e;
-                    else failed.addSuppressed(e);
-                    Log.logTrace("Failed to handle event {0}: {1}", event, e);
-                }
-            }
-            if (failed instanceof Error) throw (Error) failed;
-            if (failed instanceof RuntimeException) throw (RuntimeException) failed;
-        }
-
-        // return time to wait until next event. 0L if there's no more events.
-        return diff < 0 ? 0L : diff;
-    }
-
-    // used for the connection window
-    int getReceiveBufferSize() {
-        return Utils.getIntegerNetProperty(
-                "jdk.httpclient.receiveBufferSize", 2 * 1024 * 1024
-        );
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,491 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Flow;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import jdk.incubator.http.HttpClient.Version;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
-
-/**
- * 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
- *      AsyncSSLConnection: TLS channel direct to server
- *      AsyncSSLTunnelConnection: TLS channel via (CONNECT) proxy tunnel
- */
-abstract class HttpConnection implements Closeable {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    final static System.Logger DEBUG_LOGGER = Utils.getDebugLogger(
-            () -> "HttpConnection(SocketTube(?))", DEBUG);
-
-    /** The address this connection is connected to. Could be a server or a proxy. */
-    final InetSocketAddress address;
-    private final HttpClientImpl client;
-    private final TrailingOperations trailingOperations;
-
-    HttpConnection(InetSocketAddress address, HttpClientImpl client) {
-        this.address = address;
-        this.client = client;
-        trailingOperations = new TrailingOperations();
-    }
-
-    private static final class TrailingOperations {
-        private final Map<CompletionStage<?>, Boolean> operations =
-                new IdentityHashMap<>();
-        void add(CompletionStage<?> cf) {
-            synchronized(operations) {
-                cf.whenComplete((r,t)-> remove(cf));
-                operations.put(cf, Boolean.TRUE);
-            }
-        }
-        boolean remove(CompletionStage<?> cf) {
-            synchronized(operations) {
-                return operations.remove(cf);
-            }
-        }
-    }
-
-    final void addTrailingOperation(CompletionStage<?> cf) {
-        trailingOperations.add(cf);
-    }
-
-//    final void removeTrailingOperation(CompletableFuture<?> cf) {
-//        trailingOperations.remove(cf);
-//    }
-
-    final HttpClientImpl client() {
-        return client;
-    }
-
-    //public abstract void connect() throws IOException, InterruptedException;
-
-    public abstract CompletableFuture<Void> connectAsync();
-
-    /** Tells whether, or not, this connection is connected to its destination. */
-    abstract boolean connected();
-
-    /** Tells whether, or not, this connection is secure ( over SSL ) */
-    abstract boolean isSecure();
-
-    /** Tells whether, or not, this connection is proxied. */
-    abstract boolean isProxied();
-
-    /** Tells whether, or not, this connection is open. */
-    final boolean isOpen() {
-        return channel().isOpen() &&
-                (connected() ? !getConnectionFlow().isFinished() : true);
-    }
-
-    interface HttpPublisher extends FlowTube.TubePublisher {
-        void enqueue(List<ByteBuffer> buffers) throws IOException;
-        void enqueueUnordered(List<ByteBuffer> buffers) throws IOException;
-        void signalEnqueued() throws IOException;
-    }
-
-    /**
-     * Returns the HTTP publisher associated with this connection.  May be null
-     * if invoked before connecting.
-     */
-    abstract HttpPublisher publisher();
-
-    // HTTP/2 MUST use TLS version 1.2 or higher for HTTP/2 over TLS
-    private static final Predicate<String> testRequiredHTTP2TLSVersion = proto ->
-            proto.equals("TLSv1.2") || proto.equals("TLSv1.3");
-
-   /**
-    * Returns true if the given client's SSL parameter protocols contains at
-    * least one TLS version that HTTP/2 requires.
-    */
-   private static final boolean hasRequiredHTTP2TLSVersion(HttpClient client) {
-       String[] protos = client.sslParameters().getProtocols();
-       if (protos != null) {
-           return Arrays.stream(protos).filter(testRequiredHTTP2TLSVersion).findAny().isPresent();
-       } else {
-           return false;
-       }
-   }
-
-    /**
-     * Factory for retrieving HttpConnections. A connection can be retrieved
-     * from the connection pool, or a new one created if none available.
-     *
-     * The given {@code addr} is the ultimate destination. Any proxies,
-     * etc, are determined from the request. Returns a concrete instance which
-     * is one of the following:
-     *      {@link PlainHttpConnection}
-     *      {@link PlainTunnelingConnection}
-     *
-     * The returned connection, if not from the connection pool, must have its,
-     * connect() or connectAsync() method invoked, which ( when it completes
-     * successfully ) renders the connection usable for requests.
-     */
-    public static HttpConnection getConnection(InetSocketAddress addr,
-                                               HttpClientImpl client,
-                                               HttpRequestImpl request,
-                                               Version version) {
-        HttpConnection c = null;
-        InetSocketAddress proxy = request.proxy();
-        if (proxy != null && proxy.isUnresolved()) {
-            // The default proxy selector may select a proxy whose  address is
-            // unresolved. We must resolve the address before connecting to it.
-            proxy = new InetSocketAddress(proxy.getHostString(), proxy.getPort());
-        }
-        boolean secure = request.secure();
-        ConnectionPool pool = client.connectionPool();
-
-        if (!secure) {
-            c = pool.getConnection(false, addr, proxy);
-            if (c != null && c.isOpen() /* may have been eof/closed when in the pool */) {
-                final HttpConnection conn = c;
-                DEBUG_LOGGER.log(Level.DEBUG, () -> conn.getConnectionFlow()
-                            + ": plain connection retrieved from HTTP/1.1 pool");
-                return c;
-            } else {
-                return getPlainConnection(addr, proxy, request, client);
-            }
-        } else {  // secure
-            if (version != HTTP_2) { // only HTTP/1.1 connections are in the pool
-                c = pool.getConnection(true, addr, proxy);
-            }
-            if (c != null && c.isOpen()) {
-                final HttpConnection conn = c;
-                DEBUG_LOGGER.log(Level.DEBUG, () -> conn.getConnectionFlow()
-                            + ": SSL connection retrieved from HTTP/1.1 pool");
-                return c;
-            } else {
-                String[] alpn = null;
-                if (version == HTTP_2 && hasRequiredHTTP2TLSVersion(client)) {
-                    alpn = new String[] { "h2", "http/1.1" };
-                }
-                return getSSLConnection(addr, proxy, alpn, request, client);
-            }
-        }
-    }
-
-    private static HttpConnection getSSLConnection(InetSocketAddress addr,
-                                                   InetSocketAddress proxy,
-                                                   String[] alpn,
-                                                   HttpRequestImpl request,
-                                                   HttpClientImpl client) {
-        if (proxy != null)
-            return new AsyncSSLTunnelConnection(addr, client, alpn, proxy,
-                                                proxyTunnelHeaders(request));
-        else
-            return new AsyncSSLConnection(addr, client, alpn);
-    }
-
-    /**
-     * This method is used to build a filter that will accept or
-     * veto (header-name, value) tuple for transmission on the
-     * wire.
-     * The filter is applied to the headers when sending the headers
-     * to the remote party.
-     * Which tuple is accepted/vetoed depends on:
-     * <pre>
-     *    - whether the connection is a tunnel connection
-     *      [talking to a server through a proxy tunnel]
-     *    - whether the method is CONNECT
-     *      [establishing a CONNECT tunnel through a proxy]
-     *    - whether the request is using a proxy
-     *      (and the connection is not a tunnel)
-     *      [talking to a server through a proxy]
-     *    - whether the request is a direct connection to
-     *      a server (no tunnel, no proxy).
-     * </pre>
-     * @param request
-     * @return
-     */
-    BiPredicate<String,List<String>> headerFilter(HttpRequestImpl request) {
-        if (isTunnel()) {
-            // talking to a server through a proxy tunnel
-            // don't send proxy-* headers to a plain server
-            assert !request.isConnect();
-            return Utils.NO_PROXY_HEADERS_FILTER;
-        } else if (request.isConnect()) {
-            // establishing a proxy tunnel
-            // check for proxy tunnel disabled schemes
-            // assert !this.isTunnel();
-            assert request.proxy() == null;
-            return Utils.PROXY_TUNNEL_FILTER;
-        } else if (request.proxy() != null) {
-            // talking to a server through a proxy (no tunnel)
-            // check for proxy disabled schemes
-            // assert !isTunnel() && !request.isConnect();
-            return Utils.PROXY_FILTER;
-        } else {
-            // talking to a server directly (no tunnel, no proxy)
-            // don't send proxy-* headers to a plain server
-            // assert request.proxy() == null && !request.isConnect();
-            return Utils.NO_PROXY_HEADERS_FILTER;
-        }
-    }
-
-    // Composes a new immutable HttpHeaders that combines the
-    // user and system header but only keeps those headers that
-    // start with "proxy-"
-    private static HttpHeaders proxyTunnelHeaders(HttpRequestImpl request) {
-        Map<String, List<String>> combined = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        combined.putAll(request.getSystemHeaders().map());
-        combined.putAll(request.headers().map()); // let user override system
-
-        // keep only proxy-* - and also strip authorization headers
-        // for disabled schemes
-        return ImmutableHeaders.of(combined, Utils.PROXY_TUNNEL_FILTER);
-    }
-
-    /* Returns either a plain HTTP connection or a plain tunnelling connection
-     * for proxied WebSocket */
-    private static HttpConnection getPlainConnection(InetSocketAddress addr,
-                                                     InetSocketAddress proxy,
-                                                     HttpRequestImpl request,
-                                                     HttpClientImpl client) {
-        if (request.isWebSocket() && proxy != null)
-            return new PlainTunnelingConnection(addr, proxy, client,
-                                                proxyTunnelHeaders(request));
-
-        if (proxy == null)
-            return new PlainHttpConnection(addr, client);
-        else
-            return new PlainProxyConnection(proxy, client);
-    }
-
-    void closeOrReturnToCache(HttpHeaders hdrs) {
-        if (hdrs == null) {
-            // the connection was closed by server, eof
-            close();
-            return;
-        }
-        if (!isOpen()) {
-            return;
-        }
-        HttpClientImpl client = client();
-        if (client == null) {
-            close();
-            return;
-        }
-        ConnectionPool pool = client.connectionPool();
-        boolean keepAlive = hdrs.firstValue("Connection")
-                .map((s) -> !s.equalsIgnoreCase("close"))
-                .orElse(true);
-
-        if (keepAlive) {
-            Log.logTrace("Returning connection to the pool: {0}", this);
-            pool.returnToPool(this);
-        } else {
-            close();
-        }
-    }
-
-    /* Tells whether or not this connection is a tunnel through a proxy */
-    boolean isTunnel() { return false; }
-
-    abstract SocketChannel channel();
-
-    final InetSocketAddress address() {
-        return address;
-    }
-
-    abstract ConnectionPool.CacheKey cacheKey();
-
-    /**
-     * Closes this connection, by returning the socket to its connection pool.
-     */
-    @Override
-    public abstract void close();
-
-    abstract void shutdownInput() throws IOException;
-
-    abstract void shutdownOutput() throws IOException;
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    abstract static class DetachedConnectionChannel implements Closeable {
-        DetachedConnectionChannel() {}
-        abstract SocketChannel channel();
-        abstract long write(ByteBuffer[] buffers, int start, int number)
-                throws IOException;
-        abstract void shutdownInput() throws IOException;
-        abstract void shutdownOutput() throws IOException;
-        abstract ByteBuffer read() throws IOException;
-        @Override
-        public abstract void close();
-        @Override
-        public String toString() {
-            return this.getClass().getSimpleName() + ": " + channel().toString();
-        }
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    abstract DetachedConnectionChannel detachChannel();
-
-    abstract FlowTube getConnectionFlow();
-
-    /**
-     * A publisher that makes it possible to publish (write)
-     * ordered (normal priority) and unordered (high priority)
-     * buffers downstream.
-     */
-    final class PlainHttpPublisher implements HttpPublisher {
-        final Object reading;
-        PlainHttpPublisher() {
-            this(new Object());
-        }
-        PlainHttpPublisher(Object readingLock) {
-            this.reading = readingLock;
-        }
-        final ConcurrentLinkedDeque<List<ByteBuffer>> queue = new ConcurrentLinkedDeque<>();
-        volatile Flow.Subscriber<? super List<ByteBuffer>> subscriber;
-        volatile HttpWriteSubscription subscription;
-        final SequentialScheduler writeScheduler =
-                    new SequentialScheduler(this::flushTask);
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            synchronized (reading) {
-                //assert this.subscription == null;
-                //assert this.subscriber == null;
-                if (subscription == null) {
-                    subscription = new HttpWriteSubscription();
-                }
-                this.subscriber = subscriber;
-            }
-            // TODO: should we do this in the flow?
-            subscriber.onSubscribe(subscription);
-            signal();
-        }
-
-        void flushTask(DeferredCompleter completer) {
-            try {
-                HttpWriteSubscription sub = subscription;
-                if (sub != null) sub.flush();
-            } finally {
-                completer.complete();
-            }
-        }
-
-        void signal() {
-            writeScheduler.runOrSchedule();
-        }
-
-        final class HttpWriteSubscription implements Flow.Subscription {
-            final Demand demand = new Demand();
-
-            @Override
-            public void request(long n) {
-                if (n <= 0) throw new IllegalArgumentException("non-positive request");
-                demand.increase(n);
-                debug.log(Level.DEBUG, () -> "HttpPublisher: got request of "
-                            + n + " from "
-                            + getConnectionFlow());
-                writeScheduler.runOrSchedule();
-            }
-
-            @Override
-            public void cancel() {
-                debug.log(Level.DEBUG, () -> "HttpPublisher: cancelled by "
-                          + getConnectionFlow());
-            }
-
-            void flush() {
-                while (!queue.isEmpty() && demand.tryDecrement()) {
-                    List<ByteBuffer> elem = queue.poll();
-                    debug.log(Level.DEBUG, () -> "HttpPublisher: sending "
-                                + Utils.remaining(elem) + " bytes ("
-                                + elem.size() + " buffers) to "
-                                + getConnectionFlow());
-                    subscriber.onNext(elem);
-                }
-            }
-        }
-
-        @Override
-        public void enqueue(List<ByteBuffer> buffers) throws IOException {
-            queue.add(buffers);
-            int bytes = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
-            debug.log(Level.DEBUG, "added %d bytes to the write queue", bytes);
-        }
-
-        @Override
-        public void enqueueUnordered(List<ByteBuffer> buffers) throws IOException {
-            // Unordered frames are sent before existing frames.
-            int bytes = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
-            queue.addFirst(buffers);
-            debug.log(Level.DEBUG, "inserted %d bytes in the write queue", bytes);
-        }
-
-        @Override
-        public void signalEnqueued() throws IOException {
-            debug.log(Level.DEBUG, "signalling the publisher of the write queue");
-            signal();
-        }
-    }
-
-    String dbgTag = null;
-    final String dbgString() {
-        FlowTube flow = getConnectionFlow();
-        String tag = dbgTag;
-        if (tag == null && flow != null) {
-            dbgTag = tag = this.getClass().getSimpleName() + "(" + flow + ")";
-        } else if (tag == null) {
-            tag = this.getClass().getSimpleName() + "(?)";
-        }
-        return tag;
-    }
-
-    @Override
-    public String toString() {
-        return "HttpConnection: " + channel().toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java	Tue Feb 06 11:39:55 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java	Tue Feb 06 14:10:28 2018 +0000
@@ -44,6 +44,8 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Flow;
 import java.util.function.Supplier;
+import jdk.incubator.http.internal.HttpRequestBuilderImpl;
+import jdk.incubator.http.internal.RequestPublishers;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.URI;
-import java.time.Duration;
-import java.util.Optional;
-import jdk.incubator.http.HttpRequest.BodyPublisher;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.common.Utils;
-
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static jdk.incubator.http.internal.common.Utils.isValidName;
-import static jdk.incubator.http.internal.common.Utils.isValidValue;
-
-class HttpRequestBuilderImpl extends HttpRequest.Builder {
-
-    private HttpHeadersImpl userHeaders;
-    private URI uri;
-    private String method;
-    private boolean expectContinue;
-    private BodyPublisher bodyPublisher;
-    private volatile Optional<HttpClient.Version> version;
-    private Duration duration;
-
-    public HttpRequestBuilderImpl(URI uri) {
-        requireNonNull(uri, "uri must be non-null");
-        checkURI(uri);
-        this.uri = uri;
-        this.userHeaders = new HttpHeadersImpl();
-        this.method = "GET"; // default, as per spec
-        this.version = Optional.empty();
-    }
-
-    public HttpRequestBuilderImpl() {
-        this.userHeaders = new HttpHeadersImpl();
-        this.method = "GET"; // default, as per spec
-        this.version = Optional.empty();
-    }
-
-    @Override
-    public HttpRequestBuilderImpl uri(URI uri) {
-        requireNonNull(uri, "uri must be non-null");
-        checkURI(uri);
-        this.uri = uri;
-        return this;
-    }
-
-    private static IllegalArgumentException newIAE(String message, Object... args) {
-        return new IllegalArgumentException(format(message, args));
-    }
-
-    private static void checkURI(URI uri) {
-        String scheme = uri.getScheme();
-        if (scheme == null)
-            throw newIAE("URI with undefined scheme");
-        scheme = scheme.toLowerCase();
-        if (!(scheme.equals("https") || scheme.equals("http"))) {
-            throw newIAE("invalid URI scheme %s", scheme);
-        }
-        if (uri.getHost() == null) {
-            throw newIAE("unsupported URI %s", uri);
-        }
-    }
-
-    @Override
-    public HttpRequestBuilderImpl copy() {
-        HttpRequestBuilderImpl b = new HttpRequestBuilderImpl(this.uri);
-        b.userHeaders = this.userHeaders.deepCopy();
-        b.method = this.method;
-        b.expectContinue = this.expectContinue;
-        b.bodyPublisher = bodyPublisher;
-        b.uri = uri;
-        b.duration = duration;
-        b.version = version;
-        return b;
-    }
-
-    private void checkNameAndValue(String name, String value) {
-        requireNonNull(name, "name");
-        requireNonNull(value, "value");
-        if (!isValidName(name)) {
-            throw newIAE("invalid header name: \"%s\"", name);
-        }
-        if (!Utils.ALLOWED_HEADERS.test(name)) {
-            throw newIAE("restricted header name: \"%s\"", name);
-        }
-        if (!isValidValue(value)) {
-            throw newIAE("invalid header value: \"%s\"", value);
-        }
-    }
-
-    @Override
-    public HttpRequestBuilderImpl setHeader(String name, String value) {
-        checkNameAndValue(name, value);
-        userHeaders.setHeader(name, value);
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl header(String name, String value) {
-        checkNameAndValue(name, value);
-        userHeaders.addHeader(name, value);
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl headers(String... params) {
-        requireNonNull(params);
-        if (params.length == 0 || params.length % 2 != 0) {
-            throw newIAE("wrong number, %d, of parameters", params.length);
-        }
-        for (int i = 0; i < params.length; i += 2) {
-            String name  = params[i];
-            String value = params[i + 1];
-            header(name, value);
-        }
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl expectContinue(boolean enable) {
-        expectContinue = enable;
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl version(HttpClient.Version version) {
-        requireNonNull(version);
-        this.version = Optional.of(version);
-        return this;
-    }
-
-    HttpHeadersImpl headers() {  return userHeaders; }
-
-    URI uri() { return uri; }
-
-    String method() { return method; }
-
-    boolean expectContinue() { return expectContinue; }
-
-    BodyPublisher bodyPublisher() { return bodyPublisher; }
-
-    Optional<HttpClient.Version> version() { return version; }
-
-    @Override
-    public HttpRequest.Builder GET() {
-        return method0("GET", null);
-    }
-
-    @Override
-    public HttpRequest.Builder POST(BodyPublisher body) {
-        return method0("POST", requireNonNull(body));
-    }
-
-    @Override
-    public HttpRequest.Builder DELETE(BodyPublisher body) {
-        return method0("DELETE", requireNonNull(body));
-    }
-
-    @Override
-    public HttpRequest.Builder PUT(BodyPublisher body) {
-        return method0("PUT", requireNonNull(body));
-    }
-
-    @Override
-    public HttpRequest.Builder method(String method, BodyPublisher body) {
-        requireNonNull(method);
-        if (method.equals(""))
-            throw newIAE("illegal method <empty string>");
-        if (method.equals("CONNECT"))
-            throw newIAE("method CONNECT is not supported");
-        return method0(method, requireNonNull(body));
-    }
-
-    private HttpRequest.Builder method0(String method, BodyPublisher body) {
-        assert method != null;
-        assert !method.equals("GET") ? body != null : true;
-        assert !method.equals("");
-        this.method = method;
-        this.bodyPublisher = body;
-        return this;
-    }
-
-    @Override
-    public HttpRequest build() {
-        if (uri == null)
-            throw new IllegalStateException("uri is null");
-        assert method != null;
-        return new HttpRequestImpl(this);
-    }
-
-    @Override
-    public HttpRequest.Builder timeout(Duration duration) {
-        requireNonNull(duration);
-        if (duration.isNegative() || Duration.ZERO.equals(duration))
-            throw new IllegalArgumentException("Invalid duration: " + duration);
-        this.duration = duration;
-        return this;
-    }
-
-    Duration timeout() { return duration; }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,331 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.websocket.WebSocketRequest;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.Duration;
-import java.util.List;
-import java.util.Locale;
-import java.util.Optional;
-
-import static jdk.incubator.http.internal.common.Utils.ALLOWED_HEADERS;
-
-class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
-
-    private final HttpHeaders userHeaders;
-    private final HttpHeadersImpl systemHeaders;
-    private final URI uri;
-    private volatile Proxy proxy; // ensure safe publishing
-    private final InetSocketAddress authority; // only used when URI not specified
-    private final String method;
-    final BodyPublisher requestPublisher;
-    final boolean secure;
-    final boolean expectContinue;
-    private volatile boolean isWebSocket;
-    private volatile AccessControlContext acc;
-    private final Duration timeout;  // may be null
-    private final Optional<HttpClient.Version> version;
-
-    private static String userAgent() {
-        PrivilegedAction<String> pa = () -> System.getProperty("java.version");
-        String version = AccessController.doPrivileged(pa);
-        return "Java-http-client/" + version;
-    }
-
-    /** The value of the User-Agent header for all requests sent by the client. */
-    public static final String USER_AGENT = userAgent();
-
-    /**
-     * Creates an HttpRequestImpl from the given builder.
-     */
-    public HttpRequestImpl(HttpRequestBuilderImpl builder) {
-        String method = builder.method();
-        this.method = method == null ? "GET" : method;
-        this.userHeaders = ImmutableHeaders.of(builder.headers().map(), ALLOWED_HEADERS);
-        this.systemHeaders = new HttpHeadersImpl();
-        this.uri = builder.uri();
-        assert uri != null;
-        this.proxy = null;
-        this.expectContinue = builder.expectContinue();
-        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
-        this.requestPublisher = builder.bodyPublisher();  // may be null
-        this.timeout = builder.timeout();
-        this.version = builder.version();
-        this.authority = null;
-    }
-
-    /**
-     * Creates an HttpRequestImpl from the given request.
-     */
-    public HttpRequestImpl(HttpRequest request, ProxySelector ps, AccessControlContext acc) {
-        String method = request.method();
-        this.method = method == null ? "GET" : method;
-        this.userHeaders = request.headers();
-        if (request instanceof HttpRequestImpl) {
-            this.systemHeaders = ((HttpRequestImpl) request).systemHeaders;
-            this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
-        } else {
-            this.systemHeaders = new HttpHeadersImpl();
-        }
-        this.systemHeaders.setHeader("User-Agent", USER_AGENT);
-        this.uri = request.uri();
-        if (isWebSocket) {
-            // WebSocket determines and sets the proxy itself
-            this.proxy = ((HttpRequestImpl) request).proxy;
-        } else {
-            if (ps != null)
-                this.proxy = retrieveProxy(ps, uri);
-            else
-                this.proxy = null;
-        }
-        this.expectContinue = request.expectContinue();
-        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
-        this.requestPublisher = request.bodyPublisher().orElse(null);
-        if (acc != null && requestPublisher instanceof RequestPublishers.FilePublisher) {
-            // Restricts the file publisher with the senders ACC, if any
-            ((RequestPublishers.FilePublisher)requestPublisher).setAccessControlContext(acc);
-        }
-        this.timeout = request.timeout().orElse(null);
-        this.version = request.version();
-        this.authority = null;
-    }
-
-    /** Creates a HttpRequestImpl using fields of an existing request impl. */
-    public HttpRequestImpl(URI uri,
-                           String method,
-                           HttpRequestImpl other) {
-        this.method = method == null? "GET" : method;
-        this.userHeaders = other.userHeaders;
-        this.isWebSocket = other.isWebSocket;
-        this.systemHeaders = other.systemHeaders;
-        this.uri = uri;
-        this.proxy = other.proxy;
-        this.expectContinue = other.expectContinue;
-        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
-        this.requestPublisher = other.requestPublisher;  // may be null
-        this.acc = other.acc;
-        this.timeout = other.timeout;
-        this.version = other.version();
-        this.authority = null;
-    }
-
-    /* used for creating CONNECT requests  */
-    HttpRequestImpl(String method, InetSocketAddress authority, HttpHeaders headers) {
-        // TODO: isWebSocket flag is not specified, but the assumption is that
-        // such a request will never be made on a connection that will be returned
-        // to the connection pool (we might need to revisit this constructor later)
-        assert "CONNECT".equalsIgnoreCase(method);
-        this.method = method;
-        this.systemHeaders = new HttpHeadersImpl();
-        this.userHeaders = ImmutableHeaders.of(headers);
-        this.uri = URI.create("socket://" + authority.getHostString() + ":"
-                              + Integer.toString(authority.getPort()) + "/");
-        this.proxy = null;
-        this.requestPublisher = null;
-        this.authority = authority;
-        this.secure = false;
-        this.expectContinue = false;
-        this.timeout = null;
-        // The CONNECT request sent for tunneling is only used in two cases:
-        //   1. websocket, which only supports HTTP/1.1
-        //   2. SSL tunneling through a HTTP/1.1 proxy
-        // In either case we do not want to upgrade the connection to the proxy.
-        // What we want to possibly upgrade is the tunneled connection to the
-        // target server (so not the CONNECT request itself)
-        this.version = Optional.of(HttpClient.Version.HTTP_1_1);
-    }
-
-    final boolean isConnect() {
-        return "CONNECT".equalsIgnoreCase(method);
-    }
-
-    /**
-     * Creates a HttpRequestImpl from the given set of Headers and the associated
-     * "parent" request. Fields not taken from the headers are taken from the
-     * parent.
-     */
-    static HttpRequestImpl createPushRequest(HttpRequestImpl parent,
-                                             HttpHeadersImpl headers)
-        throws IOException
-    {
-        return new HttpRequestImpl(parent, headers);
-    }
-
-    // only used for push requests
-    private HttpRequestImpl(HttpRequestImpl parent, HttpHeadersImpl headers)
-        throws IOException
-    {
-        this.method = headers.firstValue(":method")
-                .orElseThrow(() -> new IOException("No method in Push Promise"));
-        String path = headers.firstValue(":path")
-                .orElseThrow(() -> new IOException("No path in Push Promise"));
-        String scheme = headers.firstValue(":scheme")
-                .orElseThrow(() -> new IOException("No scheme in Push Promise"));
-        String authority = headers.firstValue(":authority")
-                .orElseThrow(() -> new IOException("No authority in Push Promise"));
-        StringBuilder sb = new StringBuilder();
-        sb.append(scheme).append("://").append(authority).append(path);
-        this.uri = URI.create(sb.toString());
-        this.proxy = null;
-        this.userHeaders = ImmutableHeaders.of(headers.map(), ALLOWED_HEADERS);
-        this.systemHeaders = parent.systemHeaders;
-        this.expectContinue = parent.expectContinue;
-        this.secure = parent.secure;
-        this.requestPublisher = parent.requestPublisher;
-        this.acc = parent.acc;
-        this.timeout = parent.timeout;
-        this.version = parent.version;
-        this.authority = null;
-    }
-
-    @Override
-    public String toString() {
-        return (uri == null ? "" : uri.toString()) + " " + method;
-    }
-
-    @Override
-    public HttpHeaders headers() {
-        return userHeaders;
-    }
-
-    InetSocketAddress authority() { return authority; }
-
-    void setH2Upgrade(Http2ClientImpl h2client) {
-        systemHeaders.setHeader("Connection", "Upgrade, HTTP2-Settings");
-        systemHeaders.setHeader("Upgrade", "h2c");
-        systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
-    }
-
-    @Override
-    public boolean expectContinue() { return expectContinue; }
-
-    /** Retrieves the proxy, from the given ProxySelector, if there is one. */
-    private static Proxy retrieveProxy(ProxySelector ps, URI uri) {
-        Proxy proxy = null;
-        List<Proxy> pl = ps.select(uri);
-        if (!pl.isEmpty()) {
-            Proxy p = pl.get(0);
-            if (p.type() == Proxy.Type.HTTP)
-                proxy = p;
-        }
-        return proxy;
-    }
-
-    InetSocketAddress proxy() {
-        if (proxy == null || proxy.type() != Proxy.Type.HTTP
-                || method.equalsIgnoreCase("CONNECT")) {
-            return null;
-        }
-        return (InetSocketAddress)proxy.address();
-    }
-
-    boolean secure() { return secure; }
-
-    @Override
-    public void setProxy(Proxy proxy) {
-        assert isWebSocket;
-        this.proxy = proxy;
-    }
-
-    @Override
-    public void isWebSocket(boolean is) {
-        isWebSocket = is;
-    }
-
-    boolean isWebSocket() {
-        return isWebSocket;
-    }
-
-    @Override
-    public Optional<BodyPublisher> bodyPublisher() {
-        return requestPublisher == null ? Optional.empty()
-                                        : Optional.of(requestPublisher);
-    }
-
-    /**
-     * 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; }
-
-    @Override
-    public Optional<Duration> timeout() {
-        return timeout == null ? Optional.empty() : Optional.of(timeout);
-    }
-
-    HttpHeaders getUserHeaders() { return userHeaders; }
-
-    HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
-
-    @Override
-    public Optional<HttpClient.Version> version() { return version; }
-
-    void addSystemHeader(String name, String value) {
-        systemHeaders.addHeader(name, value);
-    }
-
-    @Override
-    public void setSystemHeader(String name, String value) {
-        systemHeaders.setHeader(name, value);
-    }
-
-    InetSocketAddress getAddress() {
-        URI uri = uri();
-        if (uri == null) {
-            return authority();
-        }
-        int p = uri.getPort();
-        if (p == -1) {
-            if (uri.getScheme().equalsIgnoreCase("https")) {
-                p = 443;
-            } else {
-                p = 80;
-            }
-        }
-        final String host = uri.getHost();
-        final int port = p;
-        if (proxy() == null) {
-            PrivilegedAction<InetSocketAddress> pa = () -> new InetSocketAddress(host, port);
-            return AccessController.doPrivileged(pa);
-        } else {
-            return InetSocketAddress.createUnresolved(host, port);
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponse.java	Tue Feb 06 11:39:55 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponse.java	Tue Feb 06 14:10:28 2018 +0000
@@ -35,9 +35,7 @@
 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.security.AccessControlContext;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -46,15 +44,16 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.Flow;
 import java.util.concurrent.Flow.Subscriber;
-import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Stream;
 import javax.net.ssl.SSLParameters;
 import jdk.incubator.http.internal.BufferingSubscriber;
 import jdk.incubator.http.internal.LineSubscriberAdapter;
+import jdk.incubator.http.internal.ResponseBodyHandlers.FileDownloadBodyHandler;
+import jdk.incubator.http.internal.ResponseBodyHandlers.PathBodyHandler;
+import jdk.incubator.http.internal.ResponseBodyHandlers.PushPromisesHandlerWithMap;
 import jdk.incubator.http.internal.ResponseSubscribers;
-import static jdk.incubator.http.internal.common.Utils.unchecked;
 import static jdk.incubator.http.internal.common.Utils.charsetFrom;
 
 /**
@@ -176,128 +175,6 @@
         return path.toFile().getPath();
     }
 
-    /** A body handler that is further restricted by a given ACC. */
-    interface UntrustedBodyHandler<T> extends BodyHandler<T> {
-        void setAccessControlContext(AccessControlContext acc);
-    }
-
-    /**
-     * A Path body handler.
-     *
-     * Note: Exists mainly too allow setting of the senders ACC post creation of
-     * the handler.
-     */
-    static class PathBodyHandler implements UntrustedBodyHandler<Path> {
-        private final Path file;
-        private final OpenOption[]openOptions;
-        private volatile AccessControlContext acc;
-
-        PathBodyHandler(Path file, OpenOption... openOptions) {
-            this.file = file;
-            this.openOptions = openOptions;
-        }
-
-        @Override
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
-        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
-            ResponseSubscribers.PathSubscriber bs = (ResponseSubscribers.PathSubscriber)
-                    BodySubscriber.asFileImpl(file, openOptions);
-            bs.setAccessControlContext(acc);
-            return bs;
-        }
-    }
-
-    /* package-private with push promise Map implementation */
-    static class PushPromisesHandlerWithMap<T> implements PushPromiseHandler<T> {
-
-        private final ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap;
-        private final Function<HttpRequest,BodyHandler<T>> pushPromiseHandler;
-
-        PushPromisesHandlerWithMap(Function<HttpRequest,BodyHandler<T>> pushPromiseHandler,
-                                   ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap) {
-            this.pushPromiseHandler = pushPromiseHandler;
-            this.pushPromisesMap = pushPromisesMap;
-        }
-
-        @Override
-        public void applyPushPromise(
-            HttpRequest initiatingRequest, HttpRequest pushRequest,
-            Function<BodyHandler<T>,CompletableFuture<HttpResponse<T>>> acceptor)
-        {
-            URI initiatingURI = initiatingRequest.uri();
-            URI pushRequestURI = pushRequest.uri();
-            if (!initiatingURI.getHost().equalsIgnoreCase(pushRequestURI.getHost()))
-                return;
-
-            int initiatingPort = initiatingURI.getPort();
-            if (initiatingPort == -1 ) {
-                if ("https".equalsIgnoreCase(initiatingURI.getScheme()))
-                    initiatingPort = 443;
-                else
-                    initiatingPort = 80;
-            }
-            int pushPort = pushRequestURI.getPort();
-            if (pushPort == -1 ) {
-                if ("https".equalsIgnoreCase(pushRequestURI.getScheme()))
-                    pushPort = 443;
-                else
-                    pushPort = 80;
-            }
-            if (initiatingPort != pushPort)
-                return;
-
-            CompletableFuture<HttpResponse<T>> cf =
-                    acceptor.apply(pushPromiseHandler.apply(pushRequest));
-            pushPromisesMap.put(pushRequest, cf);
-        }
-    }
-
-    // Similar to Path body handler, but for file download. Supports setting ACC.
-    static class FileDownloadBodyHandler implements UntrustedBodyHandler<Path> {
-        private final Path directory;
-        private final OpenOption[]openOptions;
-        private volatile AccessControlContext acc;
-
-        FileDownloadBodyHandler(Path directory, OpenOption... openOptions) {
-            this.directory = directory;
-            this.openOptions = openOptions;
-        }
-
-        @Override
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
-        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
-            String dispoHeader = headers.firstValue("Content-Disposition")
-                    .orElseThrow(() -> unchecked(new IOException("No Content-Disposition")));
-            if (!dispoHeader.startsWith("attachment;")) {
-                throw unchecked(new IOException("Unknown Content-Disposition type"));
-            }
-            int n = dispoHeader.indexOf("filename=");
-            if (n == -1) {
-                throw unchecked(new IOException("Bad Content-Disposition type"));
-            }
-            int lastsemi = dispoHeader.lastIndexOf(';');
-            String disposition;
-            if (lastsemi < n) {
-                disposition = dispoHeader.substring(n + 9);
-            } else {
-                disposition = dispoHeader.substring(n + 9, lastsemi);
-            }
-            Path file = Paths.get(directory.toString(), disposition);
-
-            ResponseSubscribers.PathSubscriber bs = (ResponseSubscribers.PathSubscriber)
-                    BodySubscriber.asFileImpl(file, openOptions);
-            bs.setAccessControlContext(acc);
-            return bs;
-        }
-    }
 
     /**
      * A handler for response bodies.
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponseImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Supplier;
-import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.internal.websocket.RawChannel;
-
-/**
- * The implementation class for HttpResponse
- */
-class HttpResponseImpl<T> extends HttpResponse<T> implements RawChannel.Provider {
-
-    final int responseCode;
-    final Exchange<T> exchange;
-    final HttpRequest initialRequest;
-    final Optional<HttpResponse<T>> previousResponse;
-    final HttpHeaders headers;
-    final SSLParameters sslParameters;
-    final URI uri;
-    final HttpClient.Version version;
-    RawChannel rawchan;
-    final HttpConnection connection;
-    final Stream<T> stream;
-    final T body;
-
-    public HttpResponseImpl(HttpRequest initialRequest,
-                            Response response,
-                            HttpResponse<T> previousResponse,
-                            T body,
-                            Exchange<T> exch) {
-        this.responseCode = response.statusCode();
-        this.exchange = exch;
-        this.initialRequest = initialRequest;
-        this.previousResponse = Optional.ofNullable(previousResponse);
-        this.headers = response.headers();
-        //this.trailers = trailers;
-        this.sslParameters = exch.client().sslParameters();
-        this.uri = response.request().uri();
-        this.version = response.version();
-        this.connection = connection(exch);
-        this.stream = null;
-        this.body = body;
-    }
-
-    private HttpConnection connection(Exchange<?> exch) {
-        if (exch == null || exch.exchImpl == null) {
-            assert responseCode == 407;
-            return null; // case of Proxy 407
-        }
-        return exch.exchImpl.connection();
-    }
-
-    private ExchangeImpl<?> exchangeImpl() {
-        return exchange != null ? exchange.exchImpl : stream;
-    }
-
-    @Override
-    public int statusCode() {
-        return responseCode;
-    }
-
-    @Override
-    public HttpRequest request() {
-        return initialRequest;
-    }
-
-    @Override
-    public Optional<HttpResponse<T>> previousResponse() {
-        return previousResponse;
-    }
-
-    @Override
-    public HttpHeaders headers() {
-        return headers;
-    }
-
-    @Override
-    public T body() {
-        return body;
-    }
-
-    @Override
-    public SSLParameters sslParameters() {
-        return sslParameters;
-    }
-
-    @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
-
-    /**
-     * Returns a RawChannel that may be used for WebSocket protocol.
-     * @implNote This implementation does not support RawChannel over
-     *           HTTP/2 connections.
-     * @return a RawChannel that may be used for WebSocket protocol.
-     * @throws UnsupportedOperationException if getting a RawChannel over
-     *         this connection is not supported.
-     * @throws IOException if an I/O exception occurs while retrieving
-     *         the channel.
-     */
-    @Override
-    public synchronized RawChannel rawChannel() throws IOException {
-        if (rawchan == null) {
-            ExchangeImpl<?> exchImpl = exchangeImpl();
-            if (!(exchImpl instanceof Http1Exchange)) {
-                // RawChannel is only used for WebSocket - and WebSocket
-                // is not supported over HTTP/2 yet, so we should not come
-                // here. Getting a RawChannel over HTTP/2 might be supported
-                // in the future, but it would entail retrieving any left over
-                // bytes that might have been read but not consumed by the
-                // HTTP/2 connection.
-                throw new UnsupportedOperationException("RawChannel is not supported over HTTP/2");
-            }
-            // Http1Exchange may have some remaining bytes in its
-            // internal buffer.
-            Supplier<ByteBuffer> initial = ((Http1Exchange<?>)exchImpl)::drainLeftOverBytes;
-            rawchan = new RawChannelImpl(exchange.client(), connection, initial);
-        }
-        return rawchan;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        String method = request().method();
-        URI uri = request().uri();
-        String uristring = uri == null ? "" : uri.toString();
-        sb.append('(')
-          .append(method)
-          .append(" ")
-          .append(uristring)
-          .append(") ")
-          .append(statusCode());
-        return sb.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ImmutableHeaders.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.unmodifiableList;
-import static java.util.Collections.unmodifiableMap;
-import static java.util.Objects.requireNonNull;
-
-final class ImmutableHeaders extends HttpHeaders {
-
-    private final Map<String, List<String>> map;
-
-    public static ImmutableHeaders empty() {
-        return of(emptyMap());
-    }
-
-    public static ImmutableHeaders of(Map<String, List<String>> src) {
-        return of(src, x -> true);
-    }
-
-    public static ImmutableHeaders of(HttpHeaders headers) {
-        return (headers instanceof ImmutableHeaders)
-                ? (ImmutableHeaders)headers
-                : of(headers.map());
-    }
-
-    public static ImmutableHeaders of(Map<String, List<String>> src,
-                                      Predicate<? super String> keyAllowed) {
-        requireNonNull(src, "src");
-        requireNonNull(keyAllowed, "keyAllowed");
-        return new ImmutableHeaders(src, headerAllowed(keyAllowed));
-    }
-
-    public static ImmutableHeaders of(Map<String, List<String>> src,
-                                      BiPredicate<? super String, ? super List<String>> headerAllowed) {
-        requireNonNull(src, "src");
-        requireNonNull(headerAllowed, "headerAllowed");
-        return new ImmutableHeaders(src, headerAllowed);
-    }
-
-    private ImmutableHeaders(Map<String, List<String>> src,
-                             BiPredicate<? super String, ? super List<String>> headerAllowed) {
-        Map<String, List<String>> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        src.entrySet().stream()
-                .filter(e -> headerAllowed.test(e.getKey(), e.getValue()))
-                .forEach(e ->
-                        {
-                            List<String> values = new ArrayList<>(e.getValue());
-                            m.put(e.getKey(), unmodifiableList(values));
-                        }
-                );
-        this.map = unmodifiableMap(m);
-    }
-
-    private static BiPredicate<String, List<String>> headerAllowed(Predicate<? super String> keyAllowed) {
-        return (n,v) -> keyAllowed.test(n);
-    }
-
-    @Override
-    public Map<String, List<String>> map() {
-        return map;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,321 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.time.Duration;
-import java.util.List;
-import java.security.AccessControlContext;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
-import jdk.incubator.http.HttpResponse.PushPromiseHandler;
-import jdk.incubator.http.HttpResponse.UntrustedBodyHandler;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.ConnectionExpiredException;
-import jdk.incubator.http.internal.common.Utils;
-import static jdk.incubator.http.internal.common.MinimalFuture.completedFuture;
-import static jdk.incubator.http.internal.common.MinimalFuture.failedFuture;
-
-/**
- * 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<T> {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("MultiExchange"::toString, DEBUG);
-
-    private final HttpRequest userRequest; // the user request
-    private final HttpRequestImpl request; // a copy of the user request
-    final AccessControlContext acc;
-    final HttpClientImpl client;
-    final HttpResponse.BodyHandler<T> responseHandler;
-    final Executor executor;
-    final AtomicInteger attempts = new AtomicInteger();
-    HttpRequestImpl currentreq; // used for async only
-    Exchange<T> exchange; // the current exchange
-    Exchange<T> previous;
-    volatile Throwable retryCause;
-    volatile boolean expiredOnce;
-    volatile HttpResponse<T> response = null;
-
-    // Maximum number of times a request will be retried/redirected
-    // for any reason
-
-    static final int DEFAULT_MAX_ATTEMPTS = 5;
-    static final int max_attempts = Utils.getIntegerNetProperty(
-            "jdk.httpclient.redirects.retrylimit", DEFAULT_MAX_ATTEMPTS
-    );
-
-    private final List<HeaderFilter> filters;
-    TimedEvent timedEvent;
-    volatile boolean cancelled;
-    final PushGroup<T> pushGroup;
-
-    /**
-     * 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 with one final response.
-     */
-    MultiExchange(HttpRequest userRequest,
-                  HttpRequestImpl requestImpl,
-                  HttpClientImpl client,
-                  HttpResponse.BodyHandler<T> responseHandler,
-                  PushPromiseHandler<T> pushPromiseHandler,
-                  AccessControlContext acc) {
-        this.previous = null;
-        this.userRequest = userRequest;
-        this.request = requestImpl;
-        this.currentreq = request;
-        this.client = client;
-        this.filters = client.filterChain();
-        this.acc = acc;
-        this.executor = client.theExecutor();
-        this.responseHandler = responseHandler;
-        if (acc != null) {
-            // Restricts the file publisher with the senders ACC, if any
-            if (responseHandler instanceof UntrustedBodyHandler)
-                ((UntrustedBodyHandler)this.responseHandler).setAccessControlContext(acc);
-        }
-
-        if (pushPromiseHandler != null) {
-            this.pushGroup = new PushGroup<>(pushPromiseHandler, request, acc);
-        } else {
-            pushGroup = null;
-        }
-
-        this.exchange = new Exchange<>(request, this);
-    }
-
-    private synchronized Exchange<T> getExchange() {
-        return exchange;
-    }
-
-    HttpClientImpl client() {
-        return client;
-    }
-
-    HttpClient.Version version() {
-        HttpClient.Version vers = request.version().orElse(client.version());
-        if (vers == HttpClient.Version.HTTP_2 && !request.secure() && request.proxy() != null)
-            vers = HttpClient.Version.HTTP_1_1;
-        return vers;
-    }
-
-    private synchronized void setExchange(Exchange<T> exchange) {
-        if (this.exchange != null && exchange != this.exchange) {
-            this.exchange.released();
-        }
-        this.exchange = exchange;
-    }
-
-    private void cancelTimer() {
-        if (timedEvent != null) {
-            client.cancelTimer(timedEvent);
-        }
-    }
-
-    private void requestFilters(HttpRequestImpl r) throws IOException {
-        Log.logTrace("Applying request filters");
-        for (HeaderFilter filter : filters) {
-            Log.logTrace("Applying {0}", filter);
-            filter.request(r, this);
-        }
-        Log.logTrace("All filters applied");
-    }
-
-    private HttpRequestImpl responseFilters(Response response) throws IOException
-    {
-        Log.logTrace("Applying response filters");
-        for (HeaderFilter filter : filters) {
-            Log.logTrace("Applying {0}", filter);
-            HttpRequestImpl newreq = filter.response(response);
-            if (newreq != null) {
-                Log.logTrace("New request: stopping filters");
-                return newreq;
-            }
-        }
-        Log.logTrace("All filters applied");
-        return null;
-    }
-
-//    public void cancel() {
-//        cancelled = true;
-//        getExchange().cancel();
-//    }
-
-    public void cancel(IOException cause) {
-        cancelled = true;
-        getExchange().cancel(cause);
-    }
-
-    public CompletableFuture<HttpResponse<T>> responseAsync() {
-        CompletableFuture<Void> start = new MinimalFuture<>();
-        CompletableFuture<HttpResponse<T>> cf = responseAsync0(start);
-        start.completeAsync( () -> null, executor); // trigger execution
-        return cf;
-    }
-
-    private CompletableFuture<HttpResponse<T>>
-    responseAsync0(CompletableFuture<Void> start) {
-        return start.thenCompose( v -> responseAsyncImpl())
-                    .thenCompose((Response r) -> {
-                        Exchange<T> exch = getExchange();
-                        return exch.readBodyAsync(responseHandler)
-                            .thenApply((T body) -> {
-                                this.response =
-                                    new HttpResponseImpl<>(userRequest, r, this.response, body, exch);
-                                return this.response;
-                            });
-                    });
-    }
-
-    private CompletableFuture<Response> responseAsyncImpl() {
-        CompletableFuture<Response> cf;
-        if (attempts.incrementAndGet() > max_attempts) {
-            cf = failedFuture(new IOException("Too many retries", retryCause));
-        } else {
-            if (currentreq.timeout().isPresent()) {
-                timedEvent = new TimedEvent(currentreq.timeout().get());
-                client.registerTimer(timedEvent);
-            }
-            try {
-                // 1. apply request filters
-                requestFilters(currentreq);
-            } catch (IOException e) {
-                return failedFuture(e);
-            }
-            Exchange<T> exch = getExchange();
-            // 2. get response
-            cf = exch.responseAsync()
-                     .thenCompose((Response response) -> {
-                        HttpRequestImpl newrequest;
-                        try {
-                            // 3. apply response filters
-                            newrequest = responseFilters(response);
-                        } catch (IOException e) {
-                            return failedFuture(e);
-                        }
-                        // 4. check filter result and repeat or continue
-                        if (newrequest == null) {
-                            if (attempts.get() > 1) {
-                                Log.logError("Succeeded on attempt: " + attempts);
-                            }
-                            return completedFuture(response);
-                        } else {
-                            this.response =
-                                new HttpResponseImpl<>(currentreq, response, this.response, null, exch);
-                            Exchange<T> oldExch = exch;
-                            return exch.ignoreBody().handle((r,t) -> {
-                                currentreq = newrequest;
-                                expiredOnce = false;
-                                setExchange(new Exchange<>(currentreq, this, acc));
-                                return responseAsyncImpl();
-                            }).thenCompose(Function.identity());
-                        } })
-                     .handle((response, ex) -> {
-                        // 5. handle errors and cancel any timer set
-                        cancelTimer();
-                        if (ex == null) {
-                            assert response != null;
-                            return completedFuture(response);
-                        }
-                        // all exceptions thrown are handled here
-                        CompletableFuture<Response> errorCF = getExceptionalCF(ex);
-                        if (errorCF == null) {
-                            return responseAsyncImpl();
-                        } else {
-                            return errorCF;
-                        } })
-                     .thenCompose(Function.identity());
-        }
-        return cf;
-    }
-
-    /**
-     * Takes a Throwable and returns a suitable CompletableFuture that is
-     * completed exceptionally, or null.
-     */
-    private CompletableFuture<Response> getExceptionalCF(Throwable t) {
-        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");
-        } else if (t instanceof ConnectionExpiredException) {
-            // allow the retry mechanism to do its work
-            // ####: method (GET,HEAD, not POST?), no bytes written or read ( differentiate? )
-            if (t.getCause() != null) retryCause = t.getCause();
-            if (!expiredOnce) {
-                DEBUG_LOGGER.log(Level.DEBUG,
-                    "MultiExchange: ConnectionExpiredException (async): retrying...",
-                    t);
-                expiredOnce = true;
-                return null;
-            } else {
-                DEBUG_LOGGER.log(Level.DEBUG,
-                    "MultiExchange: ConnectionExpiredException (async): already retried once.",
-                    t);
-                if (t.getCause() != null) t = t.getCause();
-            }
-        }
-        return failedFuture(t);
-    }
-
-    class TimedEvent extends TimeoutEvent {
-        TimedEvent(Duration duration) {
-            super(duration);
-        }
-        @Override
-        public void handle() {
-            DEBUG_LOGGER.log(Level.DEBUG,
-                    "Cancelling MultiExchange due to timeout for request %s",
-                     request);
-            cancel(new HttpTimeoutException("request timed out"));
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainHttpConnection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-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.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Plain raw TCP connection direct to destination.
- * The connection operates in asynchronous non-blocking mode.
- * All reads and writes are done non-blocking.
- */
-class PlainHttpConnection extends HttpConnection {
-
-    private final Object reading = new Object();
-    protected final SocketChannel chan;
-    private final FlowTube tube;
-    private final PlainHttpPublisher writePublisher = new PlainHttpPublisher(reading);
-    private volatile boolean connected;
-    private boolean closed;
-
-    // should be volatile to provide proper synchronization(visibility) action
-
-    final class ConnectEvent extends AsyncEvent {
-        private final 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 {
-                assert !connected : "Already connected";
-                assert !chan.isBlocking() : "Unexpected blocking channel";
-                debug.log(Level.DEBUG, "ConnectEvent: finishing connect");
-                boolean finished = chan.finishConnect();
-                assert finished : "Expected channel to be connected";
-                debug.log(Level.DEBUG,
-                          "ConnectEvent: connect finished: %s Local addr: %s", finished, chan.getLocalAddress());
-                connected = true;
-                // complete async since the event runs on the SelectorManager thread
-                cf.completeAsync(() -> null, client().theExecutor());
-            } catch (Throwable e) {
-                client().theExecutor().execute( () -> cf.completeExceptionally(e));
-            }
-        }
-
-        @Override
-        public void abort(IOException ioe) {
-            close();
-            client().theExecutor().execute( () -> cf.completeExceptionally(ioe));
-        }
-    }
-
-    @Override
-    public CompletableFuture<Void> connectAsync() {
-        CompletableFuture<Void> cf = new MinimalFuture<>();
-        try {
-            assert !connected : "Already connected";
-            assert !chan.isBlocking() : "Unexpected blocking channel";
-            boolean finished = false;
-            PrivilegedExceptionAction<Boolean> pa = () -> chan.connect(address);
-            try {
-                 finished = AccessController.doPrivileged(pa);
-            } catch (PrivilegedActionException e) {
-                cf.completeExceptionally(e.getCause());
-            }
-            if (finished) {
-                debug.log(Level.DEBUG, "connect finished without blocking");
-                connected = true;
-                cf.complete(null);
-            } else {
-                debug.log(Level.DEBUG, "registering connect event");
-                client().registerEvent(new ConnectEvent(cf));
-            }
-        } catch (Throwable throwable) {
-            cf.completeExceptionally(throwable);
-        }
-        return cf;
-    }
-
-    @Override
-    SocketChannel channel() {
-        return chan;
-    }
-
-    @Override
-    final FlowTube getConnectionFlow() {
-        return tube;
-    }
-
-    PlainHttpConnection(InetSocketAddress addr, HttpClientImpl client) {
-        super(addr, client);
-        try {
-            this.chan = SocketChannel.open();
-            chan.configureBlocking(false);
-            int bufsize = client.getReceiveBufferSize();
-            if (!trySetReceiveBufferSize(bufsize)) {
-                trySetReceiveBufferSize(256*1024);
-            }
-            chan.setOption(StandardSocketOptions.TCP_NODELAY, true);
-            // wrap the connected channel in a Tube for async reading and writing
-            tube = new SocketTube(client(), chan, Utils::getBuffer);
-        } catch (IOException e) {
-            throw new InternalError(e);
-        }
-    }
-
-    private boolean trySetReceiveBufferSize(int bufsize) {
-        try {
-            chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
-            return true;
-        } catch(IOException x) {
-            debug.log(Level.DEBUG,
-                    "Failed to set receive buffer size to %d on %s",
-                    bufsize, chan);
-        }
-        return false;
-    }
-
-    @Override
-    HttpPublisher publisher() { return writePublisher; }
-
-
-    @Override
-    public String toString() {
-        return "PlainHttpConnection: " + super.toString();
-    }
-
-    /**
-     * Closes this connection
-     */
-    @Override
-    public synchronized void close() {
-        if (closed) {
-            return;
-        }
-        closed = true;
-        try {
-            Log.logTrace("Closing: " + toString());
-            chan.close();
-        } catch (IOException e) {}
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        debug.log(Level.DEBUG, "Shutting down input");
-        chan.shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        debug.log(Level.DEBUG, "Shutting down output");
-        chan.shutdownOutput();
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return new ConnectionPool.CacheKey(address, null);
-    }
-
-    @Override
-    synchronized boolean connected() {
-        return connected;
-    }
-
-
-    @Override
-    boolean isSecure() {
-        return false;
-    }
-
-    @Override
-    boolean isProxied() {
-        return false;
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    private static final class PlainDetachedChannel
-            extends DetachedConnectionChannel {
-        final PlainHttpConnection plainConnection;
-        boolean closed;
-        PlainDetachedChannel(PlainHttpConnection conn) {
-            // We're handing the connection channel over to a web socket.
-            // We need the selector manager's thread to stay alive until
-            // the WebSocket is closed.
-            conn.client().webSocketOpen();
-            this.plainConnection = conn;
-        }
-
-        @Override
-        SocketChannel channel() {
-            return plainConnection.channel();
-        }
-
-        @Override
-        ByteBuffer read() throws IOException {
-            ByteBuffer dst = ByteBuffer.allocate(8192);
-            int n = readImpl(dst);
-            if (n > 0) {
-                return dst;
-            } else if (n == 0) {
-                return Utils.EMPTY_BYTEBUFFER;
-            } else {
-                return null;
-            }
-        }
-
-        @Override
-        public void close() {
-            HttpClientImpl client = plainConnection.client();
-            try {
-                plainConnection.close();
-            } finally {
-                // notify the HttpClientImpl that the websocket is no
-                // no longer operating.
-                synchronized(this) {
-                    if (closed == true) return;
-                    closed = true;
-                }
-                client.webSocketClose();
-            }
-        }
-
-        @Override
-        public long write(ByteBuffer[] buffers, int start, int number)
-                throws IOException
-        {
-            return channel().write(buffers, start, number);
-        }
-
-        @Override
-        public void shutdownInput() throws IOException {
-            plainConnection.shutdownInput();
-        }
-
-        @Override
-        public void shutdownOutput() throws IOException {
-            plainConnection.shutdownOutput();
-        }
-
-        private int readImpl(ByteBuffer buf) throws IOException {
-            int mark = buf.position();
-            int n;
-            n = channel().read(buf);
-            if (n == -1) {
-                return -1;
-            }
-            Utils.flipToMark(buf, mark);
-            return n;
-        }
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    @Override
-    DetachedConnectionChannel detachChannel() {
-        client().cancelRegistration(channel());
-        return new PlainDetachedChannel(this);
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainProxyConnection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.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);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,167 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Function;
-
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.MinimalFuture;
-
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-
-/**
- * A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
- * encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.
- * Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.
- */
-final class PlainTunnelingConnection extends HttpConnection {
-
-    final PlainHttpConnection delegate;
-    final HttpHeaders proxyHeaders;
-    final InetSocketAddress proxyAddr;
-    private volatile boolean connected;
-
-    protected PlainTunnelingConnection(InetSocketAddress addr,
-                                       InetSocketAddress proxy,
-                                       HttpClientImpl client,
-                                       HttpHeaders proxyHeaders) {
-        super(addr, client);
-        this.proxyAddr = proxy;
-        this.proxyHeaders = proxyHeaders;
-        delegate = new PlainHttpConnection(proxy, client);
-    }
-
-    @Override
-    public CompletableFuture<Void> connectAsync() {
-        debug.log(Level.DEBUG, "Connecting plain connection");
-        return delegate.connectAsync()
-            .thenCompose((Void v) -> {
-                debug.log(Level.DEBUG, "sending HTTP/1.1 CONNECT");
-                HttpClientImpl client = client();
-                assert client != null;
-                HttpRequestImpl req = new HttpRequestImpl("CONNECT", address, proxyHeaders);
-                MultiExchange<Void> mulEx = new MultiExchange<>(null, req,
-                        client, discard(null), null, null);
-                Exchange<Void> connectExchange = new Exchange<>(req, mulEx);
-
-                return connectExchange
-                        .responseAsyncImpl(delegate)
-                        .thenCompose((Response resp) -> {
-                            CompletableFuture<Void> cf = new MinimalFuture<>();
-                            debug.log(Level.DEBUG, "got response: %d", resp.statusCode());
-                            if (resp.statusCode() == 407) {
-                                return connectExchange.ignoreBody().handle((r,t) -> {
-                                    // close delegate after reading body: we won't
-                                    // be reusing that connection anyway.
-                                    delegate.close();
-                                    ProxyAuthenticationRequired authenticationRequired =
-                                            new ProxyAuthenticationRequired(resp);
-                                    cf.completeExceptionally(authenticationRequired);
-                                    return cf;
-                                }).thenCompose(Function.identity());
-                            } else if (resp.statusCode() != 200) {
-                                delegate.close();
-                                cf.completeExceptionally(new IOException(
-                                        "Tunnel failed, got: "+ resp.statusCode()));
-                            } else {
-                                // get the initial/remaining bytes
-                                ByteBuffer b = ((Http1Exchange<?>)connectExchange.exchImpl).drainLeftOverBytes();
-                                int remaining = b.remaining();
-                                assert remaining == 0: "Unexpected remaining: " + remaining;
-                                connected = true;
-                                cf.complete(null);
-                            }
-                            return cf;
-                        });
-            });
-    }
-
-    @Override
-    boolean isTunnel() { return true; }
-
-    @Override
-    HttpPublisher publisher() { return delegate.publisher(); }
-
-    @Override
-    boolean connected() {
-        return connected;
-    }
-
-    @Override
-    SocketChannel channel() {
-        return delegate.channel();
-    }
-
-    @Override
-    FlowTube getConnectionFlow() {
-        return delegate.getConnectionFlow();
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return new ConnectionPool.CacheKey(null, proxyAddr);
-    }
-
-    @Override
-    public void close() {
-        delegate.close();
-        connected = false;
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        delegate.shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        delegate.shutdownOutput();
-    }
-
-    @Override
-    boolean isSecure() {
-        return false;
-    }
-
-    @Override
-    boolean isProxied() {
-        return true;
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    @Override
-    DetachedConnectionChannel detachChannel() {
-        return delegate.detachChannel();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PrivilegedExecutor.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Executes tasks within a given access control context, and by a given executor.
- */
-class PrivilegedExecutor implements Executor {
-
-    /** The underlying executor. May be provided by the user. */
-    final Executor executor;
-    /** The ACC to execute the tasks within. */
-    final AccessControlContext acc;
-
-    public PrivilegedExecutor(Executor executor, AccessControlContext acc) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(acc);
-        this.executor = executor;
-        this.acc = acc;
-    }
-
-    private static class PrivilegedRunnable implements Runnable {
-        private final Runnable r;
-        private final AccessControlContext acc;
-        PrivilegedRunnable(Runnable r, AccessControlContext acc) {
-            this.r = r;
-            this.acc = acc;
-        }
-        @Override
-        public void run() {
-            PrivilegedAction<Void> pa = () -> { r.run(); return null; };
-            AccessController.doPrivileged(pa, acc);
-        }
-    }
-
-    @Override
-    public void execute(Runnable r) {
-        executor.execute(new PrivilegedRunnable(r, acc));
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ProxyAuthenticationRequired.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-import java.io.IOException;
-
-/**
- * Signals that a proxy has refused a CONNECT request with a
- * 407 error code.
- */
-final class ProxyAuthenticationRequired extends IOException {
-    private static final long serialVersionUID = 0;
-    final transient Response proxyResponse;
-
-    /**
-     * Constructs a {@code ConnectionExpiredException} with the specified detail
-     * message and cause.
-     *
-     * @param   proxyResponse the response from the proxy
-     */
-    public ProxyAuthenticationRequired(Response proxyResponse) {
-        super("Proxy Authentication Required");
-        assert proxyResponse.statusCode() == 407;
-        this.proxyResponse = proxyResponse;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PullPublisher.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.Iterator;
-import java.util.concurrent.Flow;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-
-/**
- * A Publisher that publishes items obtained from the given Iterable. Each new
- * subscription gets a new Iterator.
- */
-class PullPublisher<T> implements Flow.Publisher<T> {
-
-    // Only one of `iterable` and `throwable` can be non-null. throwable is
-    // non-null when an error has been encountered, by the creator of
-    // PullPublisher, while subscribing the subscriber, but before subscribe has
-    // completed.
-    private final Iterable<T> iterable;
-    private final Throwable throwable;
-
-    PullPublisher(Iterable<T> iterable, Throwable throwable) {
-        this.iterable = iterable;
-        this.throwable = throwable;
-    }
-
-    PullPublisher(Iterable<T> iterable) {
-        this(iterable, null);
-    }
-
-    @Override
-    public void subscribe(Flow.Subscriber<? super T> subscriber) {
-        Subscription sub;
-        if (throwable != null) {
-            assert iterable == null : "non-null iterable: " + iterable;
-            sub = new Subscription(subscriber, null, throwable);
-        } else {
-            assert throwable == null : "non-null exception: " + throwable;
-            sub = new Subscription(subscriber, iterable.iterator(), null);
-        }
-        subscriber.onSubscribe(sub);
-
-        if (throwable != null) {
-            sub.pullScheduler.runOrSchedule();
-        }
-    }
-
-    private class Subscription implements Flow.Subscription {
-
-        private final Flow.Subscriber<? super T> subscriber;
-        private final Iterator<T> iter;
-        private volatile boolean completed;
-        private volatile boolean cancelled;
-        private volatile Throwable error;
-        final SequentialScheduler pullScheduler = new SequentialScheduler(new PullTask());
-        private final Demand demand = new Demand();
-
-        Subscription(Flow.Subscriber<? super T> subscriber,
-                     Iterator<T> iter,
-                     Throwable throwable) {
-            this.subscriber = subscriber;
-            this.iter = iter;
-            this.error = throwable;
-        }
-
-        final class PullTask extends SequentialScheduler.CompleteRestartableTask {
-            @Override
-            protected void run() {
-                if (completed || cancelled) {
-                    return;
-                }
-
-                Throwable t = error;
-                if (t != null) {
-                    completed = true;
-                    pullScheduler.stop();
-                    subscriber.onError(t);
-                    return;
-                }
-
-                while (demand.tryDecrement() && !cancelled) {
-                    if (!iter.hasNext()) {
-                        break;
-                    } else {
-                        subscriber.onNext(iter.next());
-                    }
-                }
-                if (!iter.hasNext() && !cancelled) {
-                    completed = true;
-                    pullScheduler.stop();
-                    subscriber.onComplete();
-                }
-            }
-        }
-
-        @Override
-        public void request(long n) {
-            if (cancelled)
-                return;  // no-op
-
-            if (n <= 0) {
-                error = new IllegalArgumentException("illegal non-positive request:" + n);
-            } else {
-                demand.increase(n);
-            }
-            pullScheduler.runOrSchedule();
-        }
-
-        @Override
-        public void cancel() {
-            cancelled = true;
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PushGroup.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.PushPromiseHandler;
-import jdk.incubator.http.HttpResponse.UntrustedBodyHandler;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Log;
-
-/**
- * One PushGroup object is associated with the parent Stream of the pushed
- * Streams. This keeps track of all common state associated with the pushes.
- */
-class PushGroup<T> {
-    private final HttpRequest initiatingRequest;
-
-    final CompletableFuture<Void> noMorePushesCF;
-
-    volatile Throwable error; // any exception that occurred during pushes
-
-    // user's subscriber object
-    final PushPromiseHandler<T> pushPromiseHandler;
-
-    private final AccessControlContext acc;
-
-    int numberOfPushes;
-    int remainingPushes;
-    boolean noMorePushes = false;
-
-    PushGroup(PushPromiseHandler<T> pushPromiseHandler,
-              HttpRequestImpl initiatingRequest,
-              AccessControlContext acc) {
-        this(pushPromiseHandler, initiatingRequest, new MinimalFuture<>(), acc);
-    }
-
-    // Check mainBodyHandler before calling nested constructor.
-    private PushGroup(HttpResponse.PushPromiseHandler<T> pushPromiseHandler,
-                      HttpRequestImpl initiatingRequest,
-                      CompletableFuture<HttpResponse<T>> mainResponse,
-                      AccessControlContext acc) {
-        this.noMorePushesCF = new MinimalFuture<>();
-        this.pushPromiseHandler = pushPromiseHandler;
-        this.initiatingRequest = initiatingRequest;
-        // Restricts the file publisher with the senders ACC, if any
-        if (pushPromiseHandler instanceof UntrustedBodyHandler)
-            ((UntrustedBodyHandler)this.pushPromiseHandler).setAccessControlContext(acc);
-        this.acc = acc;
-    }
-
-    interface Acceptor<T> {
-        BodyHandler<T> bodyHandler();
-        CompletableFuture<HttpResponse<T>> cf();
-        boolean accepted();
-    }
-
-    private static class AcceptorImpl<T> implements Acceptor<T> {
-        private volatile HttpResponse.BodyHandler<T> bodyHandler;
-        private volatile CompletableFuture<HttpResponse<T>> cf;
-
-        CompletableFuture<HttpResponse<T>> accept(BodyHandler<T> bodyHandler) {
-            Objects.requireNonNull(bodyHandler);
-            if (this.bodyHandler != null)
-                throw new IllegalStateException("non-null bodyHandler");
-            this.bodyHandler = bodyHandler;
-            cf = new MinimalFuture<>();
-            return cf;
-        }
-
-        @Override public BodyHandler<T> bodyHandler() { return bodyHandler; }
-
-        @Override public CompletableFuture<HttpResponse<T>> cf() { return cf; }
-
-        @Override public boolean accepted() { return cf != null; }
-    }
-
-    Acceptor<T> acceptPushRequest(HttpRequest pushRequest) {
-        AcceptorImpl<T> acceptor = new AcceptorImpl<>();
-
-        pushPromiseHandler.applyPushPromise(initiatingRequest, pushRequest, acceptor::accept);
-
-        synchronized (this) {
-            if (acceptor.accepted()) {
-                if (acceptor.bodyHandler instanceof UntrustedBodyHandler) {
-                    ((UntrustedBodyHandler) acceptor.bodyHandler).setAccessControlContext(acc);
-                }
-                numberOfPushes++;
-                remainingPushes++;
-            }
-            return acceptor;
-        }
-    }
-
-    // This is called when the main body response completes because it means
-    // no more PUSH_PROMISEs are possible
-
-    synchronized void noMorePushes(boolean noMore) {
-        noMorePushes = noMore;
-        checkIfCompleted();
-        noMorePushesCF.complete(null);
-    }
-
-    synchronized CompletableFuture<Void> pushesCF() {
-        return noMorePushesCF;
-    }
-
-    synchronized boolean noMorePushes() {
-        return noMorePushes;
-    }
-
-    synchronized void pushCompleted() {
-        remainingPushes--;
-        checkIfCompleted();
-    }
-
-    synchronized void checkIfCompleted() {
-        if (Log.trace()) {
-            Log.logTrace("PushGroup remainingPushes={0} error={1} noMorePushes={2}",
-                         remainingPushes,
-                         (error==null)?error:error.getClass().getSimpleName(),
-                         noMorePushes);
-        }
-        if (remainingPushes == 0 && error == null && noMorePushes) {
-            if (Log.trace()) {
-                Log.logTrace("push completed");
-            }
-        }
-    }
-
-    synchronized void pushError(Throwable t) {
-        if (t == null) {
-            return;
-        }
-        this.error = t;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RawChannelImpl.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.websocket.RawChannel;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SocketChannel;
-import java.util.function.Supplier;
-
-/*
- * 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.
- */
-final class RawChannelImpl implements RawChannel {
-
-    private final HttpClientImpl client;
-    private final HttpConnection.DetachedConnectionChannel detachedChannel;
-    private final Object         initialLock = new Object();
-    private Supplier<ByteBuffer> initial;
-
-    RawChannelImpl(HttpClientImpl client,
-                   HttpConnection connection,
-                   Supplier<ByteBuffer> initial)
-            throws IOException
-    {
-        this.client = client;
-        this.detachedChannel = connection.detachChannel();
-        this.initial = initial;
-
-        SocketChannel chan = connection.channel();
-        client.cancelRegistration(chan);
-        // Constructing a RawChannel is supposed to have a "hand over"
-        // semantics, in other words if construction fails, the channel won't be
-        // needed by anyone, in which case someone still needs to close it
-        try {
-            chan.configureBlocking(false);
-        } catch (IOException e) {
-            try {
-                chan.close();
-            } catch (IOException e1) {
-                e.addSuppressed(e1);
-            } finally {
-                detachedChannel.close();
-            }
-            throw e;
-        }
-    }
-
-    private class NonBlockingRawAsyncEvent extends AsyncEvent {
-
-        private final RawEvent re;
-
-        NonBlockingRawAsyncEvent(RawEvent re) {
-            // !BLOCKING & !REPEATING
-            this.re = re;
-        }
-
-        @Override
-        public SelectableChannel channel() {
-            return detachedChannel.channel();
-        }
-
-        @Override
-        public int interestOps() {
-            return re.interestOps();
-        }
-
-        @Override
-        public void handle() {
-            re.handle();
-        }
-
-        @Override
-        public void abort(IOException ioe) { }
-    }
-
-    @Override
-    public void registerEvent(RawEvent event) throws IOException {
-        client.registerEvent(new NonBlockingRawAsyncEvent(event));
-    }
-
-    @Override
-    public ByteBuffer read() throws IOException {
-        assert !detachedChannel.channel().isBlocking();
-        // connection.read() will no longer be available.
-        return detachedChannel.read();
-    }
-
-    @Override
-    public ByteBuffer initialByteBuffer() {
-        synchronized (initialLock) {
-            if (initial == null) {
-                throw new IllegalStateException();
-            }
-            ByteBuffer ref = initial.get();
-            ref = ref.hasRemaining() ? Utils.copy(ref)
-                    : Utils.EMPTY_BYTEBUFFER;
-            initial = null;
-            return ref;
-        }
-    }
-
-    @Override
-    public long write(ByteBuffer[] src, int offset, int len) throws IOException {
-        // this makes the whitebox driver test fail.
-        return detachedChannel.write(src, offset, len);
-    }
-
-    @Override
-    public void shutdownInput() throws IOException {
-        detachedChannel.shutdownInput();
-    }
-
-    @Override
-    public void shutdownOutput() throws IOException {
-        detachedChannel.shutdownOutput();
-    }
-
-    @Override
-    public void close() throws IOException {
-        detachedChannel.close();
-    }
-
-    @Override
-    public String toString() {
-        return super.toString()+"("+ detachedChannel.toString() + ")";
-    }
-
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RedirectFilter.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.net.URI;
-import jdk.incubator.http.internal.common.Utils;
-
-class RedirectFilter implements HeaderFilter {
-
-    HttpRequestImpl request;
-    HttpClientImpl client;
-    HttpClient.Redirect policy;
-    String method;
-    MultiExchange<?> exchange;
-    static final int DEFAULT_MAX_REDIRECTS = 5;
-    URI uri;
-
-    static final int max_redirects = Utils.getIntegerNetProperty(
-            "jdk.httpclient.redirects.retrylimit", DEFAULT_MAX_REDIRECTS
-    );
-
-    // A public no-arg constructor is required by FilterFactory
-    public RedirectFilter() {}
-
-    @Override
-    public synchronized void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException {
-        this.request = r;
-        this.client = e.client();
-        this.policy = client.followRedirects();
-
-        this.method = r.method();
-        this.uri = r.uri();
-        this.exchange = e;
-    }
-
-    @Override
-    public synchronized HttpRequestImpl response(Response 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(Response r) {
-        int rcode = r.statusCode();
-        if (rcode == 200 || policy == HttpClient.Redirect.NEVER) {
-            return null;
-        }
-        if (rcode >= 300 && rcode <= 399) {
-            URI redir = getRedirectedURI(r.headers());
-            if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) {
-                //System.out.println("Redirecting to: " + redir);
-                return new HttpRequestImpl(redir, method, request);
-            } else {
-                //System.out.println("Redirect: giving up");
-                return null;
-            }
-        }
-        return null;
-    }
-
-    private URI getRedirectedURI(HttpHeaders headers) {
-        URI redirectedURI;
-        redirectedURI = headers.firstValue("Location")
-                .map(URI::create)
-                .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(URI redir) {
-        String newScheme = redir.getScheme();
-        String oldScheme = uri.getScheme();
-        switch (policy) {
-            case ALWAYS:
-                return true;
-            case NEVER:
-                return false;
-            case SECURE:
-                return newScheme.equalsIgnoreCase("https");
-            case SAME_PROTOCOL:
-                return newScheme.equalsIgnoreCase(oldScheme);
-            default:
-                throw new InternalError();
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RequestPublishers.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,375 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Publisher;
-import java.util.function.Supplier;
-import jdk.incubator.http.HttpRequest.BodyPublisher;
-import jdk.incubator.http.internal.common.Utils;
-
-class RequestPublishers {
-
-    static class ByteArrayPublisher implements HttpRequest.BodyPublisher {
-        private volatile Flow.Publisher<ByteBuffer> delegate;
-        private final int length;
-        private final byte[] content;
-        private final int offset;
-        private final int bufSize;
-
-        ByteArrayPublisher(byte[] content) {
-            this(content, 0, content.length);
-        }
-
-        ByteArrayPublisher(byte[] content, int offset, int length) {
-            this(content, offset, length, Utils.BUFSIZE);
-        }
-
-        /* bufSize exposed for testing purposes */
-        ByteArrayPublisher(byte[] content, int offset, int length, int bufSize) {
-            this.content = content;
-            this.offset = offset;
-            this.length = length;
-            this.bufSize = bufSize;
-        }
-
-        List<ByteBuffer> copy(byte[] content, int offset, int length) {
-            List<ByteBuffer> bufs = new ArrayList<>();
-            while (length > 0) {
-                ByteBuffer b = ByteBuffer.allocate(Math.min(bufSize, length));
-                int max = b.capacity();
-                int tocopy = Math.min(max, length);
-                b.put(content, offset, tocopy);
-                offset += tocopy;
-                length -= tocopy;
-                b.flip();
-                bufs.add(b);
-            }
-            return bufs;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            List<ByteBuffer> copy = copy(content, offset, length);
-            this.delegate = new PullPublisher<>(copy);
-            delegate.subscribe(subscriber);
-        }
-
-        @Override
-        public long contentLength() {
-            return length;
-        }
-    }
-
-    // This implementation has lots of room for improvement.
-    static class IterablePublisher implements HttpRequest.BodyPublisher {
-        private volatile Flow.Publisher<ByteBuffer> delegate;
-        private final Iterable<byte[]> content;
-        private volatile long contentLength;
-
-        IterablePublisher(Iterable<byte[]> content) {
-            this.content = Objects.requireNonNull(content);
-        }
-
-        // The ByteBufferIterator will iterate over the byte[] arrays in
-        // the content one at the time.
-        //
-        class ByteBufferIterator implements Iterator<ByteBuffer> {
-            final ConcurrentLinkedQueue<ByteBuffer> buffers = new ConcurrentLinkedQueue<>();
-            final Iterator<byte[]> iterator = content.iterator();
-            @Override
-            public boolean hasNext() {
-                return !buffers.isEmpty() || iterator.hasNext();
-            }
-
-            @Override
-            public ByteBuffer next() {
-                ByteBuffer buffer = buffers.poll();
-                while (buffer == null) {
-                    copy();
-                    buffer = buffers.poll();
-                }
-                return buffer;
-            }
-
-            ByteBuffer getBuffer() {
-                return Utils.getBuffer();
-            }
-
-            void copy() {
-                byte[] bytes = iterator.next();
-                int length = bytes.length;
-                if (length == 0 && iterator.hasNext()) {
-                    // avoid inserting empty buffers, except
-                    // if that's the last.
-                    return;
-                }
-                int offset = 0;
-                do {
-                    ByteBuffer b = getBuffer();
-                    int max = b.capacity();
-
-                    int tocopy = Math.min(max, length);
-                    b.put(bytes, offset, tocopy);
-                    offset += tocopy;
-                    length -= tocopy;
-                    b.flip();
-                    buffers.add(b);
-                } while (length > 0);
-            }
-        }
-
-        public Iterator<ByteBuffer> iterator() {
-            return new ByteBufferIterator();
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            Iterable<ByteBuffer> iterable = this::iterator;
-            this.delegate = new PullPublisher<>(iterable);
-            delegate.subscribe(subscriber);
-        }
-
-        static long computeLength(Iterable<byte[]> bytes) {
-            long len = 0;
-            for (byte[] b : bytes) {
-                len = Math.addExact(len, (long)b.length);
-            }
-            return len;
-        }
-
-        @Override
-        public long contentLength() {
-            if (contentLength == 0) {
-                synchronized(this) {
-                    if (contentLength == 0) {
-                        contentLength = computeLength(content);
-                    }
-                }
-            }
-            return contentLength;
-        }
-    }
-
-    static class StringPublisher extends ByteArrayPublisher {
-        public StringPublisher(String content, Charset charset) {
-            super(content.getBytes(charset));
-        }
-    }
-
-    static class EmptyPublisher implements HttpRequest.BodyPublisher {
-        private final Flow.Publisher<ByteBuffer> delegate =
-                new PullPublisher<ByteBuffer>(Collections.emptyList(), null);
-
-        @Override
-        public long contentLength() {
-            return 0;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            delegate.subscribe(subscriber);
-        }
-    }
-
-    static class FilePublisher implements BodyPublisher  {
-        private final File file;
-        private volatile AccessControlContext acc;
-
-        FilePublisher(Path name) {
-            file = name.toFile();
-        }
-
-        void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            if (System.getSecurityManager() != null && acc == null)
-                throw new InternalError(
-                        "Unexpected null acc when security manager has been installed");
-
-            InputStream is;
-            try {
-                PrivilegedExceptionAction<FileInputStream> pa =
-                        () -> new FileInputStream(file);
-                is = AccessController.doPrivileged(pa, acc);
-            } catch (PrivilegedActionException pae) {
-                throw new UncheckedIOException((IOException)pae.getCause());
-            }
-            PullPublisher<ByteBuffer> publisher =
-                    new PullPublisher<>(() -> new StreamIterator(is));
-            publisher.subscribe(subscriber);
-        }
-
-        @Override
-        public long contentLength() {
-            assert System.getSecurityManager() != null ? acc != null: true;
-            PrivilegedAction<Long> pa = () -> file.length();
-            return AccessController.doPrivileged(pa, acc);
-        }
-    }
-
-    /**
-     * Reads one buffer ahead all the time, blocking in hasNext()
-     */
-    static class StreamIterator implements Iterator<ByteBuffer> {
-        final InputStream is;
-        final Supplier<? extends ByteBuffer> bufSupplier;
-        volatile ByteBuffer nextBuffer;
-        volatile boolean need2Read = true;
-        volatile boolean haveNext;
-
-        StreamIterator(InputStream is) {
-            this(is, Utils::getBuffer);
-        }
-
-        StreamIterator(InputStream is, Supplier<? extends ByteBuffer> bufSupplier) {
-            this.is = is;
-            this.bufSupplier = bufSupplier;
-        }
-
-//        Throwable error() {
-//            return error;
-//        }
-
-        private int read() {
-            nextBuffer = bufSupplier.get();
-            nextBuffer.clear();
-            byte[] buf = nextBuffer.array();
-            int offset = nextBuffer.arrayOffset();
-            int cap = nextBuffer.capacity();
-            try {
-                int n = is.read(buf, offset, cap);
-                if (n == -1) {
-                    is.close();
-                    return -1;
-                }
-                //flip
-                nextBuffer.limit(n);
-                nextBuffer.position(0);
-                return n;
-            } catch (IOException ex) {
-                return -1;
-            }
-        }
-
-        @Override
-        public synchronized boolean hasNext() {
-            if (need2Read) {
-                haveNext = read() != -1;
-                if (haveNext) {
-                    need2Read = false;
-                }
-                return haveNext;
-            }
-            return haveNext;
-        }
-
-        @Override
-        public synchronized ByteBuffer next() {
-            if (!hasNext()) {
-                throw new NoSuchElementException();
-            }
-            need2Read = true;
-            return nextBuffer;
-        }
-
-    }
-
-    static class InputStreamPublisher implements BodyPublisher {
-        private final Supplier<? extends InputStream> streamSupplier;
-
-        InputStreamPublisher(Supplier<? extends InputStream> streamSupplier) {
-            this.streamSupplier = Objects.requireNonNull(streamSupplier);
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            PullPublisher<ByteBuffer> publisher;
-            InputStream is = streamSupplier.get();
-            if (is == null) {
-                Throwable t = new IOException("streamSupplier returned null");
-                publisher = new PullPublisher<>(null, t);
-            } else  {
-                publisher = new PullPublisher<>(iterableOf(is), null);
-            }
-            publisher.subscribe(subscriber);
-        }
-
-        protected Iterable<ByteBuffer> iterableOf(InputStream is) {
-            return () -> new StreamIterator(is);
-        }
-
-        @Override
-        public long contentLength() {
-            return -1;
-        }
-    }
-
-    static final class PublisherAdapter implements BodyPublisher {
-
-        private final Publisher<? extends ByteBuffer> publisher;
-        private final long contentLength;
-
-        PublisherAdapter(Publisher<? extends ByteBuffer> publisher,
-                         long contentLength) {
-            this.publisher = Objects.requireNonNull(publisher);
-            this.contentLength = contentLength;
-        }
-
-        @Override
-        public final long contentLength() {
-            return contentLength;
-        }
-
-        @Override
-        public final void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            publisher.subscribe(subscriber);
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Response.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.URI;
-
-/**
- * Response headers and status code.
- */
-class Response {
-    final HttpHeaders headers;
-    final int statusCode;
-    final HttpRequestImpl request;
-    final Exchange<?> exchange;
-    final HttpClient.Version version;
-    final boolean isConnectResponse;
-
-    Response(HttpRequestImpl req,
-             Exchange<?> exchange,
-             HttpHeaders headers,
-             int statusCode,
-             HttpClient.Version version) {
-        this(req, exchange, headers, statusCode, version,
-                "CONNECT".equalsIgnoreCase(req.method()));
-    }
-
-    Response(HttpRequestImpl req,
-             Exchange<?> exchange,
-             HttpHeaders headers,
-             int statusCode,
-             HttpClient.Version version,
-             boolean isConnectResponse) {
-        this.headers = headers;
-        this.request = req;
-        this.version = version;
-        this.exchange = exchange;
-        this.statusCode = statusCode;
-        this.isConnectResponse = isConnectResponse;
-    }
-
-    HttpRequestImpl request() {
-        return request;
-    }
-
-    HttpClient.Version version() {
-        return version;
-    }
-
-    HttpHeaders headers() {
-        return headers;
-    }
-
-//    Exchange<?> exchange() {
-//        return exchange;
-//    }
-
-    int statusCode() {
-        return statusCode;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        String method = request().method();
-        URI uri = request().uri();
-        String uristring = uri == null ? "" : uri.toString();
-        sb.append('(')
-          .append(method)
-          .append(" ")
-          .append(uristring)
-          .append(") ")
-          .append(statusCode());
-        return sb.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseContent.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,465 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Implements chunked/fixed transfer encodings of HTTP/1.1 responses.
- *
- * Call pushBody() to read the body (blocking). Data and errors are provided
- * to given Consumers. After final buffer delivered, empty optional delivered
- */
-class ResponseContent {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-
-    final HttpResponse.BodySubscriber<?> pusher;
-    final int contentLength;
-    final HttpHeaders headers;
-    // this needs to run before we complete the body
-    // so that connection can be returned to pool
-    private final Runnable onFinished;
-    private final String dbgTag;
-
-    ResponseContent(HttpConnection connection,
-                    int contentLength,
-                    HttpHeaders h,
-                    HttpResponse.BodySubscriber<?> userSubscriber,
-                    Runnable onFinished)
-    {
-        this.pusher = userSubscriber;
-        this.contentLength = contentLength;
-        this.headers = h;
-        this.onFinished = onFinished;
-        this.dbgTag = connection.dbgString() + "/ResponseContent";
-    }
-
-    static final int LF = 10;
-    static final int CR = 13;
-
-    private boolean chunkedContent, chunkedContentInitialized;
-
-    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;
-    }
-
-    interface BodyParser extends Consumer<ByteBuffer> {
-        void onSubscribe(AbstractSubscription sub);
-    }
-
-    // Returns a parser that will take care of parsing the received byte
-    // buffers and forward them to the BodySubscriber.
-    // When the parser is done, it will call onComplete.
-    // If parsing was successful, the throwable parameter will be null.
-    // Otherwise it will be the exception that occurred
-    // Note: revisit: it might be better to use a CompletableFuture than
-    //       a completion handler.
-    BodyParser getBodyParser(Consumer<Throwable> onComplete)
-        throws IOException {
-        if (contentChunked()) {
-            return new ChunkedBodyParser(onComplete);
-        } else {
-            return new FixedLengthBodyParser(contentLength, onComplete);
-        }
-    }
-
-
-    static enum ChunkState {READING_LENGTH, READING_DATA, DONE}
-    class ChunkedBodyParser implements BodyParser {
-        final ByteBuffer READMORE = Utils.EMPTY_BYTEBUFFER;
-        final Consumer<Throwable> onComplete;
-        final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-        final String dbgTag = ResponseContent.this.dbgTag + "/ChunkedBodyParser";
-
-        volatile Throwable closedExceptionally;
-        volatile int partialChunklen = 0; // partially read chunk len
-        volatile int chunklen = -1;  // number of bytes in chunk
-        volatile int bytesremaining;  // number of bytes in chunk left to be read incl CRLF
-        volatile boolean cr = false;  // tryReadChunkLength has found CR
-        volatile int bytesToConsume;  // number of bytes that still need to be consumed before proceeding
-        volatile ChunkState state = ChunkState.READING_LENGTH; // current state
-        volatile AbstractSubscription sub;
-        ChunkedBodyParser(Consumer<Throwable> onComplete) {
-            this.onComplete = onComplete;
-        }
-
-        String dbgString() {
-            return dbgTag;
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription sub) {
-            debug.log(Level.DEBUG, () ->  "onSubscribe: "
-                        + pusher.getClass().getName());
-            pusher.onSubscribe(this.sub = sub);
-        }
-
-        @Override
-        public void accept(ByteBuffer b) {
-            if (closedExceptionally != null) {
-                debug.log(Level.DEBUG, () ->  "already closed: "
-                            + closedExceptionally);
-                return;
-            }
-            boolean completed = false;
-            try {
-                List<ByteBuffer> out = new ArrayList<>();
-                do {
-                    if (tryPushOneHunk(b, out))  {
-                        // We're done! (true if the final chunk was parsed).
-                        if (!out.isEmpty()) {
-                            // push what we have and complete
-                            // only reduce demand if we actually push something.
-                            // we would not have come here if there was no
-                            // demand.
-                            boolean hasDemand = sub.demand().tryDecrement();
-                            assert hasDemand;
-                            pusher.onNext(Collections.unmodifiableList(out));
-                        }
-                        debug.log(Level.DEBUG, () ->  "done!");
-                        assert closedExceptionally == null;
-                        assert state == ChunkState.DONE;
-                        onFinished.run();
-                        pusher.onComplete();
-                        completed = true;
-                        onComplete.accept(closedExceptionally); // should be null
-                        break;
-                    }
-                    // the buffer may contain several hunks, and therefore
-                    // we must loop while it's not exhausted.
-                } while (b.hasRemaining());
-
-                if (!completed && !out.isEmpty()) {
-                    // push what we have.
-                    // only reduce demand if we actually push something.
-                    // we would not have come here if there was no
-                    // demand.
-                    boolean hasDemand = sub.demand().tryDecrement();
-                    assert hasDemand;
-                    pusher.onNext(Collections.unmodifiableList(out));
-                }
-                assert state == ChunkState.DONE || !b.hasRemaining();
-            } catch(Throwable t) {
-                closedExceptionally = t;
-                if (!completed) onComplete.accept(t);
-            }
-        }
-
-        // reads and returns chunklen. Position of chunkbuf is first byte
-        // of chunk on return. chunklen includes the CR LF at end of chunk
-        // returns -1 if needs more bytes
-        private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException {
-            assert state == ChunkState.READING_LENGTH;
-            while (chunkbuf.hasRemaining()) {
-                int c = chunkbuf.get();
-                if (cr) {
-                    if (c == LF) {
-                        return partialChunklen;
-                    } else {
-                        throw new IOException("invalid chunk header");
-                    }
-                }
-                if (c == CR) {
-                    cr = true;
-                } else {
-                    int digit = toDigit(c);
-                    partialChunklen = partialChunklen * 16 + digit;
-                }
-            }
-            return -1;
-        }
-
-
-        // try to consume as many bytes as specified by bytesToConsume.
-        // returns the number of bytes that still need to be consumed.
-        // In practice this method is only called to consume one CRLF pair
-        // with bytesToConsume set to 2, so it will only return 0 (if completed),
-        // 1, or 2 (if chunkbuf doesn't have the 2 chars).
-        private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException {
-            int n = bytesToConsume;
-            if (n > 0) {
-                int e = Math.min(chunkbuf.remaining(), n);
-
-                // verifies some assertions
-                // this methods is called only to consume CRLF
-                if (Utils.ASSERTIONSENABLED) {
-                    assert n <= 2 && e <= 2;
-                    ByteBuffer tmp = chunkbuf.slice();
-                    // if n == 2 assert that we will first consume CR
-                    assert (n == 2 && e > 0) ? tmp.get() == CR : true;
-                    // if n == 1 || n == 2 && e == 2 assert that we then consume LF
-                    assert (n == 1 || e == 2) ? tmp.get() == LF : true;
-                }
-
-                chunkbuf.position(chunkbuf.position() + e);
-                n -= e;
-                bytesToConsume = n;
-            }
-            assert n >= 0;
-            return n;
-        }
-
-        /**
-         * Returns a ByteBuffer containing chunk of data or a "hunk" of data
-         * (a chunk of a chunk if the chunk size is larger than our ByteBuffers).
-         * If the given chunk does not have enough data this method return
-         * an empty ByteBuffer (READMORE).
-         * If we encounter the final chunk (an empty chunk) this method
-         * returns null.
-         */
-        ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException {
-            int unfulfilled = bytesremaining;
-            int toconsume = bytesToConsume;
-            ChunkState st = state;
-            if (st == ChunkState.READING_LENGTH && chunklen == -1) {
-                debug.log(Level.DEBUG, () ->  "Trying to read chunk len"
-                        + " (remaining in buffer:"+chunk.remaining()+")");
-                int clen = chunklen = tryReadChunkLen(chunk);
-                if (clen == -1) return READMORE;
-                debug.log(Level.DEBUG, "Got chunk len %d", clen);
-                cr = false; partialChunklen = 0;
-                unfulfilled = bytesremaining =  clen;
-                if (clen == 0) toconsume = bytesToConsume = 2; // that was the last chunk
-                else st = state = ChunkState.READING_DATA; // read the data
-            }
-
-            if (toconsume > 0) {
-                debug.log(Level.DEBUG,
-                        "Trying to consume bytes: %d (remaining in buffer: %s)",
-                        toconsume, chunk.remaining());
-                if (tryConsumeBytes(chunk) > 0) {
-                    return READMORE;
-                }
-            }
-
-            toconsume = bytesToConsume;
-            assert toconsume == 0;
-
-
-            if (st == ChunkState.READING_LENGTH) {
-                // we will come here only if chunklen was 0, after having
-                // consumed the trailing CRLF
-                int clen = chunklen;
-                assert clen == 0;
-                debug.log(Level.DEBUG, "No more chunks: %d", clen);
-                // the DONE state is not really needed but it helps with
-                // assertions...
-                state = ChunkState.DONE;
-                return null;
-            }
-
-            int clen = chunklen;
-            assert clen > 0;
-            assert st == ChunkState.READING_DATA;
-
-            ByteBuffer returnBuffer = READMORE; // May be a hunk or a chunk
-            if (unfulfilled > 0) {
-                int bytesread = chunk.remaining();
-                debug.log(Level.DEBUG, "Reading chunk: available %d, needed %d",
-                          bytesread, unfulfilled);
-
-                int bytes2return = Math.min(bytesread, unfulfilled);
-                debug.log(Level.DEBUG,  "Returning chunk bytes: %d", bytes2return);
-                returnBuffer = Utils.sliceWithLimitedCapacity(chunk, bytes2return).asReadOnlyBuffer();
-                unfulfilled = bytesremaining -= bytes2return;
-                if (unfulfilled == 0) bytesToConsume = 2;
-            }
-
-            assert unfulfilled >= 0;
-
-            if (unfulfilled == 0) {
-                debug.log(Level.DEBUG,
-                        "No more bytes to read - %d yet to consume.",
-                        unfulfilled);
-                // check whether the trailing CRLF is consumed, try to
-                // consume it if not. If tryConsumeBytes needs more bytes
-                // then we will come back here later - skipping the block
-                // that reads data because remaining==0, and finding
-                // that the two bytes are now consumed.
-                if (tryConsumeBytes(chunk) == 0) {
-                    // we're done for this chunk! reset all states and
-                    // prepare to read the next chunk.
-                    chunklen = -1;
-                    partialChunklen = 0;
-                    cr = false;
-                    state = ChunkState.READING_LENGTH;
-                    debug.log(Level.DEBUG, "Ready to read next chunk");
-                }
-            }
-            if (returnBuffer == READMORE) {
-                debug.log(Level.DEBUG, "Need more data");
-            }
-            return returnBuffer;
-        }
-
-
-        // Attempt to parse and push one hunk from the buffer.
-        // Returns true if the final chunk was parsed.
-        // Returns false if we need to push more chunks.
-        private boolean tryPushOneHunk(ByteBuffer b, List<ByteBuffer> out)
-                throws IOException {
-            assert state != ChunkState.DONE;
-            ByteBuffer b1 = tryReadOneHunk(b);
-            if (b1 != null) {
-                //assert b1.hasRemaining() || b1 == READMORE;
-                if (b1.hasRemaining()) {
-                    debug.log(Level.DEBUG, "Sending chunk to consumer (%d)",
-                              b1.remaining());
-                    out.add(b1);
-                    debug.log(Level.DEBUG, "Chunk sent.");
-                }
-                return false; // we haven't parsed the final chunk yet.
-            } else {
-                return true; // we're done! the final chunk was parsed.
-            }
-        }
-
-        private int toDigit(int b) throws IOException {
-            if (b >= 0x30 && b <= 0x39) {
-                return b - 0x30;
-            }
-            if (b >= 0x41 && b <= 0x46) {
-                return b - 0x41 + 10;
-            }
-            if (b >= 0x61 && b <= 0x66) {
-                return b - 0x61 + 10;
-            }
-            throw new IOException("Invalid chunk header byte " + b);
-        }
-
-    }
-
-    class FixedLengthBodyParser implements BodyParser {
-        final int contentLength;
-        final Consumer<Throwable> onComplete;
-        final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-        final String dbgTag = ResponseContent.this.dbgTag + "/FixedLengthBodyParser";
-        volatile int remaining;
-        volatile Throwable closedExceptionally;
-        volatile AbstractSubscription sub;
-        FixedLengthBodyParser(int contentLength, Consumer<Throwable> onComplete) {
-            this.contentLength = this.remaining = contentLength;
-            this.onComplete = onComplete;
-        }
-
-        String dbgString() {
-            return dbgTag;
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription sub) {
-            debug.log(Level.DEBUG, () -> "length="
-                        + contentLength +", onSubscribe: "
-                        + pusher.getClass().getName());
-            pusher.onSubscribe(this.sub = sub);
-            try {
-                if (contentLength == 0) {
-                    onFinished.run();
-                    pusher.onComplete();
-                    onComplete.accept(null);
-                }
-            } catch (Throwable t) {
-                closedExceptionally = t;
-                try {
-                    pusher.onError(t);
-                } finally {
-                    onComplete.accept(t);
-                }
-            }
-        }
-
-        @Override
-        public void accept(ByteBuffer b) {
-            if (closedExceptionally != null) {
-                debug.log(Level.DEBUG, () -> "already closed: "
-                            + closedExceptionally);
-                return;
-            }
-            boolean completed = false;
-            try {
-                int unfulfilled = remaining;
-                debug.log(Level.DEBUG, "Parser got %d bytes (%d remaining / %d)",
-                        b.remaining(), unfulfilled, contentLength);
-                assert unfulfilled != 0 || contentLength == 0 || b.remaining() == 0;
-
-                if (unfulfilled == 0 && contentLength > 0) return;
-
-                if (b.hasRemaining() && unfulfilled > 0) {
-                    // only reduce demand if we actually push something.
-                    // we would not have come here if there was no
-                    // demand.
-                    boolean hasDemand = sub.demand().tryDecrement();
-                    assert hasDemand;
-                    int amount = Math.min(b.remaining(), unfulfilled);
-                    unfulfilled = remaining -= amount;
-                    ByteBuffer buffer = Utils.sliceWithLimitedCapacity(b, amount);
-                    pusher.onNext(List.of(buffer.asReadOnlyBuffer()));
-                }
-                if (unfulfilled == 0) {
-                    // We're done! All data has been received.
-                    assert closedExceptionally == null;
-                    onFinished.run();
-                    pusher.onComplete();
-                    completed = true;
-                    onComplete.accept(closedExceptionally); // should be null
-                } else {
-                    assert b.remaining() == 0;
-                }
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG, "Unexpected exception", t);
-                closedExceptionally = t;
-                if (!completed) {
-                    onComplete.accept(t);
-                }
-            }
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,489 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLEngineResult.Status;
-import javax.net.ssl.*;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
-
-/**
- * Implements the mechanics of SSL by managing an SSLEngine object.
- * <p>
- * This class is only used to implement the {@link
- * AbstractAsyncSSLConnection.SSLConnectionChannel} which is handed of
- * to RawChannelImpl when creating a WebSocket.
- */
-class SSLDelegate {
-
-    final SSLEngine engine;
-    final EngineWrapper wrapper;
-    final Lock handshaking = new ReentrantLock();
-    final SocketChannel chan;
-
-    SSLDelegate(SSLEngine eng, SocketChannel chan)
-    {
-        this.engine = eng;
-        this.chan = chan;
-        this.wrapper = new EngineWrapper(chan, engine);
-    }
-
-    // alpn[] may be null
-//    SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn, String sn)
-//        throws IOException
-//    {
-//        serverName = sn;
-//        SSLContext context = client.sslContext();
-//        engine = context.createSSLEngine();
-//        engine.setUseClientMode(true);
-//        SSLParameters sslp = client.sslParameters();
-//        sslParameters = Utils.copySSLParameters(sslp);
-//        if (sn != null) {
-//            SNIHostName sni = new SNIHostName(sn);
-//            sslParameters.setServerNames(List.of(sni));
-//        }
-//        if (alpn != null) {
-//            sslParameters.setApplicationProtocols(alpn);
-//            Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn));
-//        } else {
-//            Log.logSSL("SSLDelegate: No application protocols proposed");
-//        }
-//        engine.setSSLParameters(sslParameters);
-//        wrapper = new EngineWrapper(chan, engine);
-//        this.chan = chan;
-//        this.client = client;
-//    }
-
-//    SSLParameters getSSLParameters() {
-//        return sslParameters;
-//    }
-
-    static long countBytes(ByteBuffer[] buffers, int start, int number) {
-        long c = 0;
-        for (int i=0; i<number; i++) {
-            c+= buffers[start+i].remaining();
-        }
-        return c;
-    }
-
-
-    static class WrapperResult {
-        static WrapperResult createOK() {
-            WrapperResult r = new WrapperResult();
-            r.buf = null;
-            r.result = new SSLEngineResult(Status.OK, NOT_HANDSHAKING, 0, 0);
-            return r;
-        }
-        SSLEngineResult result;
-
-        ByteBuffer buf; // buffer containing result data
-    }
-
-    int app_buf_size;
-    int packet_buf_size;
-
-    enum BufType {
-        PACKET,
-        APPLICATION
-    }
-
-    ByteBuffer allocate (BufType type) {
-        return allocate (type, -1);
-    }
-
-    // TODO: Use buffer pool for this
-    ByteBuffer allocate (BufType type, int len) {
-        assert engine != null;
-        synchronized (this) {
-            int size;
-            if (type == BufType.PACKET) {
-                if (packet_buf_size == 0) {
-                    SSLSession sess = engine.getSession();
-                    packet_buf_size = sess.getPacketBufferSize();
-                }
-                if (len > packet_buf_size) {
-                    packet_buf_size = len;
-                }
-                size = packet_buf_size;
-            } else {
-                if (app_buf_size == 0) {
-                    SSLSession sess = engine.getSession();
-                    app_buf_size = sess.getApplicationBufferSize();
-                }
-                if (len > app_buf_size) {
-                    app_buf_size = len;
-                }
-                size = app_buf_size;
-            }
-            return ByteBuffer.allocate (size);
-        }
-    }
-
-    /* reallocates the buffer by :-
-     * 1. creating a new buffer double the size of the old one
-     * 2. putting the contents of the old buffer into the new one
-     * 3. set xx_buf_size to the new size if it was smaller than new size
-     *
-     * flip is set to true if the old buffer needs to be flipped
-     * before it is copied.
-     */
-    private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {
-        // TODO: there should be the linear growth, rather than exponential as
-        // we definitely know the maximum amount of space required to unwrap
-        synchronized (this) {
-            int nsize = 2 * b.capacity();
-            ByteBuffer n = allocate (type, nsize);
-            if (flip) {
-                b.flip();
-            }
-            n.put(b);
-            b = n;
-        }
-        return b;
-    }
-
-    /**
-     * This is a thin wrapper over SSLEngine and the SocketChannel, which
-     * guarantees the ordering of wraps/unwraps with respect to the underlying
-     * channel read/writes. It handles the UNDER/OVERFLOW status codes
-     * It does not handle the handshaking status codes, or the CLOSED status code
-     * though once the engine is closed, any attempt to read/write to it
-     * will get an exception.  The overall result is returned.
-     * It functions synchronously/blocking
-     */
-    class EngineWrapper {
-
-        SocketChannel chan;
-        SSLEngine engine;
-        final Object wrapLock;
-        final Object unwrapLock;
-        ByteBuffer unwrap_src, wrap_dst;
-        boolean closed = false;
-        int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
-
-        EngineWrapper (SocketChannel chan, SSLEngine engine) {
-            this.chan = chan;
-            this.engine = engine;
-            wrapLock = new Object();
-            unwrapLock = new Object();
-            unwrap_src = allocate(BufType.PACKET);
-            wrap_dst = allocate(BufType.PACKET);
-        }
-
-//        void close () throws IOException {
-//        }
-
-        WrapperResult wrapAndSend(ByteBuffer src, boolean ignoreClose)
-            throws IOException
-        {
-            ByteBuffer[] buffers = new ByteBuffer[1];
-            buffers[0] = src;
-            return wrapAndSend(buffers, 0, 1, ignoreClose);
-        }
-
-        /* try to wrap and send the data in src. Handles OVERFLOW.
-         * Might block if there is an outbound blockage or if another
-         * thread is calling wrap(). Also, might not send any data
-         * if an unwrap is needed.
-         */
-        WrapperResult wrapAndSend(ByteBuffer[] src,
-                                  int offset,
-                                  int len,
-                                  boolean ignoreClose)
-            throws IOException
-        {
-            if (closed && !ignoreClose) {
-                throw new IOException ("Engine is closed");
-            }
-            Status status;
-            WrapperResult r = new WrapperResult();
-            synchronized (wrapLock) {
-                wrap_dst.clear();
-                do {
-                    r.result = engine.wrap (src, offset, len, wrap_dst);
-                    status = r.result.getStatus();
-                    if (status == Status.BUFFER_OVERFLOW) {
-                        wrap_dst = realloc (wrap_dst, true, BufType.PACKET);
-                    }
-                } while (status == Status.BUFFER_OVERFLOW);
-                if (status == Status.CLOSED && !ignoreClose) {
-                    closed = true;
-                    return r;
-                }
-                if (r.result.bytesProduced() > 0) {
-                    wrap_dst.flip();
-                    int l = wrap_dst.remaining();
-                    assert l == r.result.bytesProduced();
-                    while (l>0) {
-                        l -= chan.write (wrap_dst);
-                    }
-                }
-            }
-            return r;
-        }
-
-        /* block until a complete message is available and return it
-         * in dst, together with the Result. dst may have been re-allocated
-         * so caller should check the returned value in Result
-         * If handshaking is in progress then, possibly no data is returned
-         */
-        WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
-            Status status;
-            WrapperResult r = new WrapperResult();
-            r.buf = dst;
-            if (closed) {
-                throw new IOException ("Engine is closed");
-            }
-            boolean needData;
-            if (u_remaining > 0) {
-                unwrap_src.compact();
-                unwrap_src.flip();
-                needData = false;
-            } else {
-                unwrap_src.clear();
-                needData = true;
-            }
-            synchronized (unwrapLock) {
-                int x;
-                do {
-                    if (needData) {
-                        x = chan.read (unwrap_src);
-                        if (x == -1) {
-                            throw new IOException ("connection closed for reading");
-                        }
-                        unwrap_src.flip();
-                    }
-                    r.result = engine.unwrap (unwrap_src, r.buf);
-                    status = r.result.getStatus();
-                    if (status == Status.BUFFER_UNDERFLOW) {
-                        if (unwrap_src.limit() == unwrap_src.capacity()) {
-                            /* buffer not big enough */
-                            unwrap_src = realloc (
-                                unwrap_src, false, BufType.PACKET
-                            );
-                        } else {
-                            /* Buffer not full, just need to read more
-                             * data off the channel. Reset pointers
-                             * for reading off SocketChannel
-                             */
-                            unwrap_src.position (unwrap_src.limit());
-                            unwrap_src.limit (unwrap_src.capacity());
-                        }
-                        needData = true;
-                    } else if (status == Status.BUFFER_OVERFLOW) {
-                        r.buf = realloc (r.buf, true, BufType.APPLICATION);
-                        needData = false;
-                    } else if (status == Status.CLOSED) {
-                        closed = true;
-                        r.buf.flip();
-                        return r;
-                    }
-                } while (status != Status.OK);
-            }
-            u_remaining = unwrap_src.remaining();
-            return r;
-        }
-    }
-
-//    WrapperResult sendData (ByteBuffer src) throws IOException {
-//        ByteBuffer[] buffers = new ByteBuffer[1];
-//        buffers[0] = src;
-//        return sendData(buffers, 0, 1);
-//    }
-
-    /**
-     * send the data in the given ByteBuffer. If a handshake is needed
-     * then this is handled within this method. When this call returns,
-     * all of the given user data has been sent and any handshake has been
-     * completed. Caller should check if engine has been closed.
-     */
-    WrapperResult sendData (ByteBuffer[] src, int offset, int len) throws IOException {
-        WrapperResult r = WrapperResult.createOK();
-        while (countBytes(src, offset, len) > 0) {
-            r = wrapper.wrapAndSend(src, offset, len, false);
-            Status status = r.result.getStatus();
-            if (status == Status.CLOSED) {
-                doClosure ();
-                return r;
-            }
-            HandshakeStatus hs_status = r.result.getHandshakeStatus();
-            if (hs_status != HandshakeStatus.FINISHED &&
-                hs_status != HandshakeStatus.NOT_HANDSHAKING)
-            {
-                doHandshake(hs_status);
-            }
-        }
-        return r;
-    }
-
-    /**
-     * read data thru the engine into the given ByteBuffer. If the
-     * given buffer was not large enough, a new one is allocated
-     * and returned. This call handles handshaking automatically.
-     * Caller should check if engine has been closed.
-     */
-    WrapperResult recvData (ByteBuffer dst) throws IOException {
-        /* we wait until some user data arrives */
-        int mark = dst.position();
-        WrapperResult r = null;
-        int pos = dst.position();
-        while (dst.position() == pos) {
-            r = wrapper.recvAndUnwrap (dst);
-            dst = (r.buf != dst) ? r.buf: dst;
-            Status status = r.result.getStatus();
-            if (status == Status.CLOSED) {
-                doClosure ();
-                return r;
-            }
-
-            HandshakeStatus hs_status = r.result.getHandshakeStatus();
-            if (hs_status != HandshakeStatus.FINISHED &&
-                hs_status != HandshakeStatus.NOT_HANDSHAKING)
-            {
-                doHandshake (hs_status);
-            }
-        }
-        Utils.flipToMark(dst, mark);
-        return r;
-    }
-
-    /* we've received a close notify. Need to call wrap to send
-     * the response
-     */
-    void doClosure () throws IOException {
-        try {
-            handshaking.lock();
-            ByteBuffer tmp = allocate(BufType.APPLICATION);
-            WrapperResult r;
-            do {
-                tmp.clear();
-                tmp.flip ();
-                r = wrapper.wrapAndSend(tmp, true);
-            } while (r.result.getStatus() != Status.CLOSED);
-        } finally {
-            handshaking.unlock();
-        }
-    }
-
-    /* do the (complete) handshake after acquiring the handshake lock.
-     * If two threads call this at the same time, then we depend
-     * on the wrapper methods being idempotent. eg. if wrapAndSend()
-     * is called with no data to send then there must be no problem
-     */
-    @SuppressWarnings("fallthrough")
-    void doHandshake (HandshakeStatus hs_status) throws IOException {
-        boolean wasBlocking;
-        try {
-            wasBlocking = chan.isBlocking();
-            handshaking.lock();
-            chan.configureBlocking(true);
-            ByteBuffer tmp = allocate(BufType.APPLICATION);
-            while (hs_status != HandshakeStatus.FINISHED &&
-                   hs_status != HandshakeStatus.NOT_HANDSHAKING)
-            {
-                WrapperResult r = null;
-                switch (hs_status) {
-                    case NEED_TASK:
-                        Runnable task;
-                        while ((task = engine.getDelegatedTask()) != null) {
-                            /* run in current thread, because we are already
-                             * running an external Executor
-                             */
-                            task.run();
-                        }
-                        /* fall thru - call wrap again */
-                    case NEED_WRAP:
-                        tmp.clear();
-                        tmp.flip();
-                        r = wrapper.wrapAndSend(tmp, false);
-                        break;
-
-                    case NEED_UNWRAP:
-                        tmp.clear();
-                        r = wrapper.recvAndUnwrap (tmp);
-                        if (r.buf != tmp) {
-                            tmp = r.buf;
-                        }
-                        assert tmp.position() == 0;
-                        break;
-                }
-                hs_status = r.result.getHandshakeStatus();
-            }
-            Log.logSSL(getSessionInfo());
-            if (!wasBlocking) {
-                chan.configureBlocking(false);
-            }
-        } finally {
-            handshaking.unlock();
-        }
-    }
-
-//    static void printParams(SSLParameters p) {
-//        System.out.println("SSLParameters:");
-//        if (p == null) {
-//            System.out.println("Null params");
-//            return;
-//        }
-//        for (String cipher : p.getCipherSuites()) {
-//                System.out.printf("cipher: %s\n", cipher);
-//        }
-//        // JDK 8 EXCL START
-//        for (String approto : p.getApplicationProtocols()) {
-//                System.out.printf("application protocol: %s\n", approto);
-//        }
-//        // JDK 8 EXCL END
-//        for (String protocol : p.getProtocols()) {
-//                System.out.printf("protocol: %s\n", protocol);
-//        }
-//        if (p.getServerNames() != null) {
-//            for (SNIServerName sname : p.getServerNames()) {
-//                System.out.printf("server name: %s\n", sname.toString());
-//            }
-//        }
-//    }
-
-    String getSessionInfo() {
-        StringBuilder sb = new StringBuilder();
-        String application = engine.getApplicationProtocol();
-        SSLSession sess = engine.getSession();
-        String cipher = sess.getCipherSuite();
-        String protocol = sess.getProtocol();
-        sb.append("Handshake complete alpn: ")
-                .append(application)
-                .append(", Cipher: ")
-                .append(cipher)
-                .append(", Protocol: ")
-                .append(protocol);
-        return sb.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SocketTube.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,956 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
-import jdk.incubator.http.internal.common.SequentialScheduler.RestartableTask;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * A SocketTube is a terminal tube plugged directly into the socket.
- * The read subscriber should call {@code subscribe} on the SocketTube before
- * the SocketTube can be subscribed to the write publisher.
- */
-final class SocketTube implements FlowTube {
-
-    static final boolean DEBUG = Utils.DEBUG; // revisit: temporary developer's flag
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    static final AtomicLong IDS = new AtomicLong();
-
-    private final HttpClientImpl client;
-    private final SocketChannel channel;
-    private final Supplier<ByteBuffer> buffersSource;
-    private final Object lock = new Object();
-    private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
-    private final InternalReadPublisher readPublisher;
-    private final InternalWriteSubscriber writeSubscriber;
-    private final long id = IDS.incrementAndGet();
-
-    public SocketTube(HttpClientImpl client, SocketChannel channel,
-                      Supplier<ByteBuffer> buffersSource) {
-        this.client = client;
-        this.channel = channel;
-        this.buffersSource = buffersSource;
-        this.readPublisher = new InternalReadPublisher();
-        this.writeSubscriber = new InternalWriteSubscriber();
-    }
-
-//    private static Flow.Subscription nopSubscription() {
-//        return new Flow.Subscription() {
-//            @Override public void request(long n) { }
-//            @Override public void cancel() { }
-//        };
-//    }
-
-    /**
-     * Returns {@code true} if this flow is finished.
-     * This happens when this flow internal read subscription is completed,
-     * either normally (EOF reading) or exceptionally  (EOF writing, or
-     * underlying socket closed, or some exception occurred while reading or
-     * writing to the socket).
-     *
-     * @return {@code true} if this flow is finished.
-     */
-    public boolean isFinished() {
-        InternalReadPublisher.InternalReadSubscription subscription =
-                readPublisher.subscriptionImpl;
-        return subscription != null && subscription.completed
-                || subscription == null && errorRef.get() != null;
-    }
-
-    // ===================================================================== //
-    //                       Flow.Publisher                                  //
-    // ======================================================================//
-
-    /**
-     * {@inheritDoc }
-     * @apiNote This method should be called first. In particular, the caller
-     *          must ensure that this method must be called by the read
-     *          subscriber before the write publisher can call {@code onSubscribe}.
-     *          Failure to adhere to this contract may result in assertion errors.
-     */
-    @Override
-    public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
-        Objects.requireNonNull(s);
-        assert s instanceof TubeSubscriber : "Expected TubeSubscriber, got:" + s;
-        readPublisher.subscribe(s);
-    }
-
-
-    // ===================================================================== //
-    //                       Flow.Subscriber                                 //
-    // ======================================================================//
-
-    /**
-     * {@inheritDoc }
-     * @apiNote The caller must ensure that {@code subscribe} is called by
-     *          the read subscriber before {@code onSubscribe} is called by
-     *          the write publisher.
-     *          Failure to adhere to this contract may result in assertion errors.
-     */
-    @Override
-    public void onSubscribe(Flow.Subscription subscription) {
-        writeSubscriber.onSubscribe(subscription);
-    }
-
-    @Override
-    public void onNext(List<ByteBuffer> item) {
-        writeSubscriber.onNext(item);
-    }
-
-    @Override
-    public void onError(Throwable throwable) {
-        writeSubscriber.onError(throwable);
-    }
-
-    @Override
-    public void onComplete() {
-        writeSubscriber.onComplete();
-    }
-
-    // ===================================================================== //
-    //                           Events                                      //
-    // ======================================================================//
-
-    /**
-     * A restartable task used to process tasks in sequence.
-     */
-    private static class SocketFlowTask implements RestartableTask {
-        final Runnable task;
-        private final Object monitor = new Object();
-        SocketFlowTask(Runnable task) {
-            this.task = task;
-        }
-        @Override
-        public final void run(DeferredCompleter taskCompleter) {
-            try {
-                // non contentious synchronized for visibility.
-                synchronized(monitor) {
-                    task.run();
-                }
-            } finally {
-                taskCompleter.complete();
-            }
-        }
-    }
-
-    // This is best effort - there's no guarantee that the printed set
-    // of values is consistent. It should only be considered as
-    // weakly accurate - in particular in what concerns the events states,
-    // especially when displaying a read event state from a write event
-    // callback and conversely.
-    void debugState(String when) {
-        if (debug.isLoggable(Level.DEBUG)) {
-            StringBuilder state = new StringBuilder();
-
-            InternalReadPublisher.InternalReadSubscription sub =
-                    readPublisher.subscriptionImpl;
-            InternalReadPublisher.ReadEvent readEvent =
-                    sub == null ? null : sub.readEvent;
-            Demand rdemand = sub == null ? null : sub.demand;
-            InternalWriteSubscriber.WriteEvent writeEvent =
-                    writeSubscriber.writeEvent;
-            AtomicLong wdemand = writeSubscriber.writeDemand;
-            int rops = readEvent == null ? 0 : readEvent.interestOps();
-            long rd = rdemand == null ? 0 : rdemand.get();
-            int wops = writeEvent == null ? 0 : writeEvent.interestOps();
-            long wd = wdemand == null ? 0 : wdemand.get();
-
-            state.append(when).append(" Reading: [ops=")
-                    .append(rops).append(", demand=").append(rd)
-                    .append(", stopped=")
-                    .append((sub == null ? false : sub.readScheduler.isStopped()))
-                    .append("], Writing: [ops=").append(wops)
-                    .append(", demand=").append(wd)
-                    .append("]");
-            debug.log(Level.DEBUG, state.toString());
-        }
-    }
-
-    /**
-     * A repeatable event that can be paused or resumed by changing
-     * its interestOps.
-     * When the event is fired, it is first paused before being signaled.
-     * It is the responsibility of the code triggered by {@code signalEvent}
-     * to resume the event if required.
-     */
-    private static abstract class SocketFlowEvent extends AsyncEvent {
-        final SocketChannel channel;
-        final int defaultInterest;
-        volatile int interestOps;
-        volatile boolean registered;
-        SocketFlowEvent(int defaultInterest, SocketChannel channel) {
-            super(AsyncEvent.REPEATING);
-            this.defaultInterest = defaultInterest;
-            this.channel = channel;
-        }
-        final boolean registered() {return registered;}
-        final void resume() {
-            interestOps = defaultInterest;
-            registered = true;
-        }
-        final void pause() {interestOps = 0;}
-        @Override
-        public final SelectableChannel channel() {return channel;}
-        @Override
-        public final int interestOps() {return interestOps;}
-
-        @Override
-        public final void handle() {
-            pause();       // pause, then signal
-            signalEvent(); // won't be fired again until resumed.
-        }
-        @Override
-        public final void abort(IOException error) {
-            debug().log(Level.DEBUG, () -> "abort: " + error);
-            pause();              // pause, then signal
-            signalError(error);   // should not be resumed after abort (not checked)
-        }
-
-        protected abstract void signalEvent();
-        protected abstract void signalError(Throwable error);
-        abstract System.Logger debug();
-    }
-
-    // ===================================================================== //
-    //                              Writing                                  //
-    // ======================================================================//
-
-    // This class makes the assumption that the publisher will call
-    // onNext sequentially, and that onNext won't be called if the demand
-    // has not been incremented by request(1).
-    // It has a 'queue of 1' meaning that it will call request(1) in
-    // onSubscribe, and then only after its 'current' buffer list has been
-    // fully written and current set to null;
-    private final class InternalWriteSubscriber
-            implements Flow.Subscriber<List<ByteBuffer>> {
-
-        volatile Flow.Subscription subscription;
-        volatile List<ByteBuffer> current;
-        volatile boolean completed;
-        final WriteEvent writeEvent = new WriteEvent(channel, this);
-        final AtomicLong writeDemand = new AtomicLong();
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            Flow.Subscription previous = this.subscription;
-            this.subscription = subscription;
-            debug.log(Level.DEBUG, "subscribed for writing");
-            if (current == null) {
-                if (previous == subscription || previous == null) {
-                    if (writeDemand.compareAndSet(0, 1)) {
-                        subscription.request(1);
-                    }
-                } else {
-                    writeDemand.set(1);
-                    subscription.request(1);
-                }
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> bufs) {
-            assert current == null; // this is a queue of 1.
-            assert subscription != null;
-            current = bufs;
-            tryFlushCurrent(client.isSelectorThread()); // may be in selector thread
-            // For instance in HTTP/2, a received SETTINGS frame might trigger
-            // the sending of a SETTINGS frame in turn which might cause
-            // onNext to be called from within the same selector thread that the
-            // original SETTINGS frames arrived on. If rs is the read-subscriber
-            // and ws is the write-subscriber then the following can occur:
-            // ReadEvent -> rs.onNext(bytes) -> process server SETTINGS -> write
-            // client SETTINGS -> ws.onNext(bytes) -> tryFlushCurrent
-            debugState("leaving w.onNext");
-        }
-
-        // we don't use a SequentialScheduler here: we rely on
-        // onNext() being called sequentially, and not being called
-        // if we haven't call request(1)
-        // onNext is usually called from within a user/executor thread.
-        // we will perform the initial writing in that thread.
-        // if for some reason, not all data can be written, the writeEvent
-        // will be resumed, and the rest of the data will be written from
-        // the selector manager thread when the writeEvent is fired.
-        // If we are in the selector manager thread, then we will use the executor
-        // to call request(1), ensuring that onNext() won't be called from
-        // within the selector thread.
-        // If we are not in the selector manager thread, then we don't care.
-        void tryFlushCurrent(boolean inSelectorThread) {
-            List<ByteBuffer> bufs = current;
-            if (bufs == null) return;
-            try {
-                assert inSelectorThread == client.isSelectorThread() :
-                       "should " + (inSelectorThread ? "" : "not ")
-                        + " be in the selector thread";
-                long remaining = Utils.remaining(bufs);
-                debug.log(Level.DEBUG, "trying to write: %d", remaining);
-                long written = writeAvailable(bufs);
-                debug.log(Level.DEBUG, "wrote: %d", remaining);
-                if (written == -1) {
-                    signalError(new EOFException("EOF reached while writing"));
-                    return;
-                }
-                assert written <= remaining;
-                if (remaining - written == 0) {
-                    current = null;
-                    writeDemand.decrementAndGet();
-                    Runnable requestMore = this::requestMore;
-                    if (inSelectorThread) {
-                        assert client.isSelectorThread();
-                        client.theExecutor().execute(requestMore);
-                    } else {
-                        assert !client.isSelectorThread();
-                        requestMore.run();
-                    }
-                } else {
-                    resumeWriteEvent(inSelectorThread);
-                }
-            } catch (Throwable t) {
-                signalError(t);
-                subscription.cancel();
-            }
-        }
-
-        void requestMore() {
-            try {
-                if (completed) return;
-                long d =  writeDemand.get();
-                if (writeDemand.compareAndSet(0,1)) {
-                    debug.log(Level.DEBUG, "write: requesting more...");
-                    subscription.request(1);
-                } else {
-                    debug.log(Level.DEBUG, "write: no need to request more: %d", d);
-                }
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG, () ->
-                        "write: error while requesting more: " + t);
-                signalError(t);
-                subscription.cancel();
-            } finally {
-                debugState("leaving requestMore: ");
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            signalError(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            completed = true;
-            // no need to pause the write event here: the write event will
-            // be paused if there is nothing more to write.
-            List<ByteBuffer> bufs = current;
-            long remaining = bufs == null ? 0 : Utils.remaining(bufs);
-            debug.log(Level.DEBUG,  "write completed, %d yet to send", remaining);
-            debugState("InternalWriteSubscriber::onComplete");
-        }
-
-        void resumeWriteEvent(boolean inSelectorThread) {
-            debug.log(Level.DEBUG, "scheduling write event");
-            resumeEvent(writeEvent, this::signalError);
-        }
-
-//        void pauseWriteEvent() {
-//            debug.log(Level.DEBUG, "pausing write event");
-//            pauseEvent(writeEvent, this::signalError);
-//        }
-
-        void signalWritable() {
-            debug.log(Level.DEBUG, "channel is writable");
-            tryFlushCurrent(true);
-        }
-
-        void signalError(Throwable error) {
-            debug.log(Level.DEBUG, () -> "write error: " + error);
-            completed = true;
-            readPublisher.signalError(error);
-        }
-
-        // A repeatable WriteEvent which is paused after firing and can
-        // be resumed if required - see SocketFlowEvent;
-        final class WriteEvent extends SocketFlowEvent {
-            final InternalWriteSubscriber sub;
-            WriteEvent(SocketChannel channel, InternalWriteSubscriber sub) {
-                super(SelectionKey.OP_WRITE, channel);
-                this.sub = sub;
-            }
-            @Override
-            protected final void signalEvent() {
-                try {
-                    client.eventUpdated(this);
-                    sub.signalWritable();
-                } catch(Throwable t) {
-                    sub.signalError(t);
-                }
-            }
-
-            @Override
-            protected void signalError(Throwable error) {
-                sub.signalError(error);
-            }
-
-            @Override
-            System.Logger debug() {
-                return debug;
-            }
-
-        }
-
-    }
-
-    // ===================================================================== //
-    //                              Reading                                  //
-    // ===================================================================== //
-
-    // The InternalReadPublisher uses a SequentialScheduler to ensure that
-    // onNext/onError/onComplete are called sequentially on the caller's
-    // subscriber.
-    // However, it relies on the fact that the only time where
-    // runOrSchedule() is called from a user/executor thread is in signalError,
-    // right after the errorRef has been set.
-    // Because the sequential scheduler's task always checks for errors first,
-    // and always terminate the scheduler on error, then it is safe to assume
-    // that if it reaches the point where it reads from the channel, then
-    // it is running in the SelectorManager thread. This is because all
-    // other invocation of runOrSchedule() are triggered from within a
-    // ReadEvent.
-    //
-    // When pausing/resuming the event, some shortcuts can then be taken
-    // when we know we're running in the selector manager thread
-    // (in that case there's no need to call client.eventUpdated(readEvent);
-    //
-    private final class InternalReadPublisher
-            implements Flow.Publisher<List<ByteBuffer>> {
-        private final InternalReadSubscription subscriptionImpl
-                = new InternalReadSubscription();
-        AtomicReference<ReadSubscription> pendingSubscription = new AtomicReference<>();
-        private volatile ReadSubscription subscription;
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
-            Objects.requireNonNull(s);
-
-            TubeSubscriber sub = FlowTube.asTubeSubscriber(s);
-            ReadSubscription target = new ReadSubscription(subscriptionImpl, sub);
-            ReadSubscription previous = pendingSubscription.getAndSet(target);
-
-            if (previous != null && previous != target) {
-                debug.log(Level.DEBUG,
-                        () -> "read publisher: dropping pending subscriber: "
-                        + previous.subscriber);
-                previous.errorRef.compareAndSet(null, errorRef.get());
-                previous.signalOnSubscribe();
-                if (subscriptionImpl.completed) {
-                    previous.signalCompletion();
-                } else {
-                    previous.subscriber.dropSubscription();
-                }
-            }
-
-            debug.log(Level.DEBUG, "read publisher got subscriber");
-            subscriptionImpl.signalSubscribe();
-            debugState("leaving read.subscribe: ");
-        }
-
-        void signalError(Throwable error) {
-            if (!errorRef.compareAndSet(null, error)) {
-                return;
-            }
-            subscriptionImpl.handleError();
-        }
-
-        final class ReadSubscription implements Flow.Subscription {
-            final InternalReadSubscription impl;
-            final TubeSubscriber  subscriber;
-            final AtomicReference<Throwable> errorRef = new AtomicReference<>();
-            volatile boolean subscribed;
-            volatile boolean cancelled;
-            volatile boolean completed;
-
-            public ReadSubscription(InternalReadSubscription impl,
-                                    TubeSubscriber subscriber) {
-                this.impl = impl;
-                this.subscriber = subscriber;
-            }
-
-            @Override
-            public void cancel() {
-                cancelled = true;
-            }
-
-            @Override
-            public void request(long n) {
-                if (!cancelled) {
-                    impl.request(n);
-                } else {
-                    debug.log(Level.DEBUG,
-                              "subscription cancelled, ignoring request %d", n);
-                }
-            }
-
-            void signalCompletion() {
-                assert subscribed || cancelled;
-                if (completed || cancelled) return;
-                synchronized (this) {
-                    if (completed) return;
-                    completed = true;
-                }
-                Throwable error = errorRef.get();
-                if (error != null) {
-                    debug.log(Level.DEBUG, () ->
-                        "forwarding error to subscriber: "
-                        + error);
-                    subscriber.onError(error);
-                } else {
-                    debug.log(Level.DEBUG, "completing subscriber");
-                    subscriber.onComplete();
-                }
-            }
-
-            void signalOnSubscribe() {
-                if (subscribed || cancelled) return;
-                synchronized (this) {
-                    if (subscribed || cancelled) return;
-                    subscribed = true;
-                }
-                subscriber.onSubscribe(this);
-                debug.log(Level.DEBUG, "onSubscribe called");
-                if (errorRef.get() != null) {
-                    signalCompletion();
-                }
-            }
-        }
-
-        final class InternalReadSubscription implements Flow.Subscription {
-
-            private final Demand demand = new Demand();
-            final SequentialScheduler readScheduler;
-            private volatile boolean completed;
-            private final ReadEvent readEvent;
-            private final AsyncEvent subscribeEvent;
-
-            InternalReadSubscription() {
-                readScheduler = new SequentialScheduler(new SocketFlowTask(this::read));
-                subscribeEvent = new AsyncTriggerEvent(this::signalError,
-                                                       this::handleSubscribeEvent);
-                readEvent = new ReadEvent(channel, this);
-            }
-
-            /*
-             * This method must be invoked before any other method of this class.
-             */
-            final void signalSubscribe() {
-                if (readScheduler.isStopped() || completed) {
-                    // if already completed or stopped we can handle any
-                    // pending connection directly from here.
-                    debug.log(Level.DEBUG,
-                              "handling pending subscription while completed");
-                    handlePending();
-                } else {
-                    try {
-                        debug.log(Level.DEBUG,
-                                  "registering subscribe event");
-                        client.registerEvent(subscribeEvent);
-                    } catch (Throwable t) {
-                        signalError(t);
-                        handlePending();
-                    }
-                }
-            }
-
-            final void handleSubscribeEvent() {
-                assert client.isSelectorThread();
-                debug.log(Level.DEBUG, "subscribe event raised");
-                readScheduler.runOrSchedule();
-                if (readScheduler.isStopped() || completed) {
-                    // if already completed or stopped we can handle any
-                    // pending connection directly from here.
-                    debug.log(Level.DEBUG,
-                              "handling pending subscription when completed");
-                    handlePending();
-                }
-            }
-
-
-            /*
-             * Although this method is thread-safe, the Reactive-Streams spec seems
-             * to not require it to be as such. It's a responsibility of the
-             * subscriber to signal demand in a thread-safe manner.
-             *
-             * https://github.com/reactive-streams/reactive-streams-jvm/blob/dd24d2ab164d7de6c316f6d15546f957bec29eaa/README.md
-             * (rules 2.7 and 3.4)
-             */
-            @Override
-            public final void request(long n) {
-                if (n > 0L) {
-                    boolean wasFulfilled = demand.increase(n);
-                    if (wasFulfilled) {
-                        debug.log(Level.DEBUG, "got some demand for reading");
-                        resumeReadEvent();
-                        // if demand has been changed from fulfilled
-                        // to unfulfilled register read event;
-                    }
-                } else {
-                    signalError(new IllegalArgumentException("non-positive request"));
-                }
-                debugState("leaving request("+n+"): ");
-            }
-
-            @Override
-            public final void cancel() {
-                pauseReadEvent();
-                readScheduler.stop();
-            }
-
-            private void resumeReadEvent() {
-                debug.log(Level.DEBUG, "resuming read event");
-                resumeEvent(readEvent, this::signalError);
-            }
-
-            private void pauseReadEvent() {
-                debug.log(Level.DEBUG, "pausing read event");
-                pauseEvent(readEvent, this::signalError);
-            }
-
-
-            final void handleError() {
-                assert errorRef.get() != null;
-                readScheduler.runOrSchedule();
-            }
-
-            final void signalError(Throwable error) {
-                if (!errorRef.compareAndSet(null, error)) {
-                    return;
-                }
-                debug.log(Level.DEBUG, () -> "got read error: " + error);
-                readScheduler.runOrSchedule();
-            }
-
-            final void signalReadable() {
-                readScheduler.runOrSchedule();
-            }
-
-            /** The body of the task that runs in SequentialScheduler. */
-            final void read() {
-                // It is important to only call pauseReadEvent() when stopping
-                // the scheduler. The event is automatically paused before
-                // firing, and trying to pause it again could cause a race
-                // condition between this loop, which calls tryDecrementDemand(),
-                // and the thread that calls request(n), which will try to resume
-                // reading.
-                try {
-                    while(!readScheduler.isStopped()) {
-                        if (completed) return;
-
-                        // make sure we have a subscriber
-                        if (handlePending()) {
-                            debug.log(Level.DEBUG, "pending subscriber subscribed");
-                            return;
-                        }
-
-                        // If an error was signaled, we might not be in the
-                        // the selector thread, and that is OK, because we
-                        // will just call onError and return.
-                        ReadSubscription current = subscription;
-                        TubeSubscriber subscriber = current.subscriber;
-                        Throwable error = errorRef.get();
-                        if (error != null) {
-                            completed = true;
-                            // safe to pause here because we're finished anyway.
-                            pauseReadEvent();
-                            debug.log(Level.DEBUG, () -> "Sending error " + error
-                                  + " to subscriber " + subscriber);
-                            current.errorRef.compareAndSet(null, error);
-                            current.signalCompletion();
-                            readScheduler.stop();
-                            debugState("leaving read() loop with error: ");
-                            return;
-                        }
-
-                        // If we reach here then we must be in the selector thread.
-                        assert client.isSelectorThread();
-                        if (demand.tryDecrement()) {
-                            // we have demand.
-                            try {
-                                List<ByteBuffer> bytes = readAvailable();
-                                if (bytes == EOF) {
-                                    if (!completed) {
-                                        debug.log(Level.DEBUG, "got read EOF");
-                                        completed = true;
-                                        // safe to pause here because we're finished
-                                        // anyway.
-                                        pauseReadEvent();
-                                        current.signalCompletion();
-                                        readScheduler.stop();
-                                    }
-                                    debugState("leaving read() loop after EOF: ");
-                                    return;
-                                } else if (Utils.remaining(bytes) > 0) {
-                                    // the subscriber is responsible for offloading
-                                    // to another thread if needed.
-                                    debug.log(Level.DEBUG, () -> "read bytes: "
-                                            + Utils.remaining(bytes));
-                                    assert !current.completed;
-                                    subscriber.onNext(bytes);
-                                    // we could continue looping until the demand
-                                    // reaches 0. However, that would risk starving
-                                    // other connections (bound to other socket
-                                    // channels) - as other selected keys activated
-                                    // by the selector manager thread might be
-                                    // waiting for this event to terminate.
-                                    // So resume the read event and return now...
-                                    resumeReadEvent();
-                                    debugState("leaving read() loop after onNext: ");
-                                    return;
-                                } else {
-                                    // nothing available!
-                                    debug.log(Level.DEBUG, "no more bytes available");
-                                    // re-increment the demand and resume the read
-                                    // event. This ensures that this loop is
-                                    // executed again when the socket becomes
-                                    // readable again.
-                                    demand.increase(1);
-                                    resumeReadEvent();
-                                    debugState("leaving read() loop with no bytes");
-                                    return;
-                                }
-                            } catch (Throwable x) {
-                                signalError(x);
-                                continue;
-                            }
-                        } else {
-                            debug.log(Level.DEBUG, "no more demand for reading");
-                            // the event is paused just after firing, so it should
-                            // still be paused here, unless the demand was just
-                            // incremented from 0 to n, in which case, the
-                            // event will be resumed, causing this loop to be
-                            // invoked again when the socket becomes readable:
-                            // This is what we want.
-                            // Trying to pause the event here would actually
-                            // introduce a race condition between this loop and
-                            // request(n).
-                            debugState("leaving read() loop with no demand");
-                            break;
-                        }
-                    }
-                } catch (Throwable t) {
-                    debug.log(Level.DEBUG, "Unexpected exception in read loop", t);
-                    signalError(t);
-                } finally {
-                    handlePending();
-                }
-            }
-
-            boolean handlePending() {
-                ReadSubscription pending = pendingSubscription.getAndSet(null);
-                if (pending == null) return false;
-                debug.log(Level.DEBUG, "handling pending subscription for %s",
-                          pending.subscriber);
-                ReadSubscription current = subscription;
-                if (current != null && current != pending && !completed) {
-                    current.subscriber.dropSubscription();
-                }
-                debug.log(Level.DEBUG, "read demand reset to 0");
-                subscriptionImpl.demand.reset(); // subscriber will increase demand if it needs to.
-                pending.errorRef.compareAndSet(null, errorRef.get());
-                if (!readScheduler.isStopped()) {
-                    subscription = pending;
-                } else {
-                    debug.log(Level.DEBUG, "socket tube is already stopped");
-                }
-                debug.log(Level.DEBUG, "calling onSubscribe");
-                pending.signalOnSubscribe();
-                if (completed) {
-                    pending.errorRef.compareAndSet(null, errorRef.get());
-                    pending.signalCompletion();
-                }
-                return true;
-            }
-        }
-
-
-        // A repeatable ReadEvent which is paused after firing and can
-        // be resumed if required - see SocketFlowEvent;
-        final class ReadEvent extends SocketFlowEvent {
-            final InternalReadSubscription sub;
-            ReadEvent(SocketChannel channel, InternalReadSubscription sub) {
-                super(SelectionKey.OP_READ, channel);
-                this.sub = sub;
-            }
-            @Override
-            protected final void signalEvent() {
-                try {
-                    client.eventUpdated(this);
-                    sub.signalReadable();
-                } catch(Throwable t) {
-                    sub.signalError(t);
-                }
-            }
-
-            @Override
-            protected final void signalError(Throwable error) {
-                sub.signalError(error);
-            }
-
-            @Override
-            System.Logger debug() {
-                return debug;
-            }
-        }
-
-    }
-
-    // ===================================================================== //
-    //                   Socket Channel Read/Write                           //
-    // ===================================================================== //
-    static final int MAX_BUFFERS = 3;
-    static final List<ByteBuffer> EOF = List.of();
-
-    private List<ByteBuffer> readAvailable() throws IOException {
-        ByteBuffer buf = buffersSource.get();
-        assert buf.hasRemaining();
-
-        int read;
-        int pos = buf.position();
-        List<ByteBuffer> list = null;
-        while (buf.hasRemaining()) {
-            while ((read = channel.read(buf)) > 0) {
-               if (!buf.hasRemaining()) break;
-            }
-
-            // nothing read;
-            if (buf.position() == pos) {
-                // An empty list signal the end of data, and should only be
-                // returned if read == -1.
-                // If we already read some data, then we must return what we have
-                // read, and -1 will be returned next time the caller attempts to
-                // read something.
-                if (list == null && read == -1) {  // eof
-                    list = EOF;
-                    break;
-                }
-            }
-            buf.limit(buf.position());
-            buf.position(pos);
-            if (list == null) {
-                list = List.of(buf);
-            } else {
-                if (!(list instanceof ArrayList)) {
-                    list = new ArrayList<>(list);
-                }
-                list.add(buf);
-            }
-            if (read <= 0 || list.size() == MAX_BUFFERS) break;
-            buf = buffersSource.get();
-            pos = buf.position();
-            assert buf.hasRemaining();
-        }
-        return list;
-    }
-
-    private long writeAvailable(List<ByteBuffer> bytes) throws IOException {
-        ByteBuffer[] srcs = bytes.toArray(Utils.EMPTY_BB_ARRAY);
-        final long remaining = Utils.remaining(srcs);
-        long written = 0;
-        while (remaining > written) {
-            long w = channel.write(srcs);
-            if (w == -1 && written == 0) return -1;
-            if (w == 0) break;
-            written += w;
-        }
-        return written;
-    }
-
-    private void resumeEvent(SocketFlowEvent event,
-                             Consumer<Throwable> errorSignaler) {
-        boolean registrationRequired;
-        synchronized(lock) {
-            registrationRequired = !event.registered();
-            event.resume();
-        }
-        try {
-            if (registrationRequired) {
-                client.registerEvent(event);
-             } else {
-                client.eventUpdated(event);
-            }
-        } catch(Throwable t) {
-            errorSignaler.accept(t);
-        }
-   }
-
-    private void pauseEvent(SocketFlowEvent event,
-                            Consumer<Throwable> errorSignaler) {
-        synchronized(lock) {
-            event.pause();
-        }
-        try {
-            client.eventUpdated(event);
-        } catch(Throwable t) {
-            errorSignaler.accept(t);
-        }
-    }
-
-    @Override
-    public void connectFlows(TubePublisher writePublisher,
-                             TubeSubscriber readSubscriber) {
-        debug.log(Level.DEBUG, "connecting flows");
-        this.subscribe(readSubscriber);
-        writePublisher.subscribe(this);
-    }
-
-
-    @Override
-    public String toString() {
-        return dbgString();
-    }
-
-    final String dbgString() {
-        return "SocketTube("+id+")";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Stream.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1166 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscription;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiPredicate;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
-import jdk.incubator.http.internal.common.*;
-import jdk.incubator.http.internal.frame.*;
-import jdk.incubator.http.internal.hpack.DecodingCallback;
-
-/**
- * Http/2 Stream handling.
- *
- * REQUESTS
- *
- * sendHeadersOnly() -- assembles HEADERS frame and puts on connection outbound Q
- *
- * sendRequest() -- sendHeadersOnly() + sendBody()
- *
- * sendBodyAsync() -- calls sendBody() in an executor thread.
- *
- * sendHeadersAsync() -- calls sendHeadersOnly() which does not block
- *
- * sendRequestAsync() -- calls sendRequest() in an executor thread
- *
- * RESPONSES
- *
- * Multiple responses can be received per request. Responses are queued up on
- * a LinkedList of CF<HttpResponse> and the the first one on the list is completed
- * with the next response
- *
- * getResponseAsync() -- queries list of response CFs and returns first one
- *               if one exists. Otherwise, creates one and adds it to list
- *               and returns it. Completion is achieved through the
- *               incoming() upcall from connection reader thread.
- *
- * getResponse() -- calls getResponseAsync() and waits for CF to complete
- *
- * responseBodyAsync() -- calls responseBody() in an executor thread.
- *
- * incoming() -- entry point called from connection reader thread. Frames are
- *               either handled immediately without blocking or for data frames
- *               placed on the stream's inputQ which is consumed by the stream's
- *               reader thread.
- *
- * PushedStream sub class
- * ======================
- * Sending side methods are not used because the request comes from a PUSH_PROMISE
- * frame sent by the server. When a PUSH_PROMISE is received the PushedStream
- * is created. PushedStream does not use responseCF list as there can be only
- * one response. The CF is created when the object created and when the response
- * HEADERS frame is received the object is completed.
- */
-class Stream<T> extends ExchangeImpl<T> {
-
-    final static boolean DEBUG = Utils.DEBUG; // Revisit: temporary developer's flag
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    final ConcurrentLinkedQueue<Http2Frame> inputQ = new ConcurrentLinkedQueue<>();
-    final SequentialScheduler sched =
-            SequentialScheduler.synchronizedScheduler(this::schedule);
-    final SubscriptionBase userSubscription = new SubscriptionBase(sched, this::cancel);
-
-    /**
-     * This stream's identifier. Assigned lazily by the HTTP2Connection before
-     * the stream's first frame is sent.
-     */
-    protected volatile int streamid;
-
-    long requestContentLen;
-
-    final Http2Connection connection;
-    final HttpRequestImpl request;
-    final DecodingCallback rspHeadersConsumer;
-    HttpHeadersImpl responseHeaders;
-    final HttpHeadersImpl requestPseudoHeaders;
-    volatile HttpResponse.BodySubscriber<T> responseSubscriber;
-    final HttpRequest.BodyPublisher requestPublisher;
-    volatile RequestSubscriber requestSubscriber;
-    volatile int responseCode;
-    volatile Response response;
-    volatile Throwable failed; // The exception with which this stream was canceled.
-    final CompletableFuture<Void> requestBodyCF = new MinimalFuture<>();
-    volatile CompletableFuture<T> responseBodyCF;
-
-    /** True if END_STREAM has been seen in a frame received on this stream. */
-    private volatile boolean remotelyClosed;
-    private volatile boolean closed;
-    private volatile boolean endStreamSent;
-
-    // state flags
-    private boolean requestSent, responseReceived;
-
-    /**
-     * A reference to this Stream's connection Send Window controller. The
-     * stream MUST acquire the appropriate amount of Send Window before
-     * sending any data. Will be null for PushStreams, as they cannot send data.
-     */
-    private final WindowController windowController;
-    private final WindowUpdateSender windowUpdater;
-
-    @Override
-    HttpConnection connection() {
-        return connection.connection;
-    }
-
-    /**
-     * Invoked either from incoming() -> {receiveDataFrame() or receiveResetFrame() }
-     * of after user subscription window has re-opened, from SubscriptionBase.request()
-     */
-    private void schedule() {
-        if (responseSubscriber == null)
-            // can't process anything yet
-            return;
-
-        try {
-            while (!inputQ.isEmpty()) {
-                Http2Frame frame = inputQ.peek();
-                if (frame instanceof ResetFrame) {
-                    inputQ.remove();
-                    handleReset((ResetFrame)frame);
-                    return;
-                }
-                DataFrame df = (DataFrame)frame;
-                boolean finished = df.getFlag(DataFrame.END_STREAM);
-
-                List<ByteBuffer> buffers = df.getData();
-                List<ByteBuffer> dsts = Collections.unmodifiableList(buffers);
-                int size = Utils.remaining(dsts, Integer.MAX_VALUE);
-                if (size == 0 && finished) {
-                    inputQ.remove();
-                    Log.logTrace("responseSubscriber.onComplete");
-                    debug.log(Level.DEBUG, "incoming: onComplete");
-                    sched.stop();
-                    responseSubscriber.onComplete();
-                    setEndStreamReceived();
-                    return;
-                } else if (userSubscription.tryDecrement()) {
-                    inputQ.remove();
-                    Log.logTrace("responseSubscriber.onNext {0}", size);
-                    debug.log(Level.DEBUG, "incoming: onNext(%d)", size);
-                    responseSubscriber.onNext(dsts);
-                    if (consumed(df)) {
-                        Log.logTrace("responseSubscriber.onComplete");
-                        debug.log(Level.DEBUG, "incoming: onComplete");
-                        sched.stop();
-                        responseSubscriber.onComplete();
-                        setEndStreamReceived();
-                        return;
-                    }
-                } else {
-                    return;
-                }
-            }
-        } catch (Throwable throwable) {
-            failed = throwable;
-        }
-
-        Throwable t = failed;
-        if (t != null) {
-            sched.stop();
-            responseSubscriber.onError(t);
-            close();
-        }
-    }
-
-    // Callback invoked after the Response BodySubscriber has consumed the
-    // buffers contained in a DataFrame.
-    // Returns true if END_STREAM is reached, false otherwise.
-    private boolean consumed(DataFrame df) {
-        // RFC 7540 6.1:
-        // The entire DATA frame payload is included in flow control,
-        // including the Pad Length and Padding fields if present
-        int len = df.payloadLength();
-        connection.windowUpdater.update(len);
-
-        if (!df.getFlag(DataFrame.END_STREAM)) {
-            // Don't send window update on a stream which is
-            // closed or half closed.
-            windowUpdater.update(len);
-            return false; // more data coming
-        }
-        return true; // end of stream
-    }
-
-    @Override
-    CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler,
-                                       boolean returnConnectionToPool,
-                                       Executor executor)
-    {
-        Log.logTrace("Reading body on stream {0}", streamid);
-        BodySubscriber<T> bodySubscriber = handler.apply(responseCode, responseHeaders);
-        CompletableFuture<T> cf = receiveData(bodySubscriber);
-
-        PushGroup<?> pg = exchange.getPushGroup();
-        if (pg != null) {
-            // if an error occurs make sure it is recorded in the PushGroup
-            cf = cf.whenComplete((t,e) -> pg.pushError(e));
-        }
-        return cf;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("streamid: ")
-                .append(streamid);
-        return sb.toString();
-    }
-
-    private void receiveDataFrame(DataFrame df) {
-        inputQ.add(df);
-        sched.runOrSchedule();
-    }
-
-    /** Handles a RESET frame. RESET is always handled inline in the queue. */
-    private void receiveResetFrame(ResetFrame frame) {
-        inputQ.add(frame);
-        sched.runOrSchedule();
-    }
-
-    // pushes entire response body into response subscriber
-    // blocking when required by local or remote flow control
-    CompletableFuture<T> receiveData(BodySubscriber<T> bodySubscriber) {
-        responseBodyCF = MinimalFuture.of(bodySubscriber.getBody());
-
-        if (isCanceled()) {
-            Throwable t = getCancelCause();
-            responseBodyCF.completeExceptionally(t);
-        } else {
-            bodySubscriber.onSubscribe(userSubscription);
-        }
-        // Set the responseSubscriber field now that onSubscribe has been called.
-        // This effectively allows the scheduler to start invoking the callbacks.
-        responseSubscriber = bodySubscriber;
-        sched.runOrSchedule(); // in case data waiting already to be processed
-        return responseBodyCF;
-    }
-
-    @Override
-    CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
-        return sendBodyImpl().thenApply( v -> this);
-    }
-
-    @SuppressWarnings("unchecked")
-    Stream(Http2Connection connection,
-           Exchange<T> e,
-           WindowController windowController)
-    {
-        super(e);
-        this.connection = connection;
-        this.windowController = windowController;
-        this.request = e.request();
-        this.requestPublisher = request.requestPublisher;  // may be null
-        responseHeaders = new HttpHeadersImpl();
-        rspHeadersConsumer = (name, value) -> {
-            responseHeaders.addHeader(name.toString(), value.toString());
-            if (Log.headers() && Log.trace()) {
-                Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}",
-                             streamid, name, value);
-            }
-        };
-        this.requestPseudoHeaders = new HttpHeadersImpl();
-        // NEW
-        this.windowUpdater = new StreamWindowUpdateSender(connection);
-    }
-
-    /**
-     * Entry point from Http2Connection reader thread.
-     *
-     * Data frames will be removed by response body thread.
-     */
-    void incoming(Http2Frame frame) throws IOException {
-        debug.log(Level.DEBUG, "incoming: %s", frame);
-        if ((frame instanceof HeaderFrame)) {
-            HeaderFrame hframe = (HeaderFrame)frame;
-            if (hframe.endHeaders()) {
-                Log.logTrace("handling response (streamid={0})", streamid);
-                handleResponse();
-                if (hframe.getFlag(HeaderFrame.END_STREAM)) {
-                    receiveDataFrame(new DataFrame(streamid, DataFrame.END_STREAM, List.of()));
-                }
-            }
-        } else if (frame instanceof DataFrame) {
-            receiveDataFrame((DataFrame)frame);
-        } else {
-            otherFrame(frame);
-        }
-    }
-
-    void otherFrame(Http2Frame frame) throws IOException {
-        switch (frame.type()) {
-            case WindowUpdateFrame.TYPE:
-                incoming_windowUpdate((WindowUpdateFrame) frame);
-                break;
-            case ResetFrame.TYPE:
-                incoming_reset((ResetFrame) frame);
-                break;
-            case PriorityFrame.TYPE:
-                incoming_priority((PriorityFrame) frame);
-                break;
-            default:
-                String msg = "Unexpected frame: " + frame.toString();
-                throw new IOException(msg);
-        }
-    }
-
-    // The Hpack decoder decodes into one of these consumers of name,value pairs
-
-    DecodingCallback rspHeadersConsumer() {
-        return rspHeadersConsumer;
-    }
-
-    protected void handleResponse() throws IOException {
-        responseCode = (int)responseHeaders
-                .firstValueAsLong(":status")
-                .orElseThrow(() -> new IOException("no statuscode in response"));
-
-        response = new Response(
-                request, exchange, responseHeaders,
-                responseCode, HttpClient.Version.HTTP_2);
-
-        /* TODO: review if needs to be removed
-           the value is not used, but in case `content-length` doesn't parse as
-           long, there will be NumberFormatException. If left as is, make sure
-           code up the stack handles NFE correctly. */
-        responseHeaders.firstValueAsLong("content-length");
-
-        if (Log.headers()) {
-            StringBuilder sb = new StringBuilder("RESPONSE HEADERS:\n");
-            Log.dumpHeaders(sb, "    ", responseHeaders);
-            Log.logHeaders(sb.toString());
-        }
-
-        completeResponse(response);
-    }
-
-    void incoming_reset(ResetFrame frame) {
-        Log.logTrace("Received RST_STREAM on stream {0}", streamid);
-        if (endStreamReceived()) {
-            Log.logTrace("Ignoring RST_STREAM frame received on remotely closed stream {0}", streamid);
-        } else if (closed) {
-            Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
-        } else {
-            // put it in the input queue in order to read all
-            // pending data frames first. Indeed, a server may send
-            // RST_STREAM after sending END_STREAM, in which case we should
-            // ignore it. However, we won't know if we have received END_STREAM
-            // or not until all pending data frames are read.
-            receiveResetFrame(frame);
-            // RST_STREAM was pushed to the queue. It will be handled by
-            // asyncReceive after all pending data frames have been
-            // processed.
-            Log.logTrace("RST_STREAM pushed in queue for stream {0}", streamid);
-        }
-    }
-
-    void handleReset(ResetFrame frame) {
-        Log.logTrace("Handling RST_STREAM on stream {0}", streamid);
-        if (!closed) {
-            close();
-            int error = frame.getErrorCode();
-            completeResponseExceptionally(new IOException(ErrorFrame.stringForCode(error)));
-        } else {
-            Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
-        }
-    }
-
-    void incoming_priority(PriorityFrame frame) {
-        // TODO: implement priority
-        throw new UnsupportedOperationException("Not implemented");
-    }
-
-    private void incoming_windowUpdate(WindowUpdateFrame frame)
-        throws IOException
-    {
-        int amount = frame.getUpdate();
-        if (amount <= 0) {
-            Log.logTrace("Resetting stream: {0} %d, Window Update amount: %d\n",
-                         streamid, streamid, amount);
-            connection.resetStream(streamid, ResetFrame.FLOW_CONTROL_ERROR);
-        } else {
-            assert streamid != 0;
-            boolean success = windowController.increaseStreamWindow(amount, streamid);
-            if (!success) {  // overflow
-                connection.resetStream(streamid, ResetFrame.FLOW_CONTROL_ERROR);
-            }
-        }
-    }
-
-    void incoming_pushPromise(HttpRequestImpl pushRequest,
-                              PushedStream<T> pushStream)
-        throws IOException
-    {
-        if (Log.requests()) {
-            Log.logRequest("PUSH_PROMISE: " + pushRequest.toString());
-        }
-        PushGroup<T> pushGroup = exchange.getPushGroup();
-        if (pushGroup == null) {
-            Log.logTrace("Rejecting push promise stream " + streamid);
-            connection.resetStream(pushStream.streamid, ResetFrame.REFUSED_STREAM);
-            pushStream.close();
-            return;
-        }
-
-        PushGroup.Acceptor<T> acceptor = pushGroup.acceptPushRequest(pushRequest);
-
-        if (!acceptor.accepted()) {
-            // cancel / reject
-            IOException ex = new IOException("Stream " + streamid + " cancelled by users handler");
-            if (Log.trace()) {
-                Log.logTrace("No body subscriber for {0}: {1}", pushRequest,
-                        ex.getMessage());
-            }
-            pushStream.cancelImpl(ex);
-            return;
-        }
-
-        CompletableFuture<HttpResponse<T>> pushResponseCF = acceptor.cf();
-        HttpResponse.BodyHandler<T> pushHandler = acceptor.bodyHandler();
-        assert pushHandler != null;
-
-        pushStream.requestSent();
-        pushStream.setPushHandler(pushHandler);  // TODO: could wrap the handler to throw on acceptPushPromise ?
-        // setup housekeeping for when the push is received
-        // TODO: deal with ignoring of CF anti-pattern
-        CompletableFuture<HttpResponse<T>> cf = pushStream.responseCF();
-        cf.whenComplete((HttpResponse<T> resp, Throwable t) -> {
-            t = Utils.getCompletionCause(t);
-            if (Log.trace()) {
-                Log.logTrace("Push completed on stream {0} for {1}{2}",
-                             pushStream.streamid, resp,
-                             ((t==null) ? "": " with exception " + t));
-            }
-            if (t != null) {
-                pushGroup.pushError(t);
-                pushResponseCF.completeExceptionally(t);
-            } else {
-                pushResponseCF.complete(resp);
-            }
-            pushGroup.pushCompleted();
-        });
-
-    }
-
-    private OutgoingHeaders<Stream<T>> headerFrame(long contentLength) {
-        HttpHeadersImpl h = request.getSystemHeaders();
-        if (contentLength > 0) {
-            h.setHeader("content-length", Long.toString(contentLength));
-        }
-        setPseudoHeaderFields();
-        HttpHeaders sysh = filter(h);
-        HttpHeaders userh = filter(request.getUserHeaders());
-        OutgoingHeaders<Stream<T>> f = new OutgoingHeaders<>(sysh, userh, this);
-        if (contentLength == 0) {
-            f.setFlag(HeadersFrame.END_STREAM);
-            endStreamSent = true;
-        }
-        return f;
-    }
-
-    private boolean hasProxyAuthorization(HttpHeaders headers) {
-        return headers.firstValue("proxy-authorization")
-                      .isPresent();
-    }
-
-    // Determines whether we need to build a new HttpHeader object.
-    //
-    // Ideally we should pass the filter to OutgoingHeaders refactor the
-    // code that creates the HeaderFrame to honor the filter.
-    // We're not there yet - so depending on the filter we need to
-    // apply and the content of the header we will try to determine
-    //  whether anything might need to be filtered.
-    // If nothing needs filtering then we can just use the
-    // original headers.
-    private boolean needsFiltering(HttpHeaders headers,
-                                   BiPredicate<String, List<String>> filter) {
-        if (filter == Utils.PROXY_TUNNEL_FILTER || filter == Utils.PROXY_FILTER) {
-            // we're either connecting or proxying
-            // slight optimization: we only need to filter out
-            // disabled schemes, so if there are none just
-            // pass through.
-            return Utils.proxyHasDisabledSchemes(filter == Utils.PROXY_TUNNEL_FILTER)
-                    && hasProxyAuthorization(headers);
-        } else {
-            // we're talking to a server, either directly or through
-            // a tunnel.
-            // Slight optimization: we only need to filter out
-            // proxy authorization headers, so if there are none just
-            // pass through.
-            return hasProxyAuthorization(headers);
-        }
-    }
-
-    private HttpHeaders filter(HttpHeaders headers) {
-        HttpConnection conn = connection();
-        BiPredicate<String, List<String>> filter =
-                conn.headerFilter(request);
-        if (needsFiltering(headers, filter)) {
-            return ImmutableHeaders.of(headers.map(), filter);
-        }
-        return headers;
-    }
-
-    private void setPseudoHeaderFields() {
-        HttpHeadersImpl hdrs = requestPseudoHeaders;
-        String method = request.method();
-        hdrs.setHeader(":method", method);
-        URI uri = request.uri();
-        hdrs.setHeader(":scheme", uri.getScheme());
-        // TODO: userinfo deprecated. Needs to be removed
-        hdrs.setHeader(":authority", uri.getAuthority());
-        // TODO: ensure header names beginning with : not in user headers
-        String query = uri.getQuery();
-        String path = uri.getPath();
-        if (path == null || path.isEmpty()) {
-            if (method.equalsIgnoreCase("OPTIONS")) {
-                path = "*";
-            } else {
-                path = "/";
-            }
-        }
-        if (query != null) {
-            path += "?" + query;
-        }
-        hdrs.setHeader(":path", path);
-    }
-
-    HttpHeadersImpl getRequestPseudoHeaders() {
-        return requestPseudoHeaders;
-    }
-
-    /** Sets endStreamReceived. Should be called only once. */
-    void setEndStreamReceived() {
-        assert remotelyClosed == false: "Unexpected endStream already set";
-        remotelyClosed = true;
-        responseReceived();
-    }
-
-    /** Tells whether, or not, the END_STREAM Flag has been seen in any frame
-     *  received on this stream. */
-    private boolean endStreamReceived() {
-        return remotelyClosed;
-    }
-
-    @Override
-    CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
-        debug.log(Level.DEBUG, "sendHeadersOnly()");
-        if (Log.requests() && request != null) {
-            Log.logRequest(request.toString());
-        }
-        if (requestPublisher != null) {
-            requestContentLen = requestPublisher.contentLength();
-        } else {
-            requestContentLen = 0;
-        }
-        OutgoingHeaders<Stream<T>> f = headerFrame(requestContentLen);
-        connection.sendFrame(f);
-        CompletableFuture<ExchangeImpl<T>> cf = new MinimalFuture<>();
-        cf.complete(this);  // #### good enough for now
-        return cf;
-    }
-
-    @Override
-    void released() {
-        if (streamid > 0) {
-            debug.log(Level.DEBUG, "Released stream %d", streamid);
-            // remove this stream from the Http2Connection map.
-            connection.closeStream(streamid);
-        } else {
-            debug.log(Level.DEBUG, "Can't release stream %d", streamid);
-        }
-    }
-
-    @Override
-    void completed() {
-        // There should be nothing to do here: the stream should have
-        // been already closed (or will be closed shortly after).
-    }
-
-    void registerStream(int id) {
-        this.streamid = id;
-        connection.putStream(this, streamid);
-        debug.log(Level.DEBUG, "Registered stream %d", id);
-    }
-
-    void signalWindowUpdate() {
-        RequestSubscriber subscriber = requestSubscriber;
-        assert subscriber != null;
-        debug.log(Level.DEBUG, "Signalling window update");
-        subscriber.sendScheduler.runOrSchedule();
-    }
-
-    static final ByteBuffer COMPLETED = ByteBuffer.allocate(0);
-    class RequestSubscriber implements Flow.Subscriber<ByteBuffer> {
-        // can be < 0 if the actual length is not known.
-        private final long contentLength;
-        private volatile long remainingContentLength;
-        private volatile Subscription subscription;
-
-        // Holds the outgoing data. There will be at most 2 outgoing ByteBuffers.
-        //  1) The data that was published by the request body Publisher, and
-        //  2) the COMPLETED sentinel, since onComplete can be invoked without demand.
-        final ConcurrentLinkedDeque<ByteBuffer> outgoing = new ConcurrentLinkedDeque<>();
-
-        private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
-        // A scheduler used to honor window updates. Writing must be paused
-        // when the window is exhausted, and resumed when the window acquires
-        // some space. The sendScheduler makes it possible to implement this
-        // behaviour in an asynchronous non-blocking way.
-        // See RequestSubscriber::trySend below.
-        final SequentialScheduler sendScheduler;
-
-        RequestSubscriber(long contentLen) {
-            this.contentLength = contentLen;
-            this.remainingContentLength = contentLen;
-            this.sendScheduler =
-                    SequentialScheduler.synchronizedScheduler(this::trySend);
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (this.subscription != null) {
-                throw new IllegalStateException("already subscribed");
-            }
-            this.subscription = subscription;
-            debug.log(Level.DEBUG, "RequestSubscriber: onSubscribe, request 1");
-            subscription.request(1);
-        }
-
-        @Override
-        public void onNext(ByteBuffer item) {
-            debug.log(Level.DEBUG, "RequestSubscriber: onNext(%d)", item.remaining());
-            int size = outgoing.size();
-            assert size == 0 : "non-zero size: " + size;
-            onNextImpl(item);
-        }
-
-        private void onNextImpl(ByteBuffer item) {
-            // Got some more request body bytes to send.
-            if (requestBodyCF.isDone()) {
-                // stream already cancelled, probably in timeout
-                sendScheduler.stop();
-                subscription.cancel();
-                return;
-            }
-            outgoing.add(item);
-            sendScheduler.runOrSchedule();
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            debug.log(Level.DEBUG, () -> "RequestSubscriber: onError: " + throwable);
-            // ensure that errors are handled within the flow.
-            if (errorRef.compareAndSet(null, throwable)) {
-                sendScheduler.runOrSchedule();
-            }
-        }
-
-        @Override
-        public void onComplete() {
-            debug.log(Level.DEBUG, "RequestSubscriber: onComplete");
-            int size = outgoing.size();
-            assert size == 0 || size == 1 : "non-zero or one size: " + size;
-            // last byte of request body has been obtained.
-            // ensure that everything is completed within the flow.
-            onNextImpl(COMPLETED);
-        }
-
-        // Attempts to send the data, if any.
-        // Handles errors and completion state.
-        // Pause writing if the send window is exhausted, resume it if the
-        // send window has some bytes that can be acquired.
-        void trySend() {
-            try {
-                // handle errors raised by onError;
-                Throwable t = errorRef.get();
-                if (t != null) {
-                    sendScheduler.stop();
-                    if (requestBodyCF.isDone()) return;
-                    subscription.cancel();
-                    requestBodyCF.completeExceptionally(t);
-                    return;
-                }
-
-                do {
-                    // handle COMPLETED;
-                    ByteBuffer item = outgoing.peekFirst();
-                    if (item == null) return;
-                    else if (item == COMPLETED) {
-                        sendScheduler.stop();
-                        complete();
-                        return;
-                    }
-
-                    // handle bytes to send downstream
-                    while (item.hasRemaining()) {
-                        debug.log(Level.DEBUG, "trySend: %d", item.remaining());
-                        assert !endStreamSent : "internal error, send data after END_STREAM flag";
-                        DataFrame df = getDataFrame(item);
-                        if (df == null) {
-                            debug.log(Level.DEBUG, "trySend: can't send yet: %d",
-                                    item.remaining());
-                            return; // the send window is exhausted: come back later
-                        }
-
-                        if (contentLength > 0) {
-                            remainingContentLength -= df.getDataLength();
-                            if (remainingContentLength < 0) {
-                                String msg = connection().getConnectionFlow()
-                                        + " stream=" + streamid + " "
-                                        + "[" + Thread.currentThread().getName() + "] "
-                                        + "Too many bytes in request body. Expected: "
-                                        + contentLength + ", got: "
-                                        + (contentLength - remainingContentLength);
-                                connection.resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
-                                throw new IOException(msg);
-                            } else if (remainingContentLength == 0) {
-                                df.setFlag(DataFrame.END_STREAM);
-                                endStreamSent = true;
-                            }
-                        }
-                        debug.log(Level.DEBUG, "trySend: sending: %d", df.getDataLength());
-                        connection.sendDataFrame(df);
-                    }
-                    assert !item.hasRemaining();
-                    ByteBuffer b = outgoing.removeFirst();
-                    assert b == item;
-                } while (outgoing.peekFirst() != null);
-
-                debug.log(Level.DEBUG, "trySend: request 1");
-                subscription.request(1);
-            } catch (Throwable ex) {
-                debug.log(Level.DEBUG, "trySend: ", ex);
-                sendScheduler.stop();
-                subscription.cancel();
-                requestBodyCF.completeExceptionally(ex);
-            }
-        }
-
-        private void complete() throws IOException {
-            long remaining = remainingContentLength;
-            long written = contentLength - remaining;
-            if (remaining > 0) {
-                connection.resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
-                // let trySend() handle the exception
-                throw new IOException(connection().getConnectionFlow()
-                                     + " stream=" + streamid + " "
-                                     + "[" + Thread.currentThread().getName() +"] "
-                                     + "Too few bytes returned by the publisher ("
-                                              + written + "/"
-                                              + contentLength + ")");
-            }
-            if (!endStreamSent) {
-                endStreamSent = true;
-                connection.sendDataFrame(getEmptyEndStreamDataFrame());
-            }
-            requestBodyCF.complete(null);
-        }
-    }
-
-    /**
-     * Send a RESET frame to tell server to stop sending data on this stream
-     */
-    @Override
-    public CompletableFuture<Void> ignoreBody() {
-        try {
-            connection.resetStream(streamid, ResetFrame.STREAM_CLOSED);
-            return MinimalFuture.completedFuture(null);
-        } catch (Throwable e) {
-            Log.logTrace("Error resetting stream {0}", e.toString());
-            return MinimalFuture.failedFuture(e);
-        }
-    }
-
-    DataFrame getDataFrame(ByteBuffer buffer) {
-        int requestAmount = Math.min(connection.getMaxSendFrameSize(), buffer.remaining());
-        // blocks waiting for stream send window, if exhausted
-        int actualAmount = windowController.tryAcquire(requestAmount, streamid, this);
-        if (actualAmount <= 0) return null;
-        ByteBuffer outBuf = Utils.sliceWithLimitedCapacity(buffer,  actualAmount);
-        DataFrame df = new DataFrame(streamid, 0 , outBuf);
-        return df;
-    }
-
-    private DataFrame getEmptyEndStreamDataFrame()  {
-        return new DataFrame(streamid, DataFrame.END_STREAM, List.of());
-    }
-
-    /**
-     * A List of responses relating to this stream. Normally there is only
-     * one response, but intermediate responses like 100 are allowed
-     * and must be passed up to higher level before continuing. Deals with races
-     * such as if responses are returned before the CFs get created by
-     * getResponseAsync()
-     */
-
-    final List<CompletableFuture<Response>> response_cfs = new ArrayList<>(5);
-
-    @Override
-    CompletableFuture<Response> getResponseAsync(Executor executor) {
-        CompletableFuture<Response> cf;
-        // The code below deals with race condition that can be caused when
-        // completeResponse() is being called before getResponseAsync()
-        synchronized (response_cfs) {
-            if (!response_cfs.isEmpty()) {
-                // This CompletableFuture was created by completeResponse().
-                // it will be already completed.
-                cf = response_cfs.remove(0);
-                // if we find a cf here it should be already completed.
-                // finding a non completed cf should not happen. just assert it.
-                assert cf.isDone() : "Removing uncompleted response: could cause code to hang!";
-            } else {
-                // getResponseAsync() is called first. Create a CompletableFuture
-                // that will be completed by completeResponse() when
-                // completeResponse() is called.
-                cf = new MinimalFuture<>();
-                response_cfs.add(cf);
-            }
-        }
-        if (executor != null && !cf.isDone()) {
-            // protect from executing later chain of CompletableFuture operations from SelectorManager thread
-            cf = cf.thenApplyAsync(r -> r, executor);
-        }
-        Log.logTrace("Response future (stream={0}) is: {1}", streamid, cf);
-        PushGroup<?> pg = exchange.getPushGroup();
-        if (pg != null) {
-            // if an error occurs make sure it is recorded in the PushGroup
-            cf = cf.whenComplete((t,e) -> pg.pushError(Utils.getCompletionCause(e)));
-        }
-        return cf;
-    }
-
-    /**
-     * Completes the first uncompleted CF on list, and removes it. If there is no
-     * uncompleted CF then creates one (completes it) and adds to list
-     */
-    void completeResponse(Response resp) {
-        synchronized (response_cfs) {
-            CompletableFuture<Response> cf;
-            int cfs_len = response_cfs.size();
-            for (int i=0; i<cfs_len; i++) {
-                cf = response_cfs.get(i);
-                if (!cf.isDone()) {
-                    Log.logTrace("Completing response (streamid={0}): {1}",
-                                 streamid, cf);
-                    cf.complete(resp);
-                    response_cfs.remove(cf);
-                    return;
-                } // else we found the previous response: just leave it alone.
-            }
-            cf = MinimalFuture.completedFuture(resp);
-            Log.logTrace("Created completed future (streamid={0}): {1}",
-                         streamid, cf);
-            response_cfs.add(cf);
-        }
-    }
-
-    // methods to update state and remove stream when finished
-
-    synchronized void requestSent() {
-        requestSent = true;
-        if (responseReceived) {
-            close();
-        }
-    }
-
-    synchronized void responseReceived() {
-        responseReceived = true;
-        if (requestSent) {
-            close();
-        }
-    }
-
-    /**
-     * same as above but for errors
-     */
-    void completeResponseExceptionally(Throwable t) {
-        synchronized (response_cfs) {
-            // use index to avoid ConcurrentModificationException
-            // caused by removing the CF from within the loop.
-            for (int i = 0; i < response_cfs.size(); i++) {
-                CompletableFuture<Response> cf = response_cfs.get(i);
-                if (!cf.isDone()) {
-                    cf.completeExceptionally(t);
-                    response_cfs.remove(i);
-                    return;
-                }
-            }
-            response_cfs.add(MinimalFuture.failedFuture(t));
-        }
-    }
-
-    CompletableFuture<Void> sendBodyImpl() {
-        requestBodyCF.whenComplete((v, t) -> requestSent());
-        if (requestPublisher != null) {
-            final RequestSubscriber subscriber = new RequestSubscriber(requestContentLen);
-            requestPublisher.subscribe(requestSubscriber = subscriber);
-        } else {
-            // there is no request body, therefore the request is complete,
-            // END_STREAM has already sent with outgoing headers
-            requestBodyCF.complete(null);
-        }
-        return requestBodyCF;
-    }
-
-    @Override
-    void cancel() {
-        cancel(new IOException("Stream " + streamid + " cancelled"));
-    }
-
-    @Override
-    void cancel(IOException cause) {
-        cancelImpl(cause);
-    }
-
-    // This method sends a RST_STREAM frame
-    void cancelImpl(Throwable e) {
-        debug.log(Level.DEBUG, "cancelling stream {0}: {1}", streamid, e);
-        if (Log.trace()) {
-            Log.logTrace("cancelling stream {0}: {1}\n", streamid, e);
-        }
-        boolean closing;
-        if (closing = !closed) { // assigning closing to !closed
-            synchronized (this) {
-                failed = e;
-                if (closing = !closed) { // assigning closing to !closed
-                    closed=true;
-                }
-            }
-        }
-        if (closing) { // true if the stream has not been closed yet
-            if (responseSubscriber != null)
-                sched.runOrSchedule();
-        }
-        completeResponseExceptionally(e);
-        if (!requestBodyCF.isDone()) {
-            requestBodyCF.completeExceptionally(e); // we may be sending the body..
-        }
-        if (responseBodyCF != null) {
-            responseBodyCF.completeExceptionally(e);
-        }
-        try {
-            // will send a RST_STREAM frame
-            if (streamid != 0) {
-                connection.resetStream(streamid, ResetFrame.CANCEL);
-            }
-        } catch (IOException ex) {
-            Log.logError(ex);
-        }
-    }
-
-    // This method doesn't send any frame
-    void close() {
-        if (closed) return;
-        synchronized(this) {
-            if (closed) return;
-            closed = true;
-        }
-        Log.logTrace("Closing stream {0}", streamid);
-        connection.closeStream(streamid);
-        Log.logTrace("Stream {0} closed", streamid);
-    }
-
-    static class PushedStream<T> extends Stream<T> {
-        final PushGroup<T> pushGroup;
-        // push streams need the response CF allocated up front as it is
-        // given directly to user via the multi handler callback function.
-        final CompletableFuture<Response> pushCF;
-        CompletableFuture<HttpResponse<T>> responseCF;
-        final HttpRequestImpl pushReq;
-        HttpResponse.BodyHandler<T> pushHandler;
-
-        PushedStream(PushGroup<T> pushGroup,
-                     Http2Connection connection,
-                     Exchange<T> pushReq) {
-            // ## no request body possible, null window controller
-            super(connection, pushReq, null);
-            this.pushGroup = pushGroup;
-            this.pushReq = pushReq.request();
-            this.pushCF = new MinimalFuture<>();
-            this.responseCF = new MinimalFuture<>();
-
-        }
-
-        CompletableFuture<HttpResponse<T>> responseCF() {
-            return responseCF;
-        }
-
-        synchronized void setPushHandler(HttpResponse.BodyHandler<T> pushHandler) {
-            this.pushHandler = pushHandler;
-        }
-
-        synchronized HttpResponse.BodyHandler<T> getPushHandler() {
-            // ignored parameters to function can be used as BodyHandler
-            return this.pushHandler;
-        }
-
-        // Following methods call the super class but in case of
-        // error record it in the PushGroup. The error method is called
-        // with a null value when no error occurred (is a no-op)
-        @Override
-        CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
-            return super.sendBodyAsync()
-                        .whenComplete((ExchangeImpl<T> v, Throwable t)
-                                -> pushGroup.pushError(Utils.getCompletionCause(t)));
-        }
-
-        @Override
-        CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
-            return super.sendHeadersAsync()
-                        .whenComplete((ExchangeImpl<T> ex, Throwable t)
-                                -> pushGroup.pushError(Utils.getCompletionCause(t)));
-        }
-
-        @Override
-        CompletableFuture<Response> getResponseAsync(Executor executor) {
-            CompletableFuture<Response> cf = pushCF.whenComplete(
-                    (v, t) -> pushGroup.pushError(Utils.getCompletionCause(t)));
-            if(executor!=null && !cf.isDone()) {
-                cf  = cf.thenApplyAsync( r -> r, executor);
-            }
-            return cf;
-        }
-
-        @Override
-        CompletableFuture<T> readBodyAsync(
-                HttpResponse.BodyHandler<T> handler,
-                boolean returnConnectionToPool,
-                Executor executor)
-        {
-            return super.readBodyAsync(handler, returnConnectionToPool, executor)
-                        .whenComplete((v, t) -> pushGroup.pushError(t));
-        }
-
-        @Override
-        void completeResponse(Response r) {
-            Log.logResponse(r::toString);
-            pushCF.complete(r); // not strictly required for push API
-            // start reading the body using the obtained BodySubscriber
-            CompletableFuture<Void> start = new MinimalFuture<>();
-            start.thenCompose( v -> readBodyAsync(getPushHandler(), false, getExchange().executor()))
-                .whenComplete((T body, Throwable t) -> {
-                    if (t != null) {
-                        responseCF.completeExceptionally(t);
-                    } else {
-                        HttpResponseImpl<T> resp =
-                                new HttpResponseImpl<>(r.request, r, null, body, getExchange());
-                        responseCF.complete(resp);
-                    }
-                });
-            start.completeAsync(() -> null, getExchange().executor());
-        }
-
-        @Override
-        void completeResponseExceptionally(Throwable t) {
-            pushCF.completeExceptionally(t);
-        }
-
-//        @Override
-//        synchronized void responseReceived() {
-//            super.responseReceived();
-//        }
-
-        // create and return the PushResponseImpl
-        @Override
-        protected void handleResponse() {
-            responseCode = (int)responseHeaders
-                .firstValueAsLong(":status")
-                .orElse(-1);
-
-            if (responseCode == -1) {
-                completeResponseExceptionally(new IOException("No status code"));
-            }
-
-            this.response = new Response(
-                pushReq, exchange, responseHeaders,
-                responseCode, HttpClient.Version.HTTP_2);
-
-            /* TODO: review if needs to be removed
-               the value is not used, but in case `content-length` doesn't parse
-               as long, there will be NumberFormatException. If left as is, make
-               sure code up the stack handles NFE correctly. */
-            responseHeaders.firstValueAsLong("content-length");
-
-            if (Log.headers()) {
-                StringBuilder sb = new StringBuilder("RESPONSE HEADERS");
-                sb.append(" (streamid=").append(streamid).append("): ");
-                Log.dumpHeaders(sb, "    ", responseHeaders);
-                Log.logHeaders(sb.toString());
-            }
-
-            // different implementations for normal streams and pushed streams
-            completeResponse(response);
-        }
-    }
-
-    final class StreamWindowUpdateSender extends WindowUpdateSender {
-
-        StreamWindowUpdateSender(Http2Connection connection) {
-            super(connection);
-        }
-
-        @Override
-        int getStreamId() {
-            return streamid;
-        }
-    }
-
-    /**
-     * Returns true if this exchange was canceled.
-     * @return true if this exchange was canceled.
-     */
-    synchronized boolean isCanceled() {
-        return failed != null;
-    }
-
-    /**
-     * Returns the cause for which this exchange was canceled, if available.
-     * @return the cause for which this exchange was canceled, if available.
-     */
-    synchronized Throwable getCancelCause() {
-        return failed;
-    }
-
-    final String dbgString() {
-        return connection.dbgString() + "/Stream("+streamid+")";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/TimeoutEvent.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Timeout event notified by selector thread. Executes the given handler if
- * the timer not canceled first.
- *
- * Register with {@link HttpClientImpl#registerTimer(TimeoutEvent)}.
- *
- * Cancel with {@link HttpClientImpl#cancelTimer(TimeoutEvent)}.
- */
-abstract class TimeoutEvent implements Comparable<TimeoutEvent> {
-
-    private static final AtomicLong COUNTER = new AtomicLong();
-    // we use id in compareTo to make compareTo consistent with equals
-    // see TimeoutEvent::compareTo below;
-    private final long id = COUNTER.incrementAndGet();
-    private final Instant deadline;
-
-    TimeoutEvent(Duration duration) {
-        deadline = Instant.now().plus(duration);
-    }
-
-    public abstract void handle();
-
-    public Instant deadline() {
-        return deadline;
-    }
-
-    @Override
-    public int compareTo(TimeoutEvent other) {
-        if (other == this) return 0;
-        // if two events have the same deadline, but are not equals, then the
-        // smaller is the one that was created before (has the smaller id).
-        // This is arbitrary and we don't really care which is smaller or
-        // greater, but we need a total order, so two events with the
-        // same deadline cannot compare == 0 if they are not equals.
-        final int compareDeadline = this.deadline.compareTo(other.deadline);
-        if (compareDeadline == 0 && !this.equals(other)) {
-            long diff = this.id - other.id; // should take care of wrap around
-            if (diff < 0) return -1;
-            else if (diff > 0) return 1;
-            else assert false : "Different events with same id and deadline";
-        }
-        return compareDeadline;
-    }
-
-    @Override
-    public String toString() {
-        return "TimeoutEvent[id=" + id + ", deadline=" + deadline + "]";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowController.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,320 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.lang.System.Logger.Level;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * A Send Window Flow-Controller that is used to control outgoing Connection
- * and Stream flows, per HTTP/2 connection.
- *
- * A Http2Connection has its own unique single instance of a WindowController
- * that it shares with its Streams. Each stream must acquire the appropriate
- * amount of Send Window from the controller before sending data.
- *
- * WINDOW_UPDATE frames, both connection and stream specific, must notify the
- * controller of their increments. SETTINGS frame's INITIAL_WINDOW_SIZE must
- * notify the controller so that it can adjust the active stream's window size.
- */
-final class WindowController {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary developer's flag
-    static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("WindowController"::toString, DEBUG);
-
-    /**
-     * Default initial connection Flow-Control Send Window size, as per HTTP/2.
-     */
-    private static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024 - 1;
-
-    /** The connection Send Window size. */
-    private int connectionWindowSize;
-    /** A Map of the active streams, where the key is the stream id, and the
-     *  value is the stream's Send Window size, which may be negative. */
-    private final Map<Integer,Integer> streams = new HashMap<>();
-    /** A Map of streams awaiting Send Window. The key is the stream id. The
-     * value is a pair of the Stream ( representing the key's stream id ) and
-     * the requested amount of send Window. */
-    private final Map<Integer, Map.Entry<Stream<?>, Integer>> pending
-            = new LinkedHashMap<>();
-
-    private final ReentrantLock controllerLock = new ReentrantLock();
-
-    /** A Controller with the default initial window size. */
-    WindowController() {
-        connectionWindowSize = DEFAULT_INITIAL_WINDOW_SIZE;
-    }
-
-//    /** A Controller with the given initial window size. */
-//    WindowController(int initialConnectionWindowSize) {
-//        connectionWindowSize = initialConnectionWindowSize;
-//    }
-
-    /** Registers the given stream with this controller. */
-    void registerStream(int streamid, int initialStreamWindowSize) {
-        controllerLock.lock();
-        try {
-            Integer old = streams.put(streamid, initialStreamWindowSize);
-            if (old != null)
-                throw new InternalError("Unexpected entry ["
-                        + old + "] for streamid: " + streamid);
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /** Removes/De-registers the given stream with this controller. */
-    void removeStream(int streamid) {
-        controllerLock.lock();
-        try {
-            Integer old = streams.remove(streamid);
-            // Odd stream numbers (client streams) should have been registered.
-            // Even stream numbers (server streams - aka Push Streams) should
-            // not be registered
-            final boolean isClientStream = (streamid % 2) == 1;
-            if (old == null && isClientStream) {
-                throw new InternalError("Expected entry for streamid: " + streamid);
-            } else if (old != null && !isClientStream) {
-                throw new InternalError("Unexpected entry for streamid: " + streamid);
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /**
-     * Attempts to acquire the requested amount of Send Window for the given
-     * stream.
-     *
-     * The actual amount of Send Window available may differ from the requested
-     * amount. The actual amount, returned by this method, is the minimum of,
-     * 1) the requested amount, 2) the stream's Send Window, and 3) the
-     * connection's Send Window.
-     *
-     * A negative or zero value is returned if there's no window available.
-     * When the result is negative or zero, this method arranges for the
-     * given stream's {@link Stream#signalWindowUpdate()} method to be invoke at
-     * a later time when the connection and/or stream window's have been
-     * increased. The {@code tryAcquire} method should then be invoked again to
-     * attempt to acquire the available window.
-     */
-    int tryAcquire(int requestAmount, int streamid, Stream<?> stream) {
-        controllerLock.lock();
-        try {
-            Integer streamSize = streams.get(streamid);
-            if (streamSize == null)
-                throw new InternalError("Expected entry for streamid: "
-                                        + streamid);
-            int x = Math.min(requestAmount,
-                             Math.min(streamSize, connectionWindowSize));
-
-            if (x <= 0)  { // stream window size may be negative
-                DEBUG_LOGGER.log(Level.DEBUG,
-                        "Stream %d requesting %d but only %d available (stream: %d, connection: %d)",
-                      streamid, requestAmount, Math.min(streamSize, connectionWindowSize),
-                      streamSize, connectionWindowSize);
-                // If there's not enough window size available, put the
-                // caller in a pending list.
-                pending.put(streamid, Map.entry(stream, requestAmount));
-                return x;
-            }
-
-            // Remove the caller from the pending list ( if was waiting ).
-            pending.remove(streamid);
-
-            // Update window sizes and return the allocated amount to the caller.
-            streamSize -= x;
-            streams.put(streamid, streamSize);
-            connectionWindowSize -= x;
-            DEBUG_LOGGER.log(Level.DEBUG,
-                  "Stream %d amount allocated %d, now %d available (stream: %d, connection: %d)",
-                  streamid, x, Math.min(streamSize, connectionWindowSize),
-                  streamSize, connectionWindowSize);
-            return x;
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /**
-     * Increases the Send Window size for the connection.
-     *
-     * A number of awaiting requesters, from unfulfilled tryAcquire requests,
-     * may have their stream's {@link Stream#signalWindowUpdate()} method
-     * scheduled to run ( i.e. awake awaiters ).
-     *
-     * @return false if, and only if, the addition of the given amount would
-     *         cause the Send Window to exceed 2^31-1 (overflow), otherwise true
-     */
-    boolean increaseConnectionWindow(int amount) {
-        List<Stream<?>> candidates = null;
-        controllerLock.lock();
-        try {
-            int size = connectionWindowSize;
-            size += amount;
-            if (size < 0)
-                return false;
-            connectionWindowSize = size;
-            DEBUG_LOGGER.log(Level.DEBUG, "Connection window size is now %d", size);
-
-            // Notify waiting streams, until the new increased window size is
-            // effectively exhausted.
-            Iterator<Map.Entry<Integer,Map.Entry<Stream<?>,Integer>>> iter =
-                    pending.entrySet().iterator();
-
-            while (iter.hasNext() && size > 0) {
-                Map.Entry<Integer,Map.Entry<Stream<?>,Integer>> item = iter.next();
-                Integer streamSize = streams.get(item.getKey());
-                if (streamSize == null) {
-                    iter.remove();
-                } else {
-                    Map.Entry<Stream<?>,Integer> e = item.getValue();
-                    int requestedAmount = e.getValue();
-                    // only wakes up the pending streams for which there is
-                    // at least 1 byte of space in both windows
-                    int minAmount = 1;
-                    if (size >= minAmount && streamSize >= minAmount) {
-                        size -= Math.min(streamSize, requestedAmount);
-                        iter.remove();
-                        if (candidates == null)
-                            candidates = new ArrayList<>();
-                        candidates.add(e.getKey());
-                    }
-                }
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-        if (candidates != null) {
-            candidates.forEach(Stream::signalWindowUpdate);
-        }
-        return true;
-    }
-
-    /**
-     * Increases the Send Window size for the given stream.
-     *
-     * If the given stream is awaiting window size, from an unfulfilled
-     * tryAcquire request, it will have its stream's {@link
-     * Stream#signalWindowUpdate()} method scheduled to run ( i.e. awoken ).
-     *
-     * @return false if, and only if, the addition of the given amount would
-     *         cause the Send Window to exceed 2^31-1 (overflow), otherwise true
-     */
-    boolean increaseStreamWindow(int amount, int streamid) {
-        Stream<?> s = null;
-        controllerLock.lock();
-        try {
-            Integer size = streams.get(streamid);
-            if (size == null)
-                throw new InternalError("Expected entry for streamid: " + streamid);
-            size += amount;
-            if (size < 0)
-                return false;
-            streams.put(streamid, size);
-            DEBUG_LOGGER.log(Level.DEBUG,
-                             "Stream %s window size is now %s", streamid, size);
-
-            Map.Entry<Stream<?>,Integer> p = pending.get(streamid);
-            if (p != null) {
-                int minAmount = 1;
-                // only wakes up the pending stream if there is at least
-                // 1 byte of space in both windows
-                if (size >= minAmount
-                        && connectionWindowSize >= minAmount) {
-                     pending.remove(streamid);
-                     s = p.getKey();
-                }
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-
-        if (s != null)
-            s.signalWindowUpdate();
-
-        return true;
-    }
-
-    /**
-     * Adjusts, either increases or decreases, the active streams registered
-     * with this controller.  May result in a stream's Send Window size becoming
-     * negative.
-     */
-    void adjustActiveStreams(int adjustAmount) {
-        assert adjustAmount != 0;
-
-        controllerLock.lock();
-        try {
-            for (Map.Entry<Integer,Integer> entry : streams.entrySet()) {
-                int streamid = entry.getKey();
-                // the API only supports sending on Streams initialed by
-                // the client, i.e. odd stream numbers
-                if (streamid != 0 && (streamid % 2) != 0) {
-                    Integer size = entry.getValue();
-                    size += adjustAmount;
-                    streams.put(streamid, size);
-                    DEBUG_LOGGER.log(Level.DEBUG,
-                        "Stream %s window size is now %s", streamid, size);
-                }
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /** Returns the Send Window size for the connection. */
-    int connectionWindowSize() {
-        controllerLock.lock();
-        try {
-            return connectionWindowSize;
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-//    /** Returns the Send Window size for the given stream. */
-//    int streamWindowSize(int streamid) {
-//        controllerLock.lock();
-//        try {
-//            Integer size = streams.get(streamid);
-//            if (size == null)
-//                throw new InternalError("Expected entry for streamid: " + streamid);
-//            return size;
-//        } finally {
-//            controllerLock.unlock();
-//        }
-//    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowUpdateSender.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http;
-
-import java.lang.System.Logger.Level;
-import jdk.incubator.http.internal.frame.SettingsFrame;
-import jdk.incubator.http.internal.frame.WindowUpdateFrame;
-import jdk.incubator.http.internal.common.Utils;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-abstract class WindowUpdateSender {
-
-    final static boolean DEBUG = Utils.DEBUG;
-    final System.Logger debug =
-            Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    final int limit;
-    final Http2Connection connection;
-    final AtomicInteger received = new AtomicInteger(0);
-
-    WindowUpdateSender(Http2Connection connection) {
-        this(connection, connection.clientSettings.getParameter(SettingsFrame.INITIAL_WINDOW_SIZE));
-    }
-
-    WindowUpdateSender(Http2Connection connection, int initWindowSize) {
-        this(connection, connection.getMaxReceiveFrameSize(), initWindowSize);
-    }
-
-    WindowUpdateSender(Http2Connection connection, int maxFrameSize, int initWindowSize) {
-        this.connection = connection;
-        int v0 = Math.max(0, initWindowSize - maxFrameSize);
-        int v1 = (initWindowSize + (maxFrameSize - 1)) / maxFrameSize;
-        v1 = v1 * maxFrameSize / 2;
-        // send WindowUpdate heuristic:
-        // - we got data near half of window size
-        //   or
-        // - remaining window size reached max frame size.
-        limit = Math.min(v0, v1);
-        debug.log(Level.DEBUG, "maxFrameSize=%d, initWindowSize=%d, limit=%d",
-                maxFrameSize, initWindowSize, limit);
-    }
-
-    abstract int getStreamId();
-
-    void update(int delta) {
-        debug.log(Level.DEBUG, "update: %d", delta);
-        if (received.addAndGet(delta) > limit) {
-            synchronized (this) {
-                int tosend = received.get();
-                if( tosend > limit) {
-                    received.getAndAdd(-tosend);
-                    sendWindowUpdate(tosend);
-                }
-            }
-        }
-    }
-
-    void sendWindowUpdate(int delta) {
-        debug.log(Level.DEBUG, "sending window update: %d", delta);
-        connection.sendUnorderedFrame(new WindowUpdateFrame(getStreamId(), delta));
-    }
-
-    String dbgString() {
-        return "WindowUpdateSender(stream: " + getStreamId() + ")";
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AbstractAsyncSSLConnection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import javax.net.ssl.SNIHostName;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLParameters;
+
+import jdk.incubator.http.internal.common.SSLTube;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.Utils;
+
+
+/**
+ * Asynchronous version of SSLConnection.
+ *
+ * There are two concrete implementations of this class: AsyncSSLConnection
+ * and AsyncSSLTunnelConnection.
+ * This abstraction is useful when downgrading from HTTP/2 to HTTP/1.1 over
+ * an SSL connection. See ExchangeImpl::get in the case where an ALPNException
+ * is thrown.
+ *
+ * Note: An AsyncSSLConnection wraps a PlainHttpConnection, while an
+ *       AsyncSSLTunnelConnection wraps a PlainTunnelingConnection.
+ *       If both these wrapped classes where made to inherit from a
+ *       common abstraction then it might be possible to merge
+ *       AsyncSSLConnection and AsyncSSLTunnelConnection back into
+ *       a single class - and simply use different factory methods to
+ *       create different wrappees, but this is left up for further cleanup.
+ *
+ */
+abstract class AbstractAsyncSSLConnection extends HttpConnection
+{
+    protected final SSLEngine engine;
+    protected final String serverName;
+    protected final SSLParameters sslParameters;
+
+    AbstractAsyncSSLConnection(InetSocketAddress addr,
+                               HttpClientImpl client,
+                               String serverName,
+                               String[] alpn) {
+        super(addr, client);
+        this.serverName = serverName;
+        SSLContext context = client.theSSLContext();
+        sslParameters = createSSLParameters(client, serverName, alpn);
+        Log.logParams(sslParameters);
+        engine = createEngine(context, sslParameters);
+    }
+
+    abstract HttpConnection plainConnection();
+    abstract SSLTube getConnectionFlow();
+
+    final CompletableFuture<String> getALPN() {
+        assert connected();
+        return getConnectionFlow().getALPN();
+    }
+
+    final SSLEngine getEngine() { return engine; }
+
+    private static SSLParameters createSSLParameters(HttpClientImpl client,
+                                                     String serverName,
+                                                     String[] alpn) {
+        SSLParameters sslp = client.sslParameters();
+        SSLParameters sslParameters = Utils.copySSLParameters(sslp);
+        if (alpn != null) {
+            Log.logSSL("AbstractAsyncSSLConnection: Setting application protocols: {0}",
+                       Arrays.toString(alpn));
+            sslParameters.setApplicationProtocols(alpn);
+        } else {
+            Log.logSSL("AbstractAsyncSSLConnection: no applications set!");
+        }
+        if (serverName != null) {
+            sslParameters.setServerNames(List.of(new SNIHostName(serverName)));
+        }
+        return sslParameters;
+    }
+
+    private static SSLEngine createEngine(SSLContext context,
+                                          SSLParameters sslParameters) {
+        SSLEngine engine = context.createSSLEngine();
+        engine.setUseClientMode(true);
+        engine.setSSLParameters(sslParameters);
+        return engine;
+    }
+
+    @Override
+    final boolean isSecure() {
+        return true;
+    }
+
+    // Support for WebSocket/RawChannelImpl which unfortunately
+    // still depends on synchronous read/writes.
+    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
+    static final class SSLConnectionChannel extends DetachedConnectionChannel {
+        final DetachedConnectionChannel delegate;
+        final SSLDelegate sslDelegate;
+        SSLConnectionChannel(DetachedConnectionChannel delegate, SSLDelegate sslDelegate) {
+            this.delegate = delegate;
+            this.sslDelegate = sslDelegate;
+        }
+
+        SocketChannel channel() {
+            return delegate.channel();
+        }
+
+        @Override
+        ByteBuffer read() throws IOException {
+            SSLDelegate.WrapperResult r = sslDelegate.recvData(ByteBuffer.allocate(8192));
+            // TODO: check for closure
+            int n = r.result.bytesProduced();
+            if (n > 0) {
+                return r.buf;
+            } else if (n == 0) {
+                return Utils.EMPTY_BYTEBUFFER;
+            } else {
+                return null;
+            }
+        }
+        @Override
+        long write(ByteBuffer[] buffers, int start, int number) throws IOException {
+            long l = SSLDelegate.countBytes(buffers, start, number);
+            SSLDelegate.WrapperResult r = sslDelegate.sendData(buffers, start, number);
+            if (r.result.getStatus() == SSLEngineResult.Status.CLOSED) {
+                if (l > 0) {
+                    throw new IOException("SSLHttpConnection closed");
+                }
+            }
+            return l;
+        }
+        @Override
+        public void shutdownInput() throws IOException {
+            delegate.shutdownInput();
+        }
+        @Override
+        public void shutdownOutput() throws IOException {
+            delegate.shutdownOutput();
+        }
+        @Override
+        public void close() {
+            delegate.close();
+        }
+    }
+
+    // Support for WebSocket/RawChannelImpl which unfortunately
+    // still depends on synchronous read/writes.
+    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
+    @Override
+    DetachedConnectionChannel detachChannel() {
+        assert client() != null;
+        DetachedConnectionChannel detachedChannel = plainConnection().detachChannel();
+        SSLDelegate sslDelegate = new SSLDelegate(engine,
+                                                  detachedChannel.channel());
+        return new SSLConnectionChannel(detachedChannel, sslDelegate);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AbstractSubscription.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.util.concurrent.Flow;
+import jdk.incubator.http.internal.common.Demand;
+
+/**
+ * A {@link Flow.Subscription} wrapping a {@link Demand} instance.
+ *
+ */
+abstract class AbstractSubscription implements Flow.Subscription {
+
+    private final Demand demand = new Demand();
+
+    /**
+     * Returns the subscription's demand.
+     * @return the subscription's demand.
+     */
+    protected Demand demand() { return demand; }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncEvent.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.nio.channels.SelectableChannel;
+
+/**
+ * Event handling interface from HttpClientImpl's selector.
+ *
+ * If REPEATING is set then the event is not cancelled after being posted.
+ */
+abstract class AsyncEvent {
+
+    public static final int REPEATING = 0x2; // one off event if not set
+
+    protected final int flags;
+
+    AsyncEvent() {
+        this(0);
+    }
+
+    AsyncEvent(int flags) {
+        this.flags = flags;
+    }
+
+    /** 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 an error occurs during registration, or when the selector has
+     * been shut down. Aborts all exchanges.
+     *
+     * @param ioe  the IOException, or null
+     */
+    public abstract void abort(IOException ioe);
+
+    public boolean repeating() {
+        return (flags & REPEATING) != 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncSSLConnection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CompletableFuture;
+import jdk.incubator.http.internal.common.SSLTube;
+import jdk.incubator.http.internal.common.Utils;
+
+
+/**
+ * Asynchronous version of SSLConnection.
+ */
+class AsyncSSLConnection extends AbstractAsyncSSLConnection {
+
+    final PlainHttpConnection plainConnection;
+    final PlainHttpPublisher writePublisher;
+    private volatile SSLTube flow;
+
+    AsyncSSLConnection(InetSocketAddress addr,
+                       HttpClientImpl client,
+                       String[] alpn) {
+        super(addr, client, Utils.getServerName(addr), alpn);
+        plainConnection = new PlainHttpConnection(addr, client);
+        writePublisher = new PlainHttpPublisher();
+    }
+
+    @Override
+    PlainHttpConnection plainConnection() {
+        return plainConnection;
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        return plainConnection
+                .connectAsync()
+                .thenApply( unused -> {
+                    // create the SSLTube wrapping the SocketTube, with the given engine
+                    flow = new SSLTube(engine,
+                                       client().theExecutor(),
+                                       plainConnection.getConnectionFlow());
+                    return null; } );
+    }
+
+    @Override
+    boolean connected() {
+        return plainConnection.connected();
+    }
+
+    @Override
+    HttpPublisher publisher() { return writePublisher; }
+
+    @Override
+    boolean isProxied() {
+        return false;
+    }
+
+    @Override
+    SocketChannel channel() {
+        return plainConnection.channel();
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return ConnectionPool.cacheKey(address, null);
+    }
+
+    @Override
+    public void close() {
+        plainConnection.close();
+    }
+
+    @Override
+    void shutdownInput() throws IOException {
+        debug.log(Level.DEBUG, "plainConnection.channel().shutdownInput()");
+        plainConnection.channel().shutdownInput();
+    }
+
+    @Override
+    void shutdownOutput() throws IOException {
+        debug.log(Level.DEBUG, "plainConnection.channel().shutdownOutput()");
+        plainConnection.channel().shutdownOutput();
+    }
+
+   @Override
+   SSLTube getConnectionFlow() {
+       return flow;
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncSSLTunnelConnection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CompletableFuture;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.internal.common.SSLTube;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * An SSL tunnel built on a Plain (CONNECT) TCP tunnel.
+ */
+class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
+
+    final PlainTunnelingConnection plainConnection;
+    final PlainHttpPublisher writePublisher;
+    volatile SSLTube flow;
+
+    AsyncSSLTunnelConnection(InetSocketAddress addr,
+                             HttpClientImpl client,
+                             String[] alpn,
+                             InetSocketAddress proxy,
+                             HttpHeaders proxyHeaders)
+    {
+        super(addr, client, Utils.getServerName(addr), alpn);
+        this.plainConnection = new PlainTunnelingConnection(addr, proxy, client, proxyHeaders);
+        this.writePublisher = new PlainHttpPublisher();
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        debug.log(Level.DEBUG, "Connecting plain tunnel connection");
+        // This will connect the PlainHttpConnection flow, so that
+        // its HttpSubscriber and HttpPublisher are subscribed to the
+        // SocketTube
+        return plainConnection
+                .connectAsync()
+                .thenApply( unused -> {
+                    debug.log(Level.DEBUG, "creating SSLTube");
+                    // create the SSLTube wrapping the SocketTube, with the given engine
+                    flow = new SSLTube(engine,
+                                       client().theExecutor(),
+                                       plainConnection.getConnectionFlow());
+                    return null;} );
+    }
+
+    @Override
+    boolean isTunnel() { return true; }
+
+    @Override
+    boolean connected() {
+        return plainConnection.connected(); // && sslDelegate.connected();
+    }
+
+    @Override
+    HttpPublisher publisher() { return writePublisher; }
+
+    @Override
+    public String toString() {
+        return "AsyncSSLTunnelConnection: " + super.toString();
+    }
+
+    @Override
+    PlainTunnelingConnection plainConnection() {
+        return plainConnection;
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return ConnectionPool.cacheKey(address, plainConnection.proxyAddr);
+    }
+
+    @Override
+    public void close() {
+        plainConnection.close();
+    }
+
+    @Override
+    void shutdownInput() throws IOException {
+        plainConnection.channel().shutdownInput();
+    }
+
+    @Override
+    void shutdownOutput() throws IOException {
+        plainConnection.channel().shutdownOutput();
+    }
+
+    @Override
+    SocketChannel channel() {
+        return plainConnection.channel();
+    }
+
+    @Override
+    boolean isProxied() {
+        return true;
+    }
+
+    @Override
+    SSLTube getConnectionFlow() {
+       return flow;
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AsyncTriggerEvent.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.nio.channels.SelectableChannel;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * An asynchronous event which is triggered only once from the selector manager
+ * thread as soon as event registration are handled.
+ */
+final class AsyncTriggerEvent extends AsyncEvent{
+
+    private final Runnable trigger;
+    private final Consumer<? super IOException> errorHandler;
+    AsyncTriggerEvent(Consumer<? super IOException> errorHandler,
+                      Runnable trigger) {
+        super(0);
+        this.trigger = Objects.requireNonNull(trigger);
+        this.errorHandler = Objects.requireNonNull(errorHandler);
+    }
+    /** Returns null */
+    @Override
+    public SelectableChannel channel() { return null; }
+    /** Returns 0 */
+    @Override
+    public int interestOps() { return 0; }
+    @Override
+    public void handle() { trigger.run(); }
+    @Override
+    public void abort(IOException ioe) { errorHandler.accept(ioe); }
+    @Override
+    public boolean repeating() { return false; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/AuthenticationFilter.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.InetSocketAddress;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Base64;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.Utils;
+import static java.net.Authenticator.RequestorType.PROXY;
+import static java.net.Authenticator.RequestorType.SERVER;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+/**
+ * Implementation of Http Basic authentication.
+ */
+class AuthenticationFilter implements HeaderFilter {
+    volatile MultiExchange<?> exchange;
+    private static final Base64.Encoder encoder = Base64.getEncoder();
+
+    static final int DEFAULT_RETRY_LIMIT = 3;
+
+    static final int retry_limit = Utils.getIntegerNetProperty(
+            "jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT);
+
+    static final int UNAUTHORIZED = 401;
+    static final int PROXY_UNAUTHORIZED = 407;
+
+    private static final List<String> BASIC_DUMMY =
+            List.of("Basic " + Base64.getEncoder()
+                    .encodeToString("o:o".getBytes(ISO_8859_1)));
+
+    // A public no-arg constructor is required by FilterFactory
+    public AuthenticationFilter() {}
+
+    private PasswordAuthentication getCredentials(String header,
+                                                  boolean proxy,
+                                                  HttpRequestImpl req)
+        throws IOException
+    {
+        HttpClientImpl client = exchange.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;
+        URL url = toURL(uri, req.method(), proxy);
+
+        // needs to be instance method in Authenticator
+        return auth.requestPasswordAuthenticationInstance(uri.getHost(),
+                                                          null,
+                                                          uri.getPort(),
+                                                          uri.getScheme(),
+                                                          realm,
+                                                          authscheme,
+                                                          url,
+                                                          rtype
+        );
+    }
+
+    private URL toURL(URI uri, String method, boolean proxy)
+            throws MalformedURLException
+    {
+        if (proxy && "CONNECT".equalsIgnoreCase(method)
+                && "socket".equalsIgnoreCase(uri.getScheme())) {
+            return null; // proxy tunneling
+        }
+        return uri.toURL();
+    }
+
+    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, MultiExchange<?> e) throws IOException {
+        // use preemptive authentication if an entry exists.
+        Cache cache = getCache(e);
+        this.exchange = e;
+
+        // Proxy
+        if (exchange.proxyauth == null) {
+            URI proxyURI = getProxyURI(r);
+            if (proxyURI != null) {
+                CacheEntry ca = cache.get(proxyURI, true);
+                if (ca != null) {
+                    exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca);
+                    addBasicCredentials(r, true, ca.value);
+                }
+            }
+        }
+
+        // Server
+        if (exchange.serverauth == null) {
+            CacheEntry ca = cache.get(r.uri(), false);
+            if (ca != null) {
+                exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca);
+                addBasicCredentials(r, false, ca.value);
+            }
+        }
+    }
+
+    // TODO: refactor into per auth scheme class
+    private static 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;
+        if (proxy) {
+            if (r.isConnect()) {
+                if (!Utils.PROXY_TUNNEL_FILTER
+                        .test(hdrname, List.of(value))) {
+                    Log.logError("{0} disabled", hdrname);
+                    return;
+                }
+            } else if (r.proxy() != null) {
+                if (!Utils.PROXY_FILTER
+                        .test(hdrname, List.of(value))) {
+                    Log.logError("{0} disabled", hdrname);
+                    return;
+                }
+            }
+        }
+        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;
+        }
+
+        AuthInfo retryWithCredentials(PasswordAuthentication pw) {
+            // If the info was already in the cache we need to create a new
+            // instance with fromCache==false so that it's put back in the
+            // cache if authentication succeeds
+            AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw) : this;
+            res.credentials = Objects.requireNonNull(pw);
+            res.retries = retries;
+            return res;
+        }
+
+    }
+
+    @Override
+    public HttpRequestImpl response(Response r) throws IOException {
+        Cache cache = getCache(exchange);
+        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 (exchange.serverauth != null && !exchange.serverauth.fromcache) {
+                AuthInfo au = exchange.serverauth;
+                cache.store(au.scheme, req.uri(), false, au.credentials);
+            }
+            if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
+                AuthInfo au = exchange.proxyauth;
+                cache.store(au.scheme, req.uri(), false, au.credentials);
+            }
+            return null;
+        }
+
+        boolean proxy = status == PROXY_UNAUTHORIZED;
+        String authname = proxy ? "Proxy-Authenticate" : "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
+        }
+
+        if (proxy) {
+            if (r.isConnectResponse) {
+                if (!Utils.PROXY_TUNNEL_FILTER
+                        .test("Proxy-Authorization", BASIC_DUMMY)) {
+                    Log.logError("{0} disabled", "Proxy-Authorization");
+                    return null;
+                }
+            } else if (req.proxy() != null) {
+                if (!Utils.PROXY_FILTER
+                        .test("Proxy-Authorization", BASIC_DUMMY)) {
+                    Log.logError("{0} disabled", "Proxy-Authorization");
+                    return null;
+                }
+            }
+        }
+
+        AuthInfo au = proxy ? exchange.proxyauth : exchange.serverauth;
+        if (au == null) {
+            // if no authenticator, let the user deal with 407/401
+            if (!exchange.client().authenticator().isPresent()) return 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) {
+                exchange.proxyauth = au;
+            } else {
+                exchange.serverauth = au;
+            }
+            addBasicCredentials(req, proxy, pw);
+            return req;
+        } else if (au.retries > retry_limit) {
+            throw new IOException("too many authentication attempts. Limit: " +
+                    Integer.toString(retry_limit));
+        } else {
+            // we sent credentials, but they were rejected
+            if (au.fromcache) {
+                cache.remove(au.cacheEntry);
+            }
+
+            // if no authenticator, let the user deal with 407/401
+            if (!exchange.client().authenticator().isPresent()) return null;
+
+            // try again
+            PasswordAuthentication pw = getCredentials(authval, proxy, req);
+            if (pw == null) {
+                throw new IOException("No credentials provided");
+            }
+            au = au.retryWithCredentials(pw);
+            if (proxy) {
+                exchange.proxyauth = au;
+            } else {
+                exchange.serverauth = au;
+            }
+            addBasicCredentials(req, proxy, au.credentials);
+            au.retries++;
+            return req;
+        }
+    }
+
+    // Use a WeakHashMap to make it possible for the HttpClient to
+    // be garbaged collected when no longer referenced.
+    static final WeakHashMap<HttpClientImpl,Cache> caches = new WeakHashMap<>();
+
+    static synchronized Cache getCache(MultiExchange<?> exchange) {
+        HttpClientImpl client = exchange.client();
+        Cache c = caches.get(client);
+        if (c == null) {
+            c = new Cache();
+            caches.put(client, c);
+        }
+        return c;
+    }
+
+    // Note: Make sure that Cache and CacheEntry do not keep any strong
+    //       reference to the HttpClient: it would prevent the client being
+    //       GC'ed when no longer referenced.
+    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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ConnectionPool.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,490 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.Flow;
+import java.util.stream.Collectors;
+import jdk.incubator.http.internal.common.FlowTube;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * Http 1.1 connection pool.
+ */
+final class ConnectionPool {
+
+    static final long KEEP_ALIVE = Utils.getIntegerNetProperty(
+            "jdk.httpclient.keepalive.timeout", 1200); // seconds
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    // Pools of idle connections
+
+    private final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool;
+    private final HashMap<CacheKey,LinkedList<HttpConnection>> sslPool;
+    private final ExpiryList expiryList;
+    private final String dbgTag; // used for debug
+    boolean stopped;
+
+    /**
+     * 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);
+        }
+    }
+
+    ConnectionPool(long clientId) {
+        this("ConnectionPool("+clientId+")");
+    }
+
+    /**
+     * There should be one of these per HttpClient.
+     */
+    private ConnectionPool(String tag) {
+        dbgTag = tag;
+        plainPool = new HashMap<>();
+        sslPool = new HashMap<>();
+        expiryList = new ExpiryList();
+    }
+
+    final String dbgString() {
+        return dbgTag;
+    }
+
+    synchronized void start() {
+        assert !stopped : "Already stopped";
+    }
+
+    static CacheKey cacheKey(InetSocketAddress destination,
+                             InetSocketAddress proxy)
+    {
+        return new CacheKey(destination, proxy);
+    }
+
+    synchronized HttpConnection getConnection(boolean secure,
+                                              InetSocketAddress addr,
+                                              InetSocketAddress proxy) {
+        if (stopped) return null;
+        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.
+     */
+    void returnToPool(HttpConnection conn) {
+        returnToPool(conn, Instant.now(), KEEP_ALIVE);
+    }
+
+    // Called also by whitebox tests
+    void returnToPool(HttpConnection conn, Instant now, long keepAlive) {
+
+        // Don't call registerCleanupTrigger while holding a lock,
+        // but register it before the connection is added to the pool,
+        // since we don't want to trigger the cleanup if the connection
+        // is not in the pool.
+        CleanupTrigger cleanup = registerCleanupTrigger(conn);
+
+        // it's possible that cleanup may have been called.
+        synchronized(this) {
+            if (cleanup.isDone()) {
+                return;
+            } else if (stopped) {
+                conn.close();
+                return;
+            }
+            if (conn instanceof PlainHttpConnection) {
+                putConnection(conn, plainPool);
+            } else {
+                assert conn.isSecure();
+                putConnection(conn, sslPool);
+            }
+            expiryList.add(conn, now, keepAlive);
+        }
+        //System.out.println("Return to pool: " + conn);
+    }
+
+    private CleanupTrigger registerCleanupTrigger(HttpConnection conn) {
+        // Connect the connection flow to a pub/sub pair that will take the
+        // connection out of the pool and close it if anything happens
+        // while the connection is sitting in the pool.
+        CleanupTrigger cleanup = new CleanupTrigger(conn);
+        FlowTube flow = conn.getConnectionFlow();
+        debug.log(Level.DEBUG, "registering %s", cleanup);
+        flow.connectFlows(cleanup, cleanup);
+        return cleanup;
+    }
+
+    private HttpConnection
+    findConnection(CacheKey key,
+                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
+        LinkedList<HttpConnection> l = pool.get(key);
+        if (l == null || l.isEmpty()) {
+            return null;
+        } else {
+            HttpConnection c = l.removeFirst();
+            expiryList.remove(c);
+            return c;
+        }
+    }
+
+    /* called from cache cleaner only  */
+    private boolean
+    removeFromPool(HttpConnection c,
+                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
+        //System.out.println("cacheCleaner removing: " + c);
+        assert Thread.holdsLock(this);
+        CacheKey k = c.cacheKey();
+        List<HttpConnection> l = pool.get(k);
+        if (l == null || l.isEmpty()) {
+            pool.remove(k);
+            return false;
+        }
+        return l.remove(c);
+    }
+
+    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);
+    }
+
+    /**
+     * Purge expired connection and return the number of milliseconds
+     * in which the next connection is scheduled to expire.
+     * If no connections are scheduled to be purged return 0.
+     * @return the delay in milliseconds in which the next connection will
+     *         expire.
+     */
+    long purgeExpiredConnectionsAndReturnNextDeadline() {
+        if (!expiryList.purgeMaybeRequired()) return 0;
+        return purgeExpiredConnectionsAndReturnNextDeadline(Instant.now());
+    }
+
+    // Used for whitebox testing
+    long purgeExpiredConnectionsAndReturnNextDeadline(Instant now) {
+        long nextPurge = 0;
+
+        // We may be in the process of adding new elements
+        // to the expiry list - but those elements will not
+        // have outlast their keep alive timer yet since we're
+        // just adding them.
+        if (!expiryList.purgeMaybeRequired()) return nextPurge;
+
+        List<HttpConnection> closelist;
+        synchronized (this) {
+            closelist = expiryList.purgeUntil(now);
+            for (HttpConnection c : closelist) {
+                if (c instanceof PlainHttpConnection) {
+                    boolean wasPresent = removeFromPool(c, plainPool);
+                    assert wasPresent;
+                } else {
+                    boolean wasPresent = removeFromPool(c, sslPool);
+                    assert wasPresent;
+                }
+            }
+            nextPurge = now.until(
+                    expiryList.nextExpiryDeadline().orElse(now),
+                    ChronoUnit.MILLIS);
+        }
+        closelist.forEach(this::close);
+        return nextPurge;
+    }
+
+    private void close(HttpConnection c) {
+        try {
+            c.close();
+        } catch (Throwable e) {} // ignore
+    }
+
+    void stop() {
+        List<HttpConnection> closelist = Collections.emptyList();
+        try {
+            synchronized (this) {
+                stopped = true;
+                closelist = expiryList.stream()
+                    .map(e -> e.connection)
+                    .collect(Collectors.toList());
+                expiryList.clear();
+                plainPool.clear();
+                sslPool.clear();
+            }
+        } finally {
+            closelist.forEach(this::close);
+        }
+    }
+
+    static final class ExpiryEntry {
+        final HttpConnection connection;
+        final Instant expiry; // absolute time in seconds of expiry time
+        ExpiryEntry(HttpConnection connection, Instant expiry) {
+            this.connection = connection;
+            this.expiry = expiry;
+        }
+    }
+
+    /**
+     * Manages a LinkedList of sorted ExpiryEntry. The entry with the closer
+     * deadline is at the tail of the list, and the entry with the farther
+     * deadline is at the head. In the most common situation, new elements
+     * will need to be added at the head (or close to it), and expired elements
+     * will need to be purged from the tail.
+     */
+    private static final class ExpiryList {
+        private final LinkedList<ExpiryEntry> list = new LinkedList<>();
+        private volatile boolean mayContainEntries;
+
+        // A loosely accurate boolean whose value is computed
+        // at the end of each operation performed on ExpiryList;
+        // Does not require synchronizing on the ConnectionPool.
+        boolean purgeMaybeRequired() {
+            return mayContainEntries;
+        }
+
+        // Returns the next expiry deadline
+        // should only be called while holding a synchronization
+        // lock on the ConnectionPool
+        Optional<Instant> nextExpiryDeadline() {
+            if (list.isEmpty()) return Optional.empty();
+            else return Optional.of(list.getLast().expiry);
+        }
+
+        // should only be called while holding a synchronization
+        // lock on the ConnectionPool
+        void add(HttpConnection conn) {
+            add(conn, Instant.now(), KEEP_ALIVE);
+        }
+
+        // Used by whitebox test.
+        void add(HttpConnection conn, Instant now, long keepAlive) {
+            Instant then = now.truncatedTo(ChronoUnit.SECONDS)
+                    .plus(keepAlive, ChronoUnit.SECONDS);
+
+            // Elements with the farther deadline are at the head of
+            // the list. It's more likely that the new element will
+            // have the farthest deadline, and will need to be inserted
+            // at the head of the list, so we're using an ascending
+            // list iterator to find the right insertion point.
+            ListIterator<ExpiryEntry> li = list.listIterator();
+            while (li.hasNext()) {
+                ExpiryEntry entry = li.next();
+
+                if (then.isAfter(entry.expiry)) {
+                    li.previous();
+                    // insert here
+                    li.add(new ExpiryEntry(conn, then));
+                    mayContainEntries = true;
+                    return;
+                }
+            }
+            // last (or first) element of list (the last element is
+            // the first when the list is empty)
+            list.add(new ExpiryEntry(conn, then));
+            mayContainEntries = true;
+        }
+
+        // should only be called while holding a synchronization
+        // lock on the ConnectionPool
+        void remove(HttpConnection c) {
+            if (c == null || list.isEmpty()) return;
+            ListIterator<ExpiryEntry> li = list.listIterator();
+            while (li.hasNext()) {
+                ExpiryEntry e = li.next();
+                if (e.connection.equals(c)) {
+                    li.remove();
+                    mayContainEntries = !list.isEmpty();
+                    return;
+                }
+            }
+        }
+
+        // should only be called while holding a synchronization
+        // lock on the ConnectionPool.
+        // Purge all elements whose deadline is before now (now included).
+        List<HttpConnection> purgeUntil(Instant now) {
+            if (list.isEmpty()) return Collections.emptyList();
+
+            List<HttpConnection> closelist = new ArrayList<>();
+
+            // elements with the closest deadlines are at the tail
+            // of the queue, so we're going to use a descending iterator
+            // to remove them, and stop when we find the first element
+            // that has not expired yet.
+            Iterator<ExpiryEntry> li = list.descendingIterator();
+            while (li.hasNext()) {
+                ExpiryEntry entry = li.next();
+                // use !isAfter instead of isBefore in order to
+                // remove the entry if its expiry == now
+                if (!entry.expiry.isAfter(now)) {
+                    li.remove();
+                    HttpConnection c = entry.connection;
+                    closelist.add(c);
+                } else break; // the list is sorted
+            }
+            mayContainEntries = !list.isEmpty();
+            return closelist;
+        }
+
+        // should only be called while holding a synchronization
+        // lock on the ConnectionPool
+        java.util.stream.Stream<ExpiryEntry> stream() {
+            return list.stream();
+        }
+
+        // should only be called while holding a synchronization
+        // lock on the ConnectionPool
+        void clear() {
+            list.clear();
+            mayContainEntries = false;
+        }
+    }
+
+    void cleanup(HttpConnection c, Throwable error) {
+        debug.log(Level.DEBUG,
+                  "%s : ConnectionPool.cleanup(%s)",
+                  String.valueOf(c.getConnectionFlow()),
+                  error);
+        synchronized(this) {
+            if (c instanceof PlainHttpConnection) {
+                removeFromPool(c, plainPool);
+            } else {
+                assert c.isSecure();
+                removeFromPool(c, sslPool);
+            }
+            expiryList.remove(c);
+        }
+        c.close();
+    }
+
+    /**
+     * An object that subscribes to the flow while the connection is in
+     * the pool. Anything that comes in will cause the connection to be closed
+     * and removed from the pool.
+     */
+    private final class CleanupTrigger implements
+            FlowTube.TubeSubscriber, FlowTube.TubePublisher,
+            Flow.Subscription {
+
+        private final HttpConnection connection;
+        private volatile boolean done;
+
+        public CleanupTrigger(HttpConnection connection) {
+            this.connection = connection;
+        }
+
+        public boolean isDone() { return done;}
+
+        private void triggerCleanup(Throwable error) {
+            done = true;
+            cleanup(connection, error);
+        }
+
+        @Override public void request(long n) {}
+        @Override public void cancel() {}
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            subscription.request(1);
+        }
+        @Override
+        public void onError(Throwable error) { triggerCleanup(error); }
+        @Override
+        public void onComplete() { triggerCleanup(null); }
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            triggerCleanup(new IOException("Data received while in pool"));
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            subscriber.onSubscribe(this);
+        }
+
+        @Override
+        public String toString() {
+            return "CleanupTrigger(" + connection.getConnectionFlow() + ")";
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/CookieFilter.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.incubator.http.internal.common.Log;
+
+class CookieFilter implements HeaderFilter {
+
+    public CookieFilter() {
+    }
+
+    @Override
+    public void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException {
+        HttpClientImpl client = e.client();
+        Optional<CookieHandler> cookieHandlerOpt = client.cookieHandler();
+        if (cookieHandlerOpt.isPresent()) {
+            CookieHandler cookieHandler = cookieHandlerOpt.get();
+            Map<String,List<String>> userheaders = r.getUserHeaders().map();
+            Map<String,List<String>> cookies = cookieHandler.get(r.uri(), userheaders);
+
+            // add the returned cookies
+            HttpHeadersImpl systemHeaders = r.getSystemHeaders();
+            if (cookies.isEmpty()) {
+                Log.logTrace("Request: no cookie to add for {0}",
+                             r.uri());
+            } else {
+                Log.logTrace("Request: adding cookies for {0}",
+                             r.uri());
+            }
+            for (String hdrname : cookies.keySet()) {
+                List<String> vals = cookies.get(hdrname);
+                for (String val : vals) {
+                    systemHeaders.addHeader(hdrname, val);
+                }
+            }
+        } else {
+            Log.logTrace("Request: No cookie manager found for {0}",
+                         r.uri());
+        }
+    }
+
+    @Override
+    public HttpRequestImpl response(Response r) throws IOException {
+        HttpHeaders hdrs = r.headers();
+        HttpRequestImpl request = r.request();
+        Exchange<?> e = r.exchange;
+        Log.logTrace("Response: processing cookies for {0}", request.uri());
+        Optional<CookieHandler> cookieHandlerOpt = e.client().cookieHandler();
+        if (cookieHandlerOpt.isPresent()) {
+            CookieHandler cookieHandler = cookieHandlerOpt.get();
+            Log.logTrace("Response: parsing cookies from {0}", hdrs.map());
+            cookieHandler.put(request.uri(), hdrs.map());
+        } else {
+            Log.logTrace("Response: No cookie manager found for {0}",
+                         request.uri());
+        }
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Exchange.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,574 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLPermission;
+import java.security.AccessControlContext;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpTimeoutException;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.Utils;
+import jdk.incubator.http.internal.common.Log;
+
+import static jdk.incubator.http.internal.common.Utils.permissionForProxy;
+
+/**
+ * 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)
+ *
+ */
+final class Exchange<T> {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    final HttpRequestImpl request;
+    final HttpClientImpl client;
+    volatile ExchangeImpl<T> exchImpl;
+    volatile CompletableFuture<? extends ExchangeImpl<T>> exchangeCF;
+    volatile CompletableFuture<Void> bodyIgnored;
+
+    // used to record possible cancellation raised before the exchImpl
+    // has been established.
+    private volatile IOException failed;
+    final AccessControlContext acc;
+    final MultiExchange<T> multi;
+    final Executor parentExecutor;
+    boolean upgrading; // to HTTP/2
+    final PushGroup<T> pushGroup;
+    final String dbgTag;
+
+    Exchange(HttpRequestImpl request, MultiExchange<T> multi) {
+        this.request = request;
+        this.upgrading = false;
+        this.client = multi.client();
+        this.multi = multi;
+        this.acc = multi.acc;
+        this.parentExecutor = multi.executor;
+        this.pushGroup = multi.pushGroup;
+        this.dbgTag = "Exchange";
+    }
+
+    /* If different AccessControlContext to be used  */
+    Exchange(HttpRequestImpl request,
+             MultiExchange<T> multi,
+             AccessControlContext acc)
+    {
+        this.request = request;
+        this.acc = acc;
+        this.upgrading = false;
+        this.client = multi.client();
+        this.multi = multi;
+        this.parentExecutor = multi.executor;
+        this.pushGroup = multi.pushGroup;
+        this.dbgTag = "Exchange";
+    }
+
+    PushGroup<T> getPushGroup() {
+        return pushGroup;
+    }
+
+    Executor executor() {
+        return parentExecutor;
+    }
+
+    public HttpRequestImpl request() {
+        return request;
+    }
+
+    HttpClientImpl client() {
+        return client;
+    }
+
+
+    public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) {
+        // If we received a 407 while establishing the exchange
+        // there will be no body to read: bodyIgnored will be true,
+        // and exchImpl will be null (if we were trying to establish
+        // an HTTP/2 tunnel through an HTTP/1.1 proxy)
+        if (bodyIgnored != null) return MinimalFuture.completedFuture(null);
+
+        // The connection will not be returned to the pool in the case of WebSocket
+        return exchImpl.readBodyAsync(handler, !request.isWebSocket(), parentExecutor)
+                .whenComplete((r,t) -> exchImpl.completed());
+    }
+
+    /**
+     * Called after a redirect or similar kind of retry where a body might
+     * be sent but we don't want it. Should send a RESET in h2. For http/1.1
+     * we can consume small quantity of data, or close the connection in
+     * other cases.
+     */
+    public CompletableFuture<Void> ignoreBody() {
+        if (bodyIgnored != null) return bodyIgnored;
+        return exchImpl.ignoreBody();
+    }
+
+    /**
+     * Called when a new exchange is created to replace this exchange.
+     * At this point it is guaranteed that readBody/readBodyAsync will
+     * not be called.
+     */
+    public void released() {
+        ExchangeImpl<?> impl = exchImpl;
+        if (impl != null) impl.released();
+        // Don't set exchImpl to null here. We need to keep
+        // it alive until it's replaced by a Stream in wrapForUpgrade.
+        // Setting it to null here might get it GC'ed too early, because
+        // the Http1Response is now only weakly referenced by the Selector.
+    }
+
+    public void cancel() {
+        // cancel can be called concurrently before or at the same time
+        // that the exchange impl is being established.
+        // In that case we won't be able to propagate the cancellation
+        // right away
+        if (exchImpl != null) {
+            exchImpl.cancel();
+        } else {
+            // no impl - can't cancel impl yet.
+            // call cancel(IOException) instead which takes care
+            // of race conditions between impl/cancel.
+            cancel(new IOException("Request cancelled"));
+        }
+    }
+
+    public void cancel(IOException cause) {
+        // If the impl is non null, propagate the exception right away.
+        // Otherwise record it so that it can be propagated once the
+        // exchange impl has been established.
+        ExchangeImpl<?> impl = exchImpl;
+        if (impl != null) {
+            // propagate the exception to the impl
+            debug.log(Level.DEBUG, "Cancelling exchImpl: %s", exchImpl);
+            impl.cancel(cause);
+        } else {
+            // no impl yet. record the exception
+            failed = cause;
+            // now call checkCancelled to recheck the impl.
+            // if the failed state is set and the impl is not null, reset
+            // the failed state and propagate the exception to the impl.
+            checkCancelled();
+        }
+    }
+
+    // This method will raise an exception if one was reported and if
+    // it is possible to do so. If the exception can be raised, then
+    // the failed state will be reset. Otherwise, the failed state
+    // will persist until the exception can be raised and the failed state
+    // can be cleared.
+    // Takes care of possible race conditions.
+    private void checkCancelled() {
+        ExchangeImpl<?> impl = null;
+        IOException cause = null;
+        CompletableFuture<? extends ExchangeImpl<T>> cf = null;
+        if (failed != null) {
+            synchronized(this) {
+                cause = failed;
+                impl = exchImpl;
+                cf = exchangeCF;
+            }
+        }
+        if (cause == null) return;
+        if (impl != null) {
+            // The exception is raised by propagating it to the impl.
+            debug.log(Level.DEBUG, "Cancelling exchImpl: %s", impl);
+            impl.cancel(cause);
+            failed = null;
+        } else {
+            Log.logTrace("Exchange: request [{0}/timeout={1}ms] no impl is set."
+                         + "\n\tCan''t cancel yet with {2}",
+                         request.uri(),
+                         request.timeout().isPresent() ?
+                         // calling duration.toMillis() can throw an exception.
+                         // this is just debugging, we don't care if it overflows.
+                         (request.timeout().get().getSeconds() * 1000
+                          + request.timeout().get().getNano() / 1000000) : -1,
+                         cause);
+            if (cf != null) cf.completeExceptionally(cause);
+        }
+    }
+
+    public void h2Upgrade() {
+        upgrading = true;
+        request.setH2Upgrade(client.client2());
+    }
+
+    synchronized IOException getCancelCause() {
+        return failed;
+    }
+
+    // get/set the exchange impl, solving race condition issues with
+    // potential concurrent calls to cancel() or cancel(IOException)
+    private CompletableFuture<? extends ExchangeImpl<T>>
+    establishExchange(HttpConnection connection) {
+        if (debug.isLoggable(Level.DEBUG)) {
+            debug.log(Level.DEBUG,
+                    "establishing exchange for %s,%n\t proxy=%s",
+                    request,
+                    request.proxy());
+        }
+        // check if we have been cancelled first.
+        Throwable t = getCancelCause();
+        checkCancelled();
+        if (t != null) {
+            return MinimalFuture.failedFuture(t);
+        }
+
+        CompletableFuture<? extends ExchangeImpl<T>> cf, res;
+        cf = ExchangeImpl.get(this, connection);
+        // We should probably use a VarHandle to get/set exchangeCF
+        // instead - as we need CAS semantics.
+        synchronized (this) { exchangeCF = cf; };
+        res = cf.whenComplete((r,x) -> {
+            synchronized(Exchange.this) {
+                if (exchangeCF == cf) exchangeCF = null;
+            }
+        });
+        checkCancelled();
+        return res.thenCompose((eimpl) -> {
+                    // recheck for cancelled, in case of race conditions
+                    exchImpl = eimpl;
+                    IOException tt = getCancelCause();
+                    checkCancelled();
+                    if (tt != null) {
+                        return MinimalFuture.failedFuture(tt);
+                    } else {
+                        // Now we're good to go. Because exchImpl is no longer
+                        // null cancel() will be able to propagate directly to
+                        // the impl after this point ( if needed ).
+                        return MinimalFuture.completedFuture(eimpl);
+                    } });
+    }
+
+    // Completed HttpResponse will be null if response succeeded
+    // will be a non null responseAsync if expect continue returns an error
+
+    public CompletableFuture<Response> responseAsync() {
+        return responseAsyncImpl(null);
+    }
+
+    CompletableFuture<Response> responseAsyncImpl(HttpConnection connection) {
+        SecurityException e = checkPermissions();
+        if (e != null) {
+            return MinimalFuture.failedFuture(e);
+        } else {
+            return responseAsyncImpl0(connection);
+        }
+    }
+
+    // check whether the headersSentCF was completed exceptionally with
+    // ProxyAuthorizationRequired. If so the Response embedded in the
+    // exception is returned. Otherwise we proceed.
+    private CompletableFuture<Response> checkFor407(ExchangeImpl<T> ex, Throwable t,
+                                                    Function<ExchangeImpl<T>,CompletableFuture<Response>> andThen) {
+        t = Utils.getCompletionCause(t);
+        if (t instanceof ProxyAuthenticationRequired) {
+            bodyIgnored = MinimalFuture.completedFuture(null);
+            Response proxyResponse = ((ProxyAuthenticationRequired)t).proxyResponse;
+            Response syntheticResponse = new Response(request, this,
+                    proxyResponse.headers, proxyResponse.statusCode,
+                    proxyResponse.version, true);
+            return MinimalFuture.completedFuture(syntheticResponse);
+        } else if (t != null) {
+            return MinimalFuture.failedFuture(t);
+        } else {
+            return andThen.apply(ex);
+        }
+    }
+
+    // After sending the request headers, if no ProxyAuthorizationRequired
+    // was raised and the expectContinue flag is on, we need to wait
+    // for the 100-Continue response
+    private CompletableFuture<Response> expectContinue(ExchangeImpl<T> ex) {
+        assert request.expectContinue();
+        return ex.getResponseAsync(parentExecutor)
+                .thenCompose((Response r1) -> {
+            Log.logResponse(r1::toString);
+            int rcode = r1.statusCode();
+            if (rcode == 100) {
+                Log.logTrace("Received 100-Continue: sending body");
+                CompletableFuture<Response> cf =
+                        exchImpl.sendBodyAsync()
+                                .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
+                cf = wrapForUpgrade(cf);
+                cf = wrapForLog(cf);
+                return cf;
+            } else {
+                Log.logTrace("Expectation failed: Received {0}",
+                        rcode);
+                if (upgrading && rcode == 101) {
+                    IOException failed = new IOException(
+                            "Unable to handle 101 while waiting for 100");
+                    return MinimalFuture.failedFuture(failed);
+                }
+                return exchImpl.readBodyAsync(this::ignoreBody, false, parentExecutor)
+                        .thenApply(v ->  r1);
+            }
+        });
+    }
+
+    // After sending the request headers, if no ProxyAuthorizationRequired
+    // was raised and the expectContinue flag is off, we can immediately
+    // send the request body and proceed.
+    private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) {
+        assert !request.expectContinue();
+        CompletableFuture<Response> cf = ex.sendBodyAsync()
+                .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
+        cf = wrapForUpgrade(cf);
+        cf = wrapForLog(cf);
+        return cf;
+    }
+
+    CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) {
+        Function<ExchangeImpl<T>, CompletableFuture<Response>> after407Check;
+        bodyIgnored = null;
+        if (request.expectContinue()) {
+            request.addSystemHeader("Expect", "100-Continue");
+            Log.logTrace("Sending Expect: 100-Continue");
+            // wait for 100-Continue before sending body
+            after407Check = this::expectContinue;
+        } else {
+            // send request body and proceed.
+            after407Check = this::sendRequestBody;
+        }
+        // The ProxyAuthorizationRequired can be triggered either by
+        // establishExchange (case of HTTP/2 SSL tunelling through HTTP/1.1 proxy
+        // or by sendHeaderAsync (case of HTTP/1.1 SSL tunelling through HTTP/1.1 proxy
+        // Therefore we handle it with a call to this checkFor407(...) after these
+        // two places.
+        Function<ExchangeImpl<T>, CompletableFuture<Response>> afterExch407Check =
+                (ex) -> ex.sendHeadersAsync()
+                        .handle((r,t) -> this.checkFor407(r, t, after407Check))
+                        .thenCompose(Function.identity());
+        return establishExchange(connection)
+                .handle((r,t) -> this.checkFor407(r,t, afterExch407Check))
+                .thenCompose(Function.identity());
+    }
+
+    private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> cf) {
+        if (upgrading) {
+            return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
+        }
+        return cf;
+    }
+
+    private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
+        if (Log.requests()) {
+            return cf.thenApply(response -> {
+                Log.logResponse(response::toString);
+                return response;
+            });
+        }
+        return cf;
+    }
+
+    HttpResponse.BodySubscriber<T> ignoreBody(int status, HttpHeaders hdrs) {
+        return HttpResponse.BodySubscriber.discard((T)null);
+    }
+
+    // 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<Response>
+    checkForUpgradeAsync(Response resp,
+                         ExchangeImpl<T> ex) {
+
+        int rcode = resp.statusCode();
+        if (upgrading && (rcode == 101)) {
+            Http1Exchange<T> e = (Http1Exchange<T>)ex;
+            // check for 101 switching protocols
+            // 101 responses are not supposed to contain a body.
+            //    => should we fail if there is one?
+            debug.log(Level.DEBUG, "Upgrading async %s", e.connection());
+            return e.readBodyAsync(this::ignoreBody, false, parentExecutor)
+                .thenCompose((T v) -> {// v is null
+                    debug.log(Level.DEBUG, "Ignored body");
+                    // we pass e::getBuffer to allow the ByteBuffers to accumulate
+                    // while we build the Http2Connection
+                    return Http2Connection.createAsync(e.connection(),
+                                                 client.client2(),
+                                                 this, e::drainLeftOverBytes)
+                        .thenCompose((Http2Connection c) -> {
+                            boolean cached = c.offerConnection();
+                            Stream<T> s = c.getStream(1);
+
+                            if (s == null) {
+                                // s can be null if an exception occurred
+                                // asynchronously while sending the preface.
+                                Throwable t = c.getRecordedCause();
+                                IOException ioe;
+                                if (t != null) {
+                                    if (!cached)
+                                        c.close();
+                                    ioe = new IOException("Can't get stream 1: " + t, t);
+                                } else {
+                                    ioe = new IOException("Can't get stream 1");
+                                }
+                                return MinimalFuture.failedFuture(ioe);
+                            }
+                            exchImpl.released();
+                            Throwable t;
+                            // There's a race condition window where an external
+                            // thread (SelectorManager) might complete the
+                            // exchange in timeout at the same time where we're
+                            // trying to switch the exchange impl.
+                            // 'failed' will be reset to null after
+                            // exchImpl.cancel() has completed, so either we
+                            // will observe failed != null here, or we will
+                            // observe e.getCancelCause() != null, or the
+                            // timeout exception will be routed to 's'.
+                            // Either way, we need to relay it to s.
+                            synchronized (this) {
+                                exchImpl = s;
+                                t = failed;
+                            }
+                            // Check whether the HTTP/1.1 was cancelled.
+                            if (t == null) t = e.getCancelCause();
+                            // if HTTP/1.1 exchange was timed out, don't
+                            // try to go further.
+                            if (t instanceof HttpTimeoutException) {
+                                 s.cancelImpl(t);
+                                 return MinimalFuture.failedFuture(t);
+                            }
+                            debug.log(Level.DEBUG, "Getting response async %s", s);
+                            return s.getResponseAsync(null);
+                        });}
+                );
+        }
+        return MinimalFuture.completedFuture(resp);
+    }
+
+    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;
+    }
+
+    /**
+     * Returns the security permission required for the given details.
+     * If method is CONNECT, then uri must be of form "scheme://host:port"
+     */
+    private static URLPermission permissionForServer(URI uri,
+                                                     String method,
+                                                     Map<String, List<String>> headers) {
+        if (method.equals("CONNECT")) {
+            return new URLPermission(uri.toString(), "CONNECT");
+        } else {
+            return Utils.permissionForServer(uri, method, headers.keySet().stream());
+        }
+    }
+
+    /**
+     * Performs the necessary security permission checks required to retrieve
+     * the response. Returns a security exception representing the denied
+     * permission, or null if all checks pass or there is no security manager.
+     */
+    private SecurityException checkPermissions() {
+        String method = request.method();
+        SecurityManager sm = System.getSecurityManager();
+        if (sm == null || method.equals("CONNECT")) {
+            // tunneling will have a null acc, which is fine. The proxy
+            // permission check will have already been preformed.
+            return null;
+        }
+
+        HttpHeaders userHeaders = request.getUserHeaders();
+        URI u = getURIForSecurityCheck();
+        URLPermission p = permissionForServer(u, method, userHeaders.map());
+
+        try {
+            assert acc != null;
+            sm.checkPermission(p, acc);
+        } catch (SecurityException e) {
+            return e;
+        }
+        ProxySelector ps = client.proxySelector();
+        if (ps != null) {
+            if (!method.equals("CONNECT")) {
+                // a non-tunneling HTTP proxy. Need to check access
+                URLPermission proxyPerm = permissionForProxy(request.proxy());
+                if (proxyPerm != null) {
+                    try {
+                        sm.checkPermission(proxyPerm, acc);
+                    } catch (SecurityException e) {
+                        return e;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    HttpClient.Version version() {
+        return multi.version();
+    }
+
+    String dbgString() {
+        return dbgTag;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ExchangeImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * 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<T> {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    private static final System.Logger DEBUG_LOGGER =
+            Utils.getDebugLogger("ExchangeImpl"::toString, DEBUG);
+
+    final Exchange<T> exchange;
+
+    ExchangeImpl(Exchange<T> e) {
+        // e == null means a http/2 pushed stream
+        this.exchange = e;
+    }
+
+    final Exchange<T> getExchange() {
+        return exchange;
+    }
+
+
+    /**
+     * Returns the {@link HttpConnection} instance to which this exchange is
+     * assigned.
+     */
+    abstract HttpConnection connection();
+
+    /**
+     * Initiates a new exchange and assigns it to a connection if one exists
+     * already. connection usually null.
+     */
+    static <U> CompletableFuture<? extends ExchangeImpl<U>>
+    get(Exchange<U> exchange, HttpConnection connection)
+    {
+        if (exchange.version() == HTTP_1_1) {
+            DEBUG_LOGGER.log(Level.DEBUG, "get: HTTP/1.1: new Http1Exchange");
+            return createHttp1Exchange(exchange, connection);
+        } else {
+            Http2ClientImpl c2 = exchange.client().client2(); // TODO: improve
+            HttpRequestImpl request = exchange.request();
+            CompletableFuture<Http2Connection> c2f = c2.getConnectionFor(request);
+            DEBUG_LOGGER.log(Level.DEBUG, "get: Trying to get HTTP/2 connection");
+            return c2f.handle((h2c, t) -> createExchangeImpl(h2c, t, exchange, connection))
+                    .thenCompose(Function.identity());
+        }
+    }
+
+    private static <U> CompletableFuture<? extends ExchangeImpl<U>>
+    createExchangeImpl(Http2Connection c,
+                       Throwable t,
+                       Exchange<U> exchange,
+                       HttpConnection connection)
+    {
+        DEBUG_LOGGER.log(Level.DEBUG, "handling HTTP/2 connection creation result");
+        boolean secure = exchange.request().secure();
+        if (t != null) {
+            DEBUG_LOGGER.log(Level.DEBUG,
+                             "handling HTTP/2 connection creation failed: %s",
+                             (Object)t);
+            t = Utils.getCompletionCause(t);
+            if (t instanceof Http2Connection.ALPNException) {
+                Http2Connection.ALPNException ee = (Http2Connection.ALPNException)t;
+                AbstractAsyncSSLConnection as = ee.getConnection();
+                DEBUG_LOGGER.log(Level.DEBUG, "downgrading to HTTP/1.1 with: %s", as);
+                CompletableFuture<? extends ExchangeImpl<U>> ex =
+                        createHttp1Exchange(exchange, as);
+                return ex;
+            } else {
+                DEBUG_LOGGER.log(Level.DEBUG, "HTTP/2 connection creation failed "
+                                  + "with unexpected exception: %s", (Object)t);
+                return MinimalFuture.failedFuture(t);
+            }
+        }
+        if (secure && c== null) {
+            DEBUG_LOGGER.log(Level.DEBUG, "downgrading to HTTP/1.1 ");
+            CompletableFuture<? extends ExchangeImpl<U>> ex =
+                    createHttp1Exchange(exchange, null);
+            return ex;
+        }
+        if (c == null) {
+            // no existing connection. Send request with HTTP 1 and then
+            // upgrade if successful
+            DEBUG_LOGGER.log(Level.DEBUG, "new Http1Exchange, try to upgrade");
+            return createHttp1Exchange(exchange, connection)
+                    .thenApply((e) -> {
+                        exchange.h2Upgrade();
+                        return e;
+                    });
+        } else {
+            DEBUG_LOGGER.log(Level.DEBUG, "creating HTTP/2 streams");
+            Stream<U> s = c.createStream(exchange);
+            CompletableFuture<? extends ExchangeImpl<U>> ex = MinimalFuture.completedFuture(s);
+            return ex;
+        }
+    }
+
+    private static <T> CompletableFuture<Http1Exchange<T>>
+    createHttp1Exchange(Exchange<T> ex, HttpConnection as)
+    {
+        try {
+            return MinimalFuture.completedFuture(new Http1Exchange<>(ex, as));
+        } catch (Throwable e) {
+            return MinimalFuture.failedFuture(e);
+        }
+    }
+
+    /* The following methods have separate HTTP/1.1 and HTTP/2 implementations */
+
+    abstract CompletableFuture<ExchangeImpl<T>> sendHeadersAsync();
+
+    /** Sends a request body, after request headers have been sent. */
+    abstract CompletableFuture<ExchangeImpl<T>> sendBodyAsync();
+
+    abstract CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler,
+                                                boolean returnConnectionToPool,
+                                                Executor executor);
+
+    /**
+     * Ignore/consume the body.
+     */
+    abstract CompletableFuture<Void> ignoreBody();
+
+    /** Gets the response headers. Completes before body is read. */
+    abstract CompletableFuture<Response> getResponseAsync(Executor executor);
+
+
+    /** Cancels a request.  Not currently exposed through API. */
+    abstract void cancel();
+
+    /**
+     * Cancels a request with a cause.  Not currently exposed through API.
+     */
+    abstract void cancel(IOException cause);
+
+    /**
+     * Called when the exchange is released, so that cleanup actions may be
+     * performed - such as deregistering callbacks.
+     * Typically released is called during upgrade, when an HTTP/2 stream
+     * takes over from an Http1Exchange, or when a new exchange is created
+     * during a multi exchange before the final response body was received.
+     */
+    abstract void released();
+
+    /**
+     * Called when the exchange is completed, so that cleanup actions may be
+     * performed - such as deregistering callbacks.
+     * Typically, completed is called at the end of the exchange, when the
+     * final response body has been received (or an error has caused the
+     * completion of the exchange).
+     */
+    abstract void completed();
+
+    /**
+     * Returns true if this exchange was canceled.
+     * @return true if this exchange was canceled.
+     */
+    abstract boolean isCanceled();
+
+    /**
+     * Returns the cause for which this exchange was canceled, if available.
+     * @return the cause for which this exchange was canceled, if available.
+     */
+    abstract Throwable getCancelCause();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/FilterFactory.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+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 {
+                // Requires a public no arg constructor.
+                HeaderFilter headerFilter = clazz.getConstructor().newInstance();
+                l.add(headerFilter);
+            } catch (ReflectiveOperationException e) {
+                throw new InternalError(e);
+            }
+        }
+        return l;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HeaderFilter.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+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, MultiExchange<?> e) 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(Response r) throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HeaderParser.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1AsyncReceiver.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,651 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import jdk.incubator.http.internal.common.Demand;
+import jdk.incubator.http.internal.common.FlowTube.TubeSubscriber;
+import jdk.incubator.http.internal.common.SequentialScheduler;
+import jdk.incubator.http.internal.common.ConnectionExpiredException;
+import jdk.incubator.http.internal.common.Utils;
+
+
+/**
+ * A helper class that will queue up incoming data until the receiving
+ * side is ready to handle it.
+ */
+class Http1AsyncReceiver {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    /**
+     * A delegate that can asynchronously receive data from an upstream flow,
+     * parse, it, then possibly transform it and either store it (response
+     * headers) or possibly pass it to a downstream subscriber (response body).
+     * Usually, there will be one Http1AsyncDelegate in charge of receiving
+     * and parsing headers, and another one in charge of receiving, parsing,
+     * and forwarding body. Each will sequentially subscribe with the
+     * Http1AsyncReceiver in turn. There may be additional delegates which
+     * subscribe to the Http1AsyncReceiver, mainly for the purpose of handling
+     * errors while the connection is busy transmitting the request body and the
+     * Http1Exchange::readBody method hasn't been called yet, and response
+     * delegates haven't subscribed yet.
+     */
+    static interface Http1AsyncDelegate {
+        /**
+         * Receives and handles a byte buffer reference.
+         * @param ref A byte buffer reference coming from upstream.
+         * @return false, if the byte buffer reference should be kept in the queue.
+         *         Usually, this means that either the byte buffer reference
+         *         was handled and parsing is finished, or that the receiver
+         *         didn't handle the byte reference at all.
+         *         There may or may not be any remaining data in the
+         *         byte buffer, and the byte buffer reference must not have
+         *         been cleared.
+         *         true, if the byte buffer reference was fully read and
+         *         more data can be received.
+         */
+        public boolean tryAsyncReceive(ByteBuffer ref);
+
+        /**
+         * Called when an exception is raised.
+         * @param ex The raised Throwable.
+         */
+        public void onReadError(Throwable ex);
+
+        /**
+         * Must be called before any other method on the delegate.
+         * The subscription can be either used directly by the delegate
+         * to request more data (e.g. if the delegate is a header parser),
+         * or can be forwarded to a downstream subscriber (if the delegate
+         * is a body parser that wraps a response BodySubscriber).
+         * In all cases, it is the responsibility of the delegate to ensure
+         * that request(n) and demand.tryDecrement() are called appropriately.
+         * No data will be sent to {@code tryAsyncReceive} unless
+         * the subscription has some demand.
+         *
+         * @param s A subscription that allows the delegate to control the
+         *          data flow.
+         */
+        public void onSubscribe(AbstractSubscription s);
+
+        /**
+         * Returns the subscription that was passed to {@code onSubscribe}
+         * @return the subscription that was passed to {@code onSubscribe}..
+         */
+        public AbstractSubscription subscription();
+
+    }
+
+    /**
+     * A simple subclass of AbstractSubscription that ensures the
+     * SequentialScheduler will be run when request() is called and demand
+     * becomes positive again.
+     */
+    private static final class Http1AsyncDelegateSubscription
+            extends AbstractSubscription
+    {
+        private final Runnable onCancel;
+        private final SequentialScheduler scheduler;
+        Http1AsyncDelegateSubscription(SequentialScheduler scheduler,
+                                       Runnable onCancel) {
+            this.scheduler = scheduler;
+            this.onCancel = onCancel;
+        }
+        @Override
+        public void request(long n) {
+            final Demand demand = demand();
+            if (demand.increase(n)) {
+                scheduler.runOrSchedule();
+            }
+        }
+        @Override
+        public void cancel() { onCancel.run();}
+    }
+
+    private final ConcurrentLinkedDeque<ByteBuffer> queue
+            = new ConcurrentLinkedDeque<>();
+    private final SequentialScheduler scheduler =
+            SequentialScheduler.synchronizedScheduler(this::flush);
+    private final Executor executor;
+    private final Http1TubeSubscriber subscriber = new Http1TubeSubscriber();
+    private final AtomicReference<Http1AsyncDelegate> pendingDelegateRef;
+    private final AtomicLong received = new AtomicLong();
+    final AtomicBoolean canRequestMore = new AtomicBoolean();
+
+    private volatile Throwable error;
+    private volatile Http1AsyncDelegate delegate;
+    // This reference is only used to prevent early GC of the exchange.
+    private volatile Http1Exchange<?>  owner;
+    // Only used for checking whether we run on the selector manager thread.
+    private final HttpClientImpl client;
+    private boolean retry;
+
+    public Http1AsyncReceiver(Executor executor, Http1Exchange<?> owner) {
+        this.pendingDelegateRef = new AtomicReference<>();
+        this.executor = executor;
+        this.owner = owner;
+        this.client = owner.client;
+    }
+
+    // This is the main loop called by the SequentialScheduler.
+    // It attempts to empty the queue until the scheduler is stopped,
+    // or the delegate is unregistered, or the delegate is unable to
+    // process the data (because it's not ready or already done), which
+    // it signals by returning 'true';
+    private void flush() {
+        ByteBuffer buf;
+        try {
+            assert !client.isSelectorThread() :
+                    "Http1AsyncReceiver::flush should not run in the selector: "
+                    + Thread.currentThread().getName();
+
+            // First check whether we have a pending delegate that has
+            // just subscribed, and if so, create a Subscription for it
+            // and call onSubscribe.
+            handlePendingDelegate();
+
+            // Then start emptying the queue, if possible.
+            while ((buf = queue.peek()) != null) {
+                Http1AsyncDelegate delegate = this.delegate;
+                debug.log(Level.DEBUG, "Got %s bytes for delegate %s",
+                                       buf.remaining(), delegate);
+                if (!hasDemand(delegate)) {
+                    // The scheduler will be invoked again later when the demand
+                    // becomes positive.
+                    return;
+                }
+
+                assert delegate != null;
+                debug.log(Level.DEBUG, "Forwarding %s bytes to delegate %s",
+                          buf.remaining(), delegate);
+                // The delegate has demand: feed it the next buffer.
+                if (!delegate.tryAsyncReceive(buf)) {
+                    final long remaining = buf.remaining();
+                    debug.log(Level.DEBUG, () -> {
+                        // If the scheduler is stopped, the queue may already
+                        // be empty and the reference may already be released.
+                        String remstr = scheduler.isStopped() ? "" :
+                                " remaining in ref: "
+                                + remaining;
+                        remstr =  remstr
+                                + " total remaining: " + remaining();
+                        return "Delegate done: " + remaining;
+                    });
+                    canRequestMore.set(false);
+                    // The last buffer parsed may have remaining unparsed bytes.
+                    // Don't take it out of the queue.
+                    return; // done.
+                }
+
+                // removed parsed buffer from queue, and continue with next
+                // if available
+                ByteBuffer parsed = queue.remove();
+                canRequestMore.set(queue.isEmpty());
+                assert parsed == buf;
+            }
+
+            // queue is empty: let's see if we should request more
+            checkRequestMore();
+
+        } catch (Throwable t) {
+            Throwable x = error;
+            if (x == null) error = t; // will be handled in the finally block
+            debug.log(Level.DEBUG, "Unexpected error caught in flush()", t);
+        } finally {
+            // Handles any pending error.
+            // The most recently subscribed delegate will get the error.
+            checkForErrors();
+        }
+    }
+
+    /**
+     * Must be called from within the scheduler main loop.
+     * Handles any pending errors by calling delegate.onReadError().
+     * If the error can be forwarded to the delegate, stops the scheduler.
+     */
+    private void checkForErrors() {
+        // Handles any pending error.
+        // The most recently subscribed delegate will get the error.
+        // If the delegate is null, the error will be handled by the next
+        // delegate that subscribes.
+        // If the queue is not empty, wait until it it is empty before
+        // handling the error.
+        Http1AsyncDelegate delegate = pendingDelegateRef.get();
+        if (delegate == null) delegate = this.delegate;
+        Throwable x = error;
+        if (delegate != null && x != null && queue.isEmpty()) {
+            // forward error only after emptying the queue.
+            final Object captured = delegate;
+            debug.log(Level.DEBUG, () -> "flushing " + x
+                    + "\n\t delegate: " + captured
+                    + "\t\t queue.isEmpty: " + queue.isEmpty());
+            scheduler.stop();
+            delegate.onReadError(x);
+        }
+    }
+
+    /**
+     * Must be called from within the scheduler main loop.
+     * Figure out whether more data should be requested from the
+     * Http1TubeSubscriber.
+     */
+    private void checkRequestMore() {
+        Http1AsyncDelegate delegate = this.delegate;
+        boolean more = this.canRequestMore.get();
+        boolean hasDemand = hasDemand(delegate);
+        debug.log(Level.DEBUG, () -> "checkRequestMore: "
+                  + "canRequestMore=" + more + ", hasDemand=" + hasDemand
+                  + (delegate == null ? ", delegate=null" : ""));
+        if (hasDemand) {
+            subscriber.requestMore();
+        }
+    }
+
+    /**
+     * Must be called from within the scheduler main loop.
+     * Return true if the delegate is not null and has some demand.
+     * @param delegate The Http1AsyncDelegate delegate
+     * @return true if the delegate is not null and has some demand
+     */
+    private boolean hasDemand(Http1AsyncDelegate delegate) {
+        if (delegate == null) return false;
+        AbstractSubscription subscription = delegate.subscription();
+        long demand = subscription.demand().get();
+        debug.log(Level.DEBUG, "downstream subscription demand is %s", demand);
+        return demand > 0;
+    }
+
+    /**
+     * Must be called from within the scheduler main loop.
+     * Handles pending delegate subscription.
+     * Return true if there was some pending delegate subscription and a new
+     * delegate was subscribed, false otherwise.
+     *
+     * @return true if there was some pending delegate subscription and a new
+     *         delegate was subscribed, false otherwise.
+     */
+    private boolean handlePendingDelegate() {
+        Http1AsyncDelegate pending = pendingDelegateRef.get();
+        if (pending != null && pendingDelegateRef.compareAndSet(pending, null)) {
+            Http1AsyncDelegate delegate = this.delegate;
+            if (delegate != null) unsubscribe(delegate);
+            Runnable cancel = () -> {
+                debug.log(Level.DEBUG, "Downstream subscription cancelled by %s", pending);
+                // The connection should be closed, as some data may
+                // be left over in the stream.
+                try {
+                    setRetryOnError(false);
+                    onReadError(new IOException("subscription cancelled"));
+                    unsubscribe(pending);
+                } finally {
+                    Http1Exchange<?> exchg = owner;
+                    stop();
+                    if (exchg != null) exchg.connection().close();
+                }
+            };
+            // The subscription created by a delegate is only loosely
+            // coupled with the upstream subscription. This is partly because
+            // the header/body parser work with a flow of ByteBuffer, whereas
+            // we have a flow List<ByteBuffer> upstream.
+            Http1AsyncDelegateSubscription subscription =
+                    new Http1AsyncDelegateSubscription(scheduler, cancel);
+            pending.onSubscribe(subscription);
+            this.delegate = delegate = pending;
+            final Object captured = delegate;
+            debug.log(Level.DEBUG, () -> "delegate is now " + captured
+                  + ", demand=" + subscription.demand().get()
+                  + ", canRequestMore=" + canRequestMore.get()
+                  + ", queue.isEmpty=" + queue.isEmpty());
+            return true;
+        }
+        return false;
+    }
+
+    synchronized void setRetryOnError(boolean retry) {
+        this.retry = retry;
+    }
+
+    void clear() {
+        debug.log(Level.DEBUG, "cleared");
+        this.pendingDelegateRef.set(null);
+        this.delegate = null;
+        this.owner = null;
+    }
+
+    void subscribe(Http1AsyncDelegate delegate) {
+        synchronized(this) {
+            pendingDelegateRef.set(delegate);
+        }
+        if (queue.isEmpty()) {
+            canRequestMore.set(true);
+        }
+        debug.log(Level.DEBUG, () ->
+                "Subscribed pending " + delegate + " queue.isEmpty: "
+                + queue.isEmpty());
+        // Everything may have been received already. Make sure
+        // we parse it.
+        if (client.isSelectorThread()) {
+            scheduler.runOrSchedule(executor);
+        } else {
+            scheduler.runOrSchedule();
+        }
+    }
+
+    // Used for debugging only!
+    long remaining() {
+        return Utils.remaining(queue.toArray(Utils.EMPTY_BB_ARRAY));
+    }
+
+    void unsubscribe(Http1AsyncDelegate delegate) {
+        synchronized(this) {
+            if (this.delegate == delegate) {
+                debug.log(Level.DEBUG, "Unsubscribed %s", delegate);
+                this.delegate = null;
+            }
+        }
+    }
+
+    // Callback: Consumer of ByteBuffer
+    private void asyncReceive(ByteBuffer buf) {
+        debug.log(Level.DEBUG, "Putting %s bytes into the queue", buf.remaining());
+        received.addAndGet(buf.remaining());
+        queue.offer(buf);
+
+        // This callback is called from within the selector thread.
+        // Use an executor here to avoid doing the heavy lifting in the
+        // selector.
+        scheduler.runOrSchedule(executor);
+    }
+
+    // Callback: Consumer of Throwable
+    void onReadError(Throwable ex) {
+        Http1AsyncDelegate delegate;
+        Throwable recorded;
+        debug.log(Level.DEBUG, "onError: %s", (Object) ex);
+        synchronized (this) {
+            delegate = this.delegate;
+            recorded = error;
+            if (recorded == null) {
+                // retry is set to true by HttpExchange when the connection is
+                // already connected, which means it's been retrieved from
+                // the pool.
+                if (retry && (ex instanceof IOException)) {
+                    // could be either EOFException, or
+                    // IOException("connection reset by peer), or
+                    // SSLHandshakeException resulting from the server having
+                    // closed the SSL session.
+                    if (received.get() == 0) {
+                        // If we receive such an exception before having
+                        // received any byte, then in this case, we will
+                        // throw ConnectionExpiredException
+                        // to try & force a retry of the request.
+                        retry = false;
+                        ex = new ConnectionExpiredException(
+                                "subscription is finished", ex);
+                    }
+                }
+                error = ex;
+            }
+            final Throwable t = (recorded == null ? ex : recorded);
+            debug.log(Level.DEBUG, () -> "recorded " + t
+                    + "\n\t delegate: " + delegate
+                    + "\t\t queue.isEmpty: " + queue.isEmpty(), ex);
+        }
+        if (queue.isEmpty() || pendingDelegateRef.get() != null) {
+            // This callback is called from within the selector thread.
+            // Use an executor here to avoid doing the heavy lifting in the
+            // selector.
+            scheduler.runOrSchedule(executor);
+        }
+    }
+
+    void stop() {
+        debug.log(Level.DEBUG, "stopping");
+        scheduler.stop();
+        delegate = null;
+        owner  = null;
+    }
+
+    /**
+     * Returns the TubeSubscriber for reading from the connection flow.
+     * @return the TubeSubscriber for reading from the connection flow.
+     */
+    TubeSubscriber subscriber() {
+        return subscriber;
+    }
+
+    /**
+     * A simple tube subscriber for reading from the connection flow.
+     */
+    final class Http1TubeSubscriber implements TubeSubscriber {
+        volatile Flow.Subscription subscription;
+        volatile boolean completed;
+        volatile boolean dropped;
+
+        public void onSubscribe(Flow.Subscription subscription) {
+            // supports being called multiple time.
+            // doesn't cancel the previous subscription, since that is
+            // most probably the same as the new subscription.
+            assert this.subscription == null || dropped == false;
+            this.subscription = subscription;
+            dropped = false;
+            canRequestMore.set(true);
+            if (delegate != null) {
+                scheduler.runOrSchedule(executor);
+            }
+        }
+
+        void requestMore() {
+            Flow.Subscription s = subscription;
+            if (s == null) return;
+            if (canRequestMore.compareAndSet(true, false)) {
+                if (!completed && !dropped) {
+                    debug.log(Level.DEBUG,
+                        "Http1TubeSubscriber: requesting one more from upstream");
+                    s.request(1);
+                    return;
+                }
+            }
+            debug.log(Level.DEBUG, "Http1TubeSubscriber: no need to request more");
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            canRequestMore.set(item.isEmpty());
+            for (ByteBuffer buffer : item) {
+                asyncReceive(buffer);
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            onReadError(throwable);
+            completed = true;
+        }
+
+        @Override
+        public void onComplete() {
+            onReadError(new EOFException("EOF reached while reading"));
+            completed = true;
+        }
+
+        public void dropSubscription() {
+            debug.log(Level.DEBUG, "Http1TubeSubscriber: dropSubscription");
+            // we could probably set subscription to null here...
+            // then we might not need the 'dropped' boolean?
+            dropped = true;
+        }
+
+    }
+
+    // Drains the content of the queue into a single ByteBuffer.
+    // The scheduler must be permanently stopped before calling drain().
+    ByteBuffer drain(ByteBuffer initial) {
+        // Revisit: need to clean that up.
+        //
+        ByteBuffer b = initial = (initial == null ? Utils.EMPTY_BYTEBUFFER : initial);
+        assert scheduler.isStopped();
+
+        if (queue.isEmpty()) return b;
+
+        // sanity check: we shouldn't have queued the same
+        // buffer twice.
+        ByteBuffer[] qbb = queue.toArray(new ByteBuffer[queue.size()]);
+        assert java.util.stream.Stream.of(qbb)
+                .collect(Collectors.toSet())
+                .size() == qbb.length : debugQBB(qbb);
+
+        // compute the number of bytes in the queue, the number of bytes
+        // in the initial buffer
+        // TODO: will need revisiting - as it is not guaranteed that all
+        // data will fit in single BB!
+        int size = Utils.remaining(qbb, Integer.MAX_VALUE);
+        int remaining = b.remaining();
+        int free = b.capacity() - b.position() - remaining;
+        debug.log(Level.DEBUG,
+            "Flushing %s bytes from queue into initial buffer (remaining=%s, free=%s)",
+            size, remaining, free);
+
+        // check whether the initial buffer has enough space
+        if (size > free) {
+            debug.log(Level.DEBUG,
+                    "Allocating new buffer for initial: %s", (size + remaining));
+            // allocates a new buffer and copy initial to it
+            b = ByteBuffer.allocate(size + remaining);
+            Utils.copy(initial, b);
+            assert b.position() == remaining;
+            b.flip();
+            assert b.position() == 0;
+            assert b.limit() == remaining;
+            assert b.remaining() == remaining;
+        }
+
+        // store position and limit
+        int pos = b.position();
+        int limit = b.limit();
+        assert limit - pos == remaining;
+        assert b.capacity() >= remaining + size
+                : "capacity: " + b.capacity()
+                + ", remaining: " + b.remaining()
+                + ", size: " + size;
+
+        // prepare to copy the content of the queue
+        b.position(limit);
+        b.limit(pos + remaining + size);
+        assert b.remaining() >= size :
+                "remaining: " + b.remaining() + ", size: " + size;
+
+        // copy the content of the queue
+        int count = 0;
+        for (int i=0; i<qbb.length; i++) {
+            ByteBuffer b2 = qbb[i];
+            int r = b2.remaining();
+            assert b.remaining() >= r : "need at least " + r + " only "
+                    + b.remaining() + " available";
+            int copied = Utils.copy(b2, b);
+            assert copied == r : "copied="+copied+" available="+r;
+            assert b2.remaining() == 0;
+            count += copied;
+        }
+        assert count == size;
+        assert b.position() == pos + remaining + size :
+                "b.position="+b.position()+" != "+pos+"+"+remaining+"+"+size;
+
+        // reset limit and position
+        b.limit(limit+size);
+        b.position(pos);
+
+        // we can clear the refs
+        queue.clear();
+        final ByteBuffer bb = b;
+        debug.log(Level.DEBUG, () -> "Initial buffer now has " + bb.remaining()
+                + " pos=" + bb.position() + " limit=" + bb.limit());
+
+        return b;
+    }
+
+    private String debugQBB(ByteBuffer[] qbb) {
+        StringBuilder msg = new StringBuilder();
+        List<ByteBuffer> lbb = Arrays.asList(qbb);
+        Set<ByteBuffer> sbb = new HashSet<>(Arrays.asList(qbb));
+
+        int uniquebb = sbb.size();
+        msg.append("qbb: ").append(lbb.size())
+           .append(" (unique: ").append(uniquebb).append("), ")
+           .append("duplicates: ");
+        String sep = "";
+        for (ByteBuffer b : lbb) {
+            if (!sbb.remove(b)) {
+                msg.append(sep)
+                   .append(String.valueOf(b))
+                   .append("[remaining=")
+                   .append(b.remaining())
+                   .append(", position=")
+                   .append(b.position())
+                   .append(", capacity=")
+                   .append(b.capacity())
+                   .append("]");
+                sep = ", ";
+            }
+        }
+        return msg.toString();
+    }
+
+    volatile String dbgTag;
+    String dbgString() {
+        String tag = dbgTag;
+        if (tag == null) {
+            String flowTag = null;
+            Http1Exchange<?> exchg = owner;
+            Object flow = (exchg != null)
+                    ? exchg.connection().getConnectionFlow()
+                    : null;
+            flowTag = tag = flow == null ? null: (String.valueOf(flow));
+            if (flowTag != null) {
+                dbgTag = tag = flowTag + " Http1AsyncReceiver";
+            } else {
+                tag = "Http1AsyncReceiver";
+            }
+        }
+        return tag;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1Exchange.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,616 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import jdk.incubator.http.HttpResponse.BodyHandler;
+import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import jdk.incubator.http.internal.common.Demand;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.FlowTube;
+import jdk.incubator.http.internal.common.SequentialScheduler;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.Utils;
+import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
+
+/**
+ * Encapsulates one HTTP/1.1 request/response exchange.
+ */
+class Http1Exchange<T> extends ExchangeImpl<T> {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+    private static final System.Logger DEBUG_LOGGER =
+            Utils.getDebugLogger("Http1Exchange"::toString, DEBUG);
+
+    final HttpRequestImpl request; // main request
+    final Http1Request requestAction;
+    private volatile Http1Response<T> response;
+    final HttpConnection connection;
+    final HttpClientImpl client;
+    final Executor executor;
+    private final Http1AsyncReceiver asyncReceiver;
+
+    /** Records a possible cancellation raised before any operation
+     * has been initiated, or an error received while sending the request. */
+    private Throwable failed;
+    private final List<CompletableFuture<?>> operations; // used for cancel
+
+    /** Must be held when operating on any internal state or data. */
+    private final Object lock = new Object();
+
+    /** Holds the outgoing data, either the headers or a request body part. Or
+     * an error from the request body publisher. At most there can be ~2 pieces
+     * of outgoing data ( onComplete|onError can be invoked without demand ).*/
+    final ConcurrentLinkedDeque<DataPair> outgoing = new ConcurrentLinkedDeque<>();
+
+    /** The write publisher, responsible for writing the complete request ( both
+     * headers and body ( if any ). */
+    private final Http1Publisher writePublisher = new Http1Publisher();
+
+    /** Completed when the header have been published, or there is an error */
+    private final CompletableFuture<ExchangeImpl<T>> headersSentCF  = new MinimalFuture<>();
+     /** Completed when the body has been published, or there is an error */
+    private final CompletableFuture<ExchangeImpl<T>> bodySentCF = new MinimalFuture<>();
+
+    /** The subscriber to the request's body published. Maybe null. */
+    private volatile Http1BodySubscriber bodySubscriber;
+
+    enum State { INITIAL,
+                 HEADERS,
+                 BODY,
+                 ERROR,          // terminal state
+                 COMPLETING,
+                 COMPLETED }     // terminal state
+
+    private State state = State.INITIAL;
+
+    /** A carrier for either data or an error. Used to carry data, and communicate
+     * errors from the request ( both headers and body ) to the exchange. */
+    static class DataPair {
+        Throwable throwable;
+        List<ByteBuffer> data;
+        DataPair(List<ByteBuffer> data, Throwable throwable){
+            this.data = data;
+            this.throwable = throwable;
+        }
+        @Override
+        public String toString() {
+            return "DataPair [data=" + data + ", throwable=" + throwable + "]";
+        }
+    }
+
+    /** An abstract supertype for HTTP/1.1 body subscribers. There are two
+     * concrete implementations: {@link Http1Request.StreamSubscriber}, and
+     * {@link Http1Request.FixedContentSubscriber}, for receiving chunked and
+     * fixed length bodies, respectively. */
+    static abstract class Http1BodySubscriber implements Flow.Subscriber<ByteBuffer> {
+        protected volatile Flow.Subscription subscription;
+        protected volatile boolean complete;
+
+        /** Final sentinel in the stream of request body. */
+        static final List<ByteBuffer> COMPLETED = List.of(ByteBuffer.allocate(0));
+
+        void request(long n) {
+            DEBUG_LOGGER.log(Level.DEBUG, () ->
+                "Http1BodySubscriber requesting " + n + ", from " + subscription);
+            subscription.request(n);
+        }
+
+        static Http1BodySubscriber completeSubscriber() {
+            return new Http1BodySubscriber() {
+                @Override public void onSubscribe(Flow.Subscription subscription) { error(); }
+                @Override public void onNext(ByteBuffer item) { error(); }
+                @Override public void onError(Throwable throwable) { error(); }
+                @Override public void onComplete() { error(); }
+                private void error() {
+                    throw new InternalError("should not reach here");
+                }
+            };
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "HTTP/1.1 " + request.toString();
+    }
+
+    HttpRequestImpl request() {
+        return request;
+    }
+
+    Http1Exchange(Exchange<T> exchange, HttpConnection connection)
+        throws IOException
+    {
+        super(exchange);
+        this.request = exchange.request();
+        this.client = exchange.client();
+        this.executor = exchange.executor();
+        this.operations = new LinkedList<>();
+        operations.add(headersSentCF);
+        operations.add(bodySentCF);
+        if (connection != null) {
+            this.connection = connection;
+        } else {
+            InetSocketAddress addr = request.getAddress();
+            this.connection = HttpConnection.getConnection(addr, client, request, HTTP_1_1);
+        }
+        this.requestAction = new Http1Request(request, this);
+        this.asyncReceiver = new Http1AsyncReceiver(executor, this);
+        asyncReceiver.subscribe(new InitialErrorReceiver());
+    }
+
+    /** An initial receiver that handles no data, but cancels the request if
+     * it receives an error. Will be replaced when reading response body. */
+    final class InitialErrorReceiver implements Http1AsyncReceiver.Http1AsyncDelegate {
+        volatile AbstractSubscription s;
+        @Override
+        public boolean tryAsyncReceive(ByteBuffer ref) {
+            return false;  // no data has been processed, leave it in the queue
+        }
+
+        @Override
+        public void onReadError(Throwable ex) {
+            cancelImpl(ex);
+        }
+
+        @Override
+        public void onSubscribe(AbstractSubscription s) {
+            this.s = s;
+        }
+
+        public AbstractSubscription subscription() {
+            return s;
+        }
+    }
+
+    @Override
+    HttpConnection connection() {
+        return connection;
+    }
+
+    private void connectFlows(HttpConnection connection) {
+        FlowTube tube =  connection.getConnectionFlow();
+        debug.log(Level.DEBUG, "%s connecting flows", tube);
+
+        // Connect the flow to our Http1TubeSubscriber:
+        //   asyncReceiver.subscriber().
+        tube.connectFlows(writePublisher,
+                          asyncReceiver.subscriber());
+    }
+
+    @Override
+    CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
+        // create the response before sending the request headers, so that
+        // the response can set the appropriate receivers.
+        debug.log(Level.DEBUG, "Sending headers only");
+        if (response == null) {
+            response = new Http1Response<>(connection, this, asyncReceiver);
+        }
+
+        debug.log(Level.DEBUG, "response created in advance");
+        // If the first attempt to read something triggers EOF, or
+        // IOException("channel reset by peer"), we're going to retry.
+        // Instruct the asyncReceiver to throw ConnectionExpiredException
+        // to force a retry.
+        asyncReceiver.setRetryOnError(true);
+
+        CompletableFuture<Void> connectCF;
+        if (!connection.connected()) {
+            debug.log(Level.DEBUG, "initiating connect async");
+            connectCF = connection.connectAsync();
+            synchronized (lock) {
+                operations.add(connectCF);
+            }
+        } else {
+            connectCF = new MinimalFuture<>();
+            connectCF.complete(null);
+        }
+
+        return connectCF
+                .thenCompose(unused -> {
+                    CompletableFuture<Void> cf = new MinimalFuture<>();
+                    try {
+                        connectFlows(connection);
+
+                        debug.log(Level.DEBUG, "requestAction.headers");
+                        List<ByteBuffer> data = requestAction.headers();
+                        synchronized (lock) {
+                            state = State.HEADERS;
+                        }
+                        debug.log(Level.DEBUG, "setting outgoing with headers");
+                        assert outgoing.isEmpty() : "Unexpected outgoing:" + outgoing;
+                        appendToOutgoing(data);
+                        cf.complete(null);
+                        return cf;
+                    } catch (Throwable t) {
+                        debug.log(Level.DEBUG, "Failed to send headers: %s", t);
+                        connection.close();
+                        cf.completeExceptionally(t);
+                        return cf;
+                    } })
+                .thenCompose(unused -> headersSentCF);
+    }
+
+    @Override
+    CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
+        assert headersSentCF.isDone();
+        try {
+            bodySubscriber = requestAction.continueRequest();
+            if (bodySubscriber == null) {
+                bodySubscriber = Http1BodySubscriber.completeSubscriber();
+                appendToOutgoing(Http1BodySubscriber.COMPLETED);
+            } else {
+                bodySubscriber.request(1);  // start
+            }
+        } catch (Throwable t) {
+            connection.close();
+            bodySentCF.completeExceptionally(t);
+        }
+        return bodySentCF;
+    }
+
+    @Override
+    CompletableFuture<Response> getResponseAsync(Executor executor) {
+        CompletableFuture<Response> cf = response.readHeadersAsync(executor);
+        Throwable cause;
+        synchronized (lock) {
+            operations.add(cf);
+            cause = failed;
+            failed = null;
+        }
+
+        if (cause != null) {
+            Log.logTrace("Http1Exchange: request [{0}/timeout={1}ms]"
+                            + "\n\tCompleting exceptionally with {2}\n",
+                         request.uri(),
+                         request.timeout().isPresent() ?
+                            // calling duration.toMillis() can throw an exception.
+                            // this is just debugging, we don't care if it overflows.
+                            (request.timeout().get().getSeconds() * 1000
+                             + request.timeout().get().getNano() / 1000000) : -1,
+                         cause);
+            boolean acknowledged = cf.completeExceptionally(cause);
+            debug.log(Level.DEBUG,
+                      () -> acknowledged
+                            ? ("completed response with " + cause)
+                            : ("response already completed, ignoring " + cause));
+        }
+        return cf;
+    }
+
+    @Override
+    CompletableFuture<T> readBodyAsync(BodyHandler<T> handler,
+                                       boolean returnConnectionToPool,
+                                       Executor executor)
+    {
+        BodySubscriber<T> bs = handler.apply(response.responseCode(),
+                                             response.responseHeaders());
+        CompletableFuture<T> bodyCF = response.readBody(bs,
+                                                        returnConnectionToPool,
+                                                        executor);
+        return bodyCF;
+    }
+
+    @Override
+    CompletableFuture<Void> ignoreBody() {
+        return response.ignoreBody(executor);
+    }
+
+    ByteBuffer drainLeftOverBytes() {
+        synchronized (lock) {
+            asyncReceiver.stop();
+            return asyncReceiver.drain(Utils.EMPTY_BYTEBUFFER);
+        }
+    }
+
+    void released() {
+        Http1Response<T> resp = this.response;
+        if (resp != null) resp.completed();
+        asyncReceiver.clear();
+    }
+
+    void completed() {
+        Http1Response<T> resp = this.response;
+        if (resp != null) resp.completed();
+    }
+
+    /**
+     * Cancel checks to see if request and responseAsync finished already.
+     * If not it closes the connection and completes all pending operations
+     */
+    @Override
+    void cancel() {
+        cancelImpl(new IOException("Request cancelled"));
+    }
+
+    /**
+     * Cancel checks to see if request and responseAsync finished already.
+     * If not it closes the connection and completes all pending operations
+     */
+    @Override
+    void cancel(IOException cause) {
+        cancelImpl(cause);
+    }
+
+    private void cancelImpl(Throwable cause) {
+        LinkedList<CompletableFuture<?>> toComplete = null;
+        int count = 0;
+        synchronized (lock) {
+            if (failed == null)
+                failed = cause;
+            if (requestAction != null && requestAction.finished()
+                    && response != null && response.finished()) {
+                return;
+            }
+            connection.close();   // TODO: ensure non-blocking if holding the lock
+            writePublisher.writeScheduler.stop();
+            if (operations.isEmpty()) {
+                Log.logTrace("Http1Exchange: request [{0}/timeout={1}ms] no pending operation."
+                                + "\n\tCan''t cancel yet with {2}",
+                             request.uri(),
+                             request.timeout().isPresent() ?
+                                // calling duration.toMillis() can throw an exception.
+                                // this is just debugging, we don't care if it overflows.
+                                (request.timeout().get().getSeconds() * 1000
+                                 + request.timeout().get().getNano() / 1000000) : -1,
+                             cause);
+            } else {
+                for (CompletableFuture<?> cf : operations) {
+                    if (!cf.isDone()) {
+                        if (toComplete == null) toComplete = new LinkedList<>();
+                        toComplete.add(cf);
+                        count++;
+                    }
+                }
+                operations.clear();
+            }
+        }
+        Log.logError("Http1Exchange.cancel: count=" + count);
+        if (toComplete != null) {
+            // We might be in the selector thread in case of timeout, when
+            // the SelectorManager calls purgeTimeoutsAndReturnNextDeadline()
+            // There may or may not be other places that reach here
+            // from the SelectorManager thread, so just make sure we
+            // don't complete any CF from within the selector manager
+            // thread.
+            Executor exec = client.isSelectorThread()
+                            ? executor
+                            : this::runInline;
+            while (!toComplete.isEmpty()) {
+                CompletableFuture<?> cf = toComplete.poll();
+                exec.execute(() -> {
+                    if (cf.completeExceptionally(cause)) {
+                        debug.log(Level.DEBUG, "completed cf with %s",
+                                 (Object) cause);
+                    }
+                });
+            }
+        }
+    }
+
+    private void runInline(Runnable run) {
+        assert !client.isSelectorThread();
+        run.run();
+    }
+
+    /** Returns true if this exchange was canceled. */
+    boolean isCanceled() {
+        synchronized (lock) {
+            return failed != null;
+        }
+    }
+
+    /** Returns the cause for which this exchange was canceled, if available. */
+    Throwable getCancelCause() {
+        synchronized (lock) {
+            return failed;
+        }
+    }
+
+    /** Convenience for {@link #appendToOutgoing(DataPair)}, with just a Throwable. */
+    void appendToOutgoing(Throwable throwable) {
+        appendToOutgoing(new DataPair(null, throwable));
+    }
+
+    /** Convenience for {@link #appendToOutgoing(DataPair)}, with just data. */
+    void appendToOutgoing(List<ByteBuffer> item) {
+        appendToOutgoing(new DataPair(item, null));
+    }
+
+    private void appendToOutgoing(DataPair dp) {
+        debug.log(Level.DEBUG, "appending to outgoing " + dp);
+        outgoing.add(dp);
+        writePublisher.writeScheduler.runOrSchedule();
+    }
+
+    /** Tells whether, or not, there is any outgoing data that can be published,
+     * or if there is an error. */
+    private boolean hasOutgoing() {
+        return !outgoing.isEmpty();
+    }
+
+    // Invoked only by the publisher
+    // ALL tasks should execute off the Selector-Manager thread
+    /** Returns the next portion of the HTTP request, or the error. */
+    private DataPair getOutgoing() {
+        final Executor exec = client.theExecutor();
+        final DataPair dp = outgoing.pollFirst();
+
+        if (dp == null)  // publisher has not published anything yet
+            return null;
+
+        synchronized (lock) {
+            if (dp.throwable != null) {
+                state = State.ERROR;
+                exec.execute(() -> {
+                    connection.close();
+                    headersSentCF.completeExceptionally(dp.throwable);
+                    bodySentCF.completeExceptionally(dp.throwable);
+                });
+                return dp;
+            }
+
+            switch (state) {
+                case HEADERS:
+                    state = State.BODY;
+                    // completeAsync, since dependent tasks should run in another thread
+                    debug.log(Level.DEBUG, "initiating completion of headersSentCF");
+                    headersSentCF.completeAsync(() -> this, exec);
+                    break;
+                case BODY:
+                    if (dp.data == Http1BodySubscriber.COMPLETED) {
+                        state = State.COMPLETING;
+                        debug.log(Level.DEBUG, "initiating completion of bodySentCF");
+                        bodySentCF.completeAsync(() -> this, exec);
+                    } else {
+                        debug.log(Level.DEBUG, "requesting more body from the subscriber");
+                        exec.execute(() -> bodySubscriber.request(1));
+                    }
+                    break;
+                case INITIAL:
+                case ERROR:
+                case COMPLETING:
+                case COMPLETED:
+                default:
+                    assert false : "Unexpected state:" + state;
+            }
+
+            return dp;
+        }
+    }
+
+    /** A Publisher of HTTP/1.1 headers and request body. */
+    final class Http1Publisher implements FlowTube.TubePublisher {
+
+        final System.Logger  debug = Utils.getDebugLogger(this::dbgString);
+        volatile Flow.Subscriber<? super List<ByteBuffer>> subscriber;
+        volatile boolean cancelled;
+        final Http1WriteSubscription subscription = new Http1WriteSubscription();
+        final Demand demand = new Demand();
+        final SequentialScheduler writeScheduler =
+                SequentialScheduler.synchronizedScheduler(new WriteTask());
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
+            assert state == State.INITIAL;
+            Objects.requireNonNull(s);
+            assert subscriber == null;
+
+            subscriber = s;
+            debug.log(Level.DEBUG, "got subscriber: %s", s);
+            s.onSubscribe(subscription);
+        }
+
+        volatile String dbgTag;
+        String dbgString() {
+            String tag = dbgTag;
+            Object flow = connection.getConnectionFlow();
+            if (tag == null && flow != null) {
+                dbgTag = tag = "Http1Publisher(" + flow + ")";
+            } else if (tag == null) {
+                tag = "Http1Publisher(?)";
+            }
+            return tag;
+        }
+
+        final class WriteTask implements Runnable {
+            @Override
+            public void run() {
+                assert state != State.COMPLETED : "Unexpected state:" + state;
+                debug.log(Level.DEBUG, "WriteTask");
+                if (subscriber == null) {
+                    debug.log(Level.DEBUG, "no subscriber yet");
+                    return;
+                }
+                debug.log(Level.DEBUG, () -> "hasOutgoing = " + hasOutgoing());
+                while (hasOutgoing() && demand.tryDecrement()) {
+                    DataPair dp = getOutgoing();
+
+                    if (dp.throwable != null) {
+                        debug.log(Level.DEBUG, "onError");
+                        // Do not call the subscriber's onError, it is not required.
+                        writeScheduler.stop();
+                    } else {
+                        List<ByteBuffer> data = dp.data;
+                        if (data == Http1BodySubscriber.COMPLETED) {
+                            synchronized (lock) {
+                                assert state == State.COMPLETING : "Unexpected state:" + state;
+                                state = State.COMPLETED;
+                            }
+                            debug.log(Level.DEBUG,
+                                     "completed, stopping %s", writeScheduler);
+                            writeScheduler.stop();
+                            // Do nothing more. Just do not publish anything further.
+                            // The next Subscriber will eventually take over.
+
+                        } else {
+                            debug.log(Level.DEBUG, () ->
+                                    "onNext with " + Utils.remaining(data) + " bytes");
+                            subscriber.onNext(data);
+                        }
+                    }
+                }
+            }
+        }
+
+        final class Http1WriteSubscription implements Flow.Subscription {
+
+            @Override
+            public void request(long n) {
+                if (cancelled)
+                    return;  //no-op
+                demand.increase(n);
+                debug.log(Level.DEBUG,
+                        "subscription request(%d), demand=%s", n, demand);
+                writeScheduler.runOrSchedule(client.theExecutor());
+            }
+
+            @Override
+            public void cancel() {
+                debug.log(Level.DEBUG, "subscription cancelled");
+                if (cancelled)
+                    return;  //no-op
+                cancelled = true;
+                writeScheduler.stop();
+            }
+        }
+    }
+
+    String dbgString() {
+        return "Http1Exchange";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1HeaderParser.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import jdk.incubator.http.HttpHeaders;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+class Http1HeaderParser {
+
+    private static final char CR = '\r';
+    private static final char LF = '\n';
+    private static final char HT = '\t';
+    private static final char SP = ' ';
+
+    private StringBuilder sb = new StringBuilder();
+    private String statusLine;
+    private int responseCode;
+    private HttpHeaders headers;
+    private Map<String,List<String>> privateMap = new HashMap<>();
+
+    enum State { STATUS_LINE,
+                 STATUS_LINE_FOUND_CR,
+                 STATUS_LINE_END,
+                 STATUS_LINE_END_CR,
+                 HEADER,
+                 HEADER_FOUND_CR,
+                 HEADER_FOUND_LF,
+                 HEADER_FOUND_CR_LF,
+                 HEADER_FOUND_CR_LF_CR,
+                 FINISHED }
+
+    private State state = State.STATUS_LINE;
+
+    /** Returns the status-line. */
+    String statusLine() { return statusLine; }
+
+    /** Returns the response code. */
+    int responseCode() { return responseCode; }
+
+    /** Returns the headers, possibly empty. */
+    HttpHeaders headers() { assert state == State.FINISHED; return headers; }
+
+    /**
+     * Parses HTTP/1.X status-line and headers from the given bytes. Must be
+     * called successive times, with additional data, until returns true.
+     *
+     * All given ByteBuffers will be consumed, until ( possibly ) the last one
+     * ( when true is returned ), which may not be fully consumed.
+     *
+     * @param input the ( partial ) header data
+     * @return true iff the end of the headers block has been reached
+     */
+    boolean parse(ByteBuffer input) throws ProtocolException {
+        requireNonNull(input, "null input");
+
+        while (input.hasRemaining() && state != State.FINISHED) {
+            switch (state) {
+                case STATUS_LINE:
+                    readResumeStatusLine(input);
+                    break;
+                case STATUS_LINE_FOUND_CR:
+                    readStatusLineFeed(input);
+                    break;
+                case STATUS_LINE_END:
+                    maybeStartHeaders(input);
+                    break;
+                case STATUS_LINE_END_CR:
+                    maybeEndHeaders(input);
+                    break;
+                case HEADER:
+                    readResumeHeader(input);
+                    break;
+                // fallthrough
+                case HEADER_FOUND_CR:
+                case HEADER_FOUND_LF:
+                    resumeOrLF(input);
+                    break;
+                case HEADER_FOUND_CR_LF:
+                    resumeOrSecondCR(input);
+                    break;
+                case HEADER_FOUND_CR_LF_CR:
+                    resumeOrEndHeaders(input);
+                    break;
+                default:
+                    throw new InternalError(
+                            "Unexpected state: " + String.valueOf(state));
+            }
+        }
+
+        return state == State.FINISHED;
+    }
+
+    private void readResumeStatusLine(ByteBuffer input) {
+        char c = 0;
+        while (input.hasRemaining() && (c =(char)input.get()) != CR) {
+            sb.append(c);
+        }
+
+        if (c == CR) {
+            state = State.STATUS_LINE_FOUND_CR;
+        }
+    }
+
+    private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
+        char c = (char)input.get();
+        if (c != LF) {
+            throw protocolException("Bad trailing char, \"%s\", when parsing status-line, \"%s\"",
+                                    c, sb.toString());
+        }
+
+        statusLine = sb.toString();
+        sb = new StringBuilder();
+        if (!statusLine.startsWith("HTTP/1.")) {
+            throw protocolException("Invalid status line: \"%s\"", statusLine);
+        }
+        if (statusLine.length() < 12) {
+            throw protocolException("Invalid status line: \"%s\"", statusLine);
+        }
+        responseCode = Integer.parseInt(statusLine.substring(9, 12));
+
+        state = State.STATUS_LINE_END;
+    }
+
+    private void maybeStartHeaders(ByteBuffer input) {
+        assert state == State.STATUS_LINE_END;
+        assert sb.length() == 0;
+        char c = (char)input.get();
+        if (c == CR) {
+            state = State.STATUS_LINE_END_CR;
+        } else {
+            sb.append(c);
+            state = State.HEADER;
+        }
+    }
+
+    private void maybeEndHeaders(ByteBuffer input) throws ProtocolException {
+        assert state == State.STATUS_LINE_END_CR;
+        assert sb.length() == 0;
+        char c = (char)input.get();
+        if (c == LF) {
+            headers = ImmutableHeaders.of(privateMap);
+            privateMap = null;
+            state = State.FINISHED;  // no headers
+        } else {
+            throw protocolException("Unexpected \"%s\", after status-line CR", c);
+        }
+    }
+
+    private void readResumeHeader(ByteBuffer input) {
+        assert state == State.HEADER;
+        assert input.hasRemaining();
+        while (input.hasRemaining()) {
+            char c = (char)input.get();
+            if (c == CR) {
+                state = State.HEADER_FOUND_CR;
+                break;
+            } else if (c == LF) {
+                state = State.HEADER_FOUND_LF;
+                break;
+            }
+
+            if (c == HT)
+                c = SP;
+            sb.append(c);
+        }
+    }
+
+    private void addHeaderFromString(String headerString) {
+        assert sb.length() == 0;
+        int idx = headerString.indexOf(':');
+        if (idx == -1)
+            return;
+        String name = headerString.substring(0, idx).trim();
+        if (name.isEmpty())
+            return;
+        String value = headerString.substring(idx + 1, headerString.length()).trim();
+
+        privateMap.computeIfAbsent(name.toLowerCase(Locale.US),
+                                   k -> new ArrayList<>()).add(value);
+    }
+
+    private void resumeOrLF(ByteBuffer input) {
+        assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF;
+        char c = (char)input.get();
+        if (c == LF && state == State.HEADER_FOUND_CR) {
+            // header value will be flushed by
+            // resumeOrSecondCR if next line does not
+            // begin by SP or HT
+            state = State.HEADER_FOUND_CR_LF;
+        } else if (c == SP || c == HT) {
+            sb.append(SP); // parity with MessageHeaders
+            state = State.HEADER;
+        } else {
+            sb = new StringBuilder();
+            sb.append(c);
+            state = State.HEADER;
+        }
+    }
+
+    private void resumeOrSecondCR(ByteBuffer input) {
+        assert state == State.HEADER_FOUND_CR_LF;
+        char c = (char)input.get();
+        if (c == CR) {
+            if (sb.length() > 0) {
+                // no continuation line - flush
+                // previous header value.
+                String headerString = sb.toString();
+                sb = new StringBuilder();
+                addHeaderFromString(headerString);
+            }
+            state = State.HEADER_FOUND_CR_LF_CR;
+        } else if (c == SP || c == HT) {
+            assert sb.length() != 0;
+            sb.append(SP); // continuation line
+            state = State.HEADER;
+        } else {
+            if (sb.length() > 0) {
+                // no continuation line - flush
+                // previous header value.
+                String headerString = sb.toString();
+                sb = new StringBuilder();
+                addHeaderFromString(headerString);
+            }
+            sb.append(c);
+            state = State.HEADER;
+        }
+    }
+
+    private void resumeOrEndHeaders(ByteBuffer input) throws ProtocolException {
+        assert state == State.HEADER_FOUND_CR_LF_CR;
+        char c = (char)input.get();
+        if (c == LF) {
+            state = State.FINISHED;
+            headers = ImmutableHeaders.of(privateMap);
+            privateMap = null;
+        } else {
+            throw protocolException("Unexpected \"%s\", after CR LF CR", c);
+        }
+    }
+
+    private ProtocolException protocolException(String format, Object... args) {
+        return new ProtocolException(format(format, args));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1Request.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.net.InetSocketAddress;
+import java.util.Objects;
+import java.util.concurrent.Flow;
+import java.util.function.BiPredicate;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.internal.Http1Exchange.Http1BodySubscriber;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.Utils;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+/**
+ *  An HTTP/1.1 request.
+ */
+class Http1Request {
+    private final HttpRequestImpl request;
+    private final Http1Exchange<?> http1Exchange;
+    private final HttpConnection connection;
+    private final HttpRequest.BodyPublisher requestPublisher;
+    private final HttpHeaders userHeaders;
+    private final HttpHeadersImpl systemHeaders;
+    private volatile boolean streaming;
+    private volatile long contentLength;
+
+    Http1Request(HttpRequestImpl request,
+                 Http1Exchange<?> http1Exchange)
+        throws IOException
+    {
+        this.request = request;
+        this.http1Exchange = http1Exchange;
+        this.connection = http1Exchange.connection();
+        this.requestPublisher = request.requestPublisher;  // may be null
+        this.userHeaders = request.getUserHeaders();
+        this.systemHeaders = request.getSystemHeaders();
+    }
+
+    private void logHeaders(String completeHeaders) {
+        if (Log.headers()) {
+            //StringBuilder sb = new StringBuilder(256);
+            //sb.append("REQUEST HEADERS:\n");
+            //Log.dumpHeaders(sb, "    ", systemHeaders);
+            //Log.dumpHeaders(sb, "    ", userHeaders);
+            //Log.logHeaders(sb.toString());
+
+            String s = completeHeaders.replaceAll("\r\n", "\n");
+            Log.logHeaders("REQUEST HEADERS:\n" + s);
+        }
+    }
+
+
+    private void collectHeaders0(StringBuilder sb) {
+        BiPredicate<String,List<String>> filter =
+                connection.headerFilter(request);
+
+        // If we're sending this request through a tunnel,
+        // then don't send any preemptive proxy-* headers that
+        // the authentication filter may have saved in its
+        // cache.
+        collectHeaders1(sb, systemHeaders, filter);
+
+        // If we're sending this request through a tunnel,
+        // don't send any user-supplied proxy-* headers
+        // to the target server.
+        collectHeaders1(sb, userHeaders, filter);
+        sb.append("\r\n");
+    }
+
+    private void collectHeaders1(StringBuilder sb, HttpHeaders headers,
+                                 BiPredicate<String, List<String>> filter) {
+        for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
+            String key = entry.getKey();
+            List<String> values = entry.getValue();
+            if (!filter.test(key, values)) continue;
+            for (String value : values) {
+                sb.append(key).append(": ").append(value).append("\r\n");
+            }
+        }
+    }
+
+    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 hostString() {
+        URI uri = request.uri();
+        int port = uri.getPort();
+        String host = uri.getHost();
+
+        boolean defaultPort;
+        if (port == -1) {
+            defaultPort = true;
+        } else if (request.secure()) {
+            defaultPort = port == 443;
+        } else {
+            defaultPort = port == 80;
+        }
+
+        if (defaultPort) {
+            return host;
+        } else {
+            return host + ":" + Integer.toString(port);
+        }
+    }
+
+    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);
+            }
+        }
+        if (request.method().equals("CONNECT")) {
+            // use authority for connect itself
+            return authorityString(request.authority());
+        }
+
+        return uri == null? authorityString(request.authority()) : uri.toString();
+    }
+
+    private boolean finished;
+
+    synchronized boolean finished() {
+        return  finished;
+    }
+
+    synchronized void setFinished() {
+        finished = true;
+    }
+
+    List<ByteBuffer> headers() {
+        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");
+
+        URI uri = request.uri();
+        if (uri != null) {
+            systemHeaders.setHeader("Host", hostString());
+        }
+        if (requestPublisher == null) {
+            // Not a user request, or maybe a method, e.g. GET, with no body.
+            contentLength = 0;
+        } else {
+            contentLength = requestPublisher.contentLength();
+        }
+
+        if (contentLength == 0) {
+            systemHeaders.setHeader("Content-Length", "0");
+        } else if (contentLength > 0) {
+            systemHeaders.setHeader("Content-Length", Long.toString(contentLength));
+            streaming = false;
+        } else {
+            streaming = true;
+            systemHeaders.setHeader("Transfer-encoding", "chunked");
+        }
+        collectHeaders0(sb);
+        String hs = sb.toString();
+        logHeaders(hs);
+        ByteBuffer b = ByteBuffer.wrap(hs.getBytes(US_ASCII));
+        return List.of(b);
+    }
+
+    Http1BodySubscriber continueRequest()  {
+        Http1BodySubscriber subscriber;
+        if (streaming) {
+            subscriber = new StreamSubscriber();
+            requestPublisher.subscribe(subscriber);
+        } else {
+            if (contentLength == 0)
+                return null;
+
+            subscriber = new FixedContentSubscriber();
+            requestPublisher.subscribe(subscriber);
+        }
+        return subscriber;
+    }
+
+    class StreamSubscriber extends Http1BodySubscriber {
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (this.subscription != null) {
+                Throwable t = new IllegalStateException("already subscribed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                this.subscription = subscription;
+            }
+        }
+
+        @Override
+        public void onNext(ByteBuffer item) {
+            Objects.requireNonNull(item);
+            if (complete) {
+                Throwable t = new IllegalStateException("subscription already completed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                int chunklen = item.remaining();
+                ArrayList<ByteBuffer> l = new ArrayList<>(3);
+                l.add(getHeader(chunklen));
+                l.add(item);
+                l.add(ByteBuffer.wrap(CRLF));
+                http1Exchange.appendToOutgoing(l);
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            if (complete)
+                return;
+
+            subscription.cancel();
+            http1Exchange.appendToOutgoing(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            if (complete) {
+                Throwable t = new IllegalStateException("subscription already completed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                ArrayList<ByteBuffer> l = new ArrayList<>(2);
+                l.add(ByteBuffer.wrap(EMPTY_CHUNK_BYTES));
+                l.add(ByteBuffer.wrap(CRLF));
+                complete = true;
+                //setFinished();
+                http1Exchange.appendToOutgoing(l);
+                http1Exchange.appendToOutgoing(COMPLETED);
+                setFinished();  // TODO: before or after,? does it matter?
+
+            }
+        }
+    }
+
+    class FixedContentSubscriber extends Http1BodySubscriber {
+
+        private volatile long contentWritten;
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (this.subscription != null) {
+                Throwable t = new IllegalStateException("already subscribed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                this.subscription = subscription;
+            }
+        }
+
+        @Override
+        public void onNext(ByteBuffer item) {
+            debug.log(Level.DEBUG, "onNext");
+            Objects.requireNonNull(item);
+            if (complete) {
+                Throwable t = new IllegalStateException("subscription already completed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                long writing = item.remaining();
+                long written = (contentWritten += writing);
+
+                if (written > contentLength) {
+                    subscription.cancel();
+                    String msg = connection.getConnectionFlow()
+                                  + " [" + Thread.currentThread().getName() +"] "
+                                  + "Too many bytes in request body. Expected: "
+                                  + contentLength + ", got: " + written;
+                    http1Exchange.appendToOutgoing(new IOException(msg));
+                } else {
+                    http1Exchange.appendToOutgoing(List.of(item));
+                }
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            debug.log(Level.DEBUG, "onError");
+            if (complete)  // TODO: error?
+                return;
+
+            subscription.cancel();
+            http1Exchange.appendToOutgoing(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            debug.log(Level.DEBUG, "onComplete");
+            if (complete) {
+                Throwable t = new IllegalStateException("subscription already completed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                complete = true;
+                long written = contentWritten;
+                if (contentLength > written) {
+                    subscription.cancel();
+                    Throwable t = new IOException(connection.getConnectionFlow()
+                                         + " [" + Thread.currentThread().getName() +"] "
+                                         + "Too few bytes returned by the publisher ("
+                                                  + written + "/"
+                                                  + contentLength + ")");
+                    http1Exchange.appendToOutgoing(t);
+                } else {
+                    http1Exchange.appendToOutgoing(COMPLETED);
+                }
+            }
+        }
+    }
+
+    private static final byte[] CRLF = {'\r', '\n'};
+    private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};
+
+    /** 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);
+    }
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this::toString, DEBUG);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http1Response.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,518 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.EOFException;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.internal.ResponseContent.BodyParser;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.Utils;
+import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
+
+/**
+ * Handles a HTTP/1.1 response (headers + body).
+ * There can be more than one of these per Http exchange.
+ */
+class Http1Response<T> {
+
+    private volatile ResponseContent content;
+    private final HttpRequestImpl request;
+    private Response response;
+    private final HttpConnection connection;
+    private HttpHeaders headers;
+    private int responseCode;
+    private final Http1Exchange<T> exchange;
+    private boolean return2Cache; // return connection to cache when finished
+    private final HeadersReader headersReader; // used to read the headers
+    private final BodyReader bodyReader; // used to read the body
+    private final Http1AsyncReceiver asyncReceiver;
+    private volatile EOFException eof;
+    // max number of bytes of (fixed length) body to ignore on redirect
+    private final static int MAX_IGNORE = 1024;
+
+    // Revisit: can we get rid of this?
+    static enum State {INITIAL, READING_HEADERS, READING_BODY, DONE}
+    private volatile State readProgress = State.INITIAL;
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this.getClass()::getSimpleName, DEBUG);
+
+
+    Http1Response(HttpConnection conn,
+                  Http1Exchange<T> exchange,
+                  Http1AsyncReceiver asyncReceiver) {
+        this.readProgress = State.INITIAL;
+        this.request = exchange.request();
+        this.exchange = exchange;
+        this.connection = conn;
+        this.asyncReceiver = asyncReceiver;
+        headersReader = new HeadersReader(this::advance);
+        bodyReader = new BodyReader(this::advance);
+    }
+
+   public CompletableFuture<Response> readHeadersAsync(Executor executor) {
+        debug.log(Level.DEBUG, () -> "Reading Headers: (remaining: "
+                + asyncReceiver.remaining() +") "  + readProgress);
+        // with expect continue we will resume reading headers + body.
+        asyncReceiver.unsubscribe(bodyReader);
+        bodyReader.reset();
+        Http1HeaderParser hd = new Http1HeaderParser();
+        readProgress = State.READING_HEADERS;
+        headersReader.start(hd);
+        asyncReceiver.subscribe(headersReader);
+        CompletableFuture<State> cf = headersReader.completion();
+        assert cf != null : "parsing not started";
+
+        Function<State, Response> lambda = (State completed) -> {
+                assert completed == State.READING_HEADERS;
+                debug.log(Level.DEBUG, () ->
+                            "Reading Headers: creating Response object;"
+                            + " state is now " + readProgress);
+                asyncReceiver.unsubscribe(headersReader);
+                responseCode = hd.responseCode();
+                headers = hd.headers();
+
+                response = new Response(request,
+                                        exchange.getExchange(),
+                                        headers,
+                                        responseCode,
+                                        HTTP_1_1);
+                return response;
+            };
+
+        if (executor != null) {
+            return cf.thenApplyAsync(lambda, executor);
+        } else {
+            return cf.thenApply(lambda);
+        }
+    }
+
+    private boolean finished;
+
+    synchronized void completed() {
+        finished = true;
+    }
+
+    synchronized boolean finished() {
+        return finished;
+    }
+
+    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;
+    }
+
+    /**
+     * Read up to MAX_IGNORE bytes discarding
+     */
+    public CompletableFuture<Void> ignoreBody(Executor executor) {
+        int clen = (int)headers.firstValueAsLong("Content-Length").orElse(-1);
+        if (clen == -1 || clen > MAX_IGNORE) {
+            connection.close();
+            return MinimalFuture.completedFuture(null); // not treating as error
+        } else {
+            return readBody(HttpResponse.BodySubscriber.discard((Void)null), true, executor);
+        }
+    }
+
+    public <U> CompletableFuture<U> readBody(HttpResponse.BodySubscriber<U> p,
+                                         boolean return2Cache,
+                                         Executor executor) {
+        this.return2Cache = return2Cache;
+        final HttpResponse.BodySubscriber<U> pusher = p;
+        final CompletionStage<U> bodyCF = p.getBody();
+        final CompletableFuture<U> cf = MinimalFuture.of(bodyCF);
+
+        int clen0 = (int)headers.firstValueAsLong("Content-Length").orElse(-1);
+
+        final int clen = fixupContentLen(clen0);
+
+        // expect-continue reads headers and body twice.
+        // if we reach here, we must reset the headersReader state.
+        asyncReceiver.unsubscribe(headersReader);
+        headersReader.reset();
+
+        executor.execute(() -> {
+            try {
+                HttpClientImpl client = connection.client();
+                content = new ResponseContent(
+                        connection, clen, headers, pusher,
+                        this::onFinished
+                );
+                if (cf.isCompletedExceptionally()) {
+                    // if an error occurs during subscription
+                    connection.close();
+                    return;
+                }
+                // increment the reference count on the HttpClientImpl
+                // to prevent the SelectorManager thread from exiting until
+                // the body is fully read.
+                client.reference();
+                bodyReader.start(content.getBodyParser(
+                    (t) -> {
+                        try {
+                            if (t != null) {
+                                pusher.onError(t);
+                                connection.close();
+                                if (!cf.isDone())
+                                    cf.completeExceptionally(t);
+                            }
+                        } finally {
+                            // decrement the reference count on the HttpClientImpl
+                            // to allow the SelectorManager thread to exit if no
+                            // other operation is pending and the facade is no
+                            // longer referenced.
+                            client.unreference();
+                            bodyReader.onComplete(t);
+                        }
+                    }));
+                CompletableFuture<State> bodyReaderCF = bodyReader.completion();
+                asyncReceiver.subscribe(bodyReader);
+                assert bodyReaderCF != null : "parsing not started";
+                // Make sure to keep a reference to asyncReceiver from
+                // within this
+                CompletableFuture<?> trailingOp = bodyReaderCF.whenComplete((s,t) ->  {
+                    t = Utils.getCompletionCause(t);
+                    try {
+                        if (t != null) {
+                            debug.log(Level.DEBUG, () ->
+                                    "Finished reading body: " + s);
+                            assert s == State.READING_BODY;
+                        }
+                        if (t != null && !cf.isDone()) {
+                            pusher.onError(t);
+                            cf.completeExceptionally(t);
+                        }
+                    } catch (Throwable x) {
+                        // not supposed to happen
+                        asyncReceiver.onReadError(x);
+                    }
+                });
+                connection.addTrailingOperation(trailingOp);
+            } catch (Throwable t) {
+               debug.log(Level.DEBUG, () -> "Failed reading body: " + t);
+                try {
+                    if (!cf.isDone()) {
+                        pusher.onError(t);
+                        cf.completeExceptionally(t);
+                    }
+                } finally {
+                    asyncReceiver.onReadError(t);
+                }
+            }
+        });
+        return cf;
+    }
+
+
+    private void onFinished() {
+        asyncReceiver.clear();
+        if (return2Cache) {
+            Log.logTrace("Attempting to return connection to the pool: {0}", connection);
+            // TODO: need to do something here?
+            // connection.setAsyncCallbacks(null, null, null);
+
+            // don't return the connection to the cache if EOF happened.
+            debug.log(Level.DEBUG, () -> connection.getConnectionFlow()
+                                   + ": return to HTTP/1.1 pool");
+            connection.closeOrReturnToCache(eof == null ? headers : null);
+        }
+    }
+
+    HttpHeaders responseHeaders() {
+        return headers;
+    }
+
+    int responseCode() {
+        return responseCode;
+    }
+
+// ================ Support for plugging into Http1Receiver   =================
+// ============================================================================
+
+    // Callback: Error receiver: Consumer of Throwable.
+    void onReadError(Throwable t) {
+        Log.logError(t);
+        Receiver<?> receiver = receiver(readProgress);
+        if (t instanceof EOFException) {
+            debug.log(Level.DEBUG, "onReadError: received EOF");
+            eof = (EOFException) t;
+        }
+        CompletableFuture<?> cf = receiver == null ? null : receiver.completion();
+        debug.log(Level.DEBUG, () -> "onReadError: cf is "
+                + (cf == null  ? "null"
+                : (cf.isDone() ? "already completed"
+                               : "not yet completed")));
+        if (cf != null && !cf.isDone()) cf.completeExceptionally(t);
+        else { debug.log(Level.DEBUG, "onReadError", t); }
+        debug.log(Level.DEBUG, () -> "closing connection: cause is " + t);
+        connection.close();
+    }
+
+    // ========================================================================
+
+    private State advance(State previous) {
+        assert readProgress == previous;
+        switch(previous) {
+            case READING_HEADERS:
+                asyncReceiver.unsubscribe(headersReader);
+                return readProgress = State.READING_BODY;
+            case READING_BODY:
+                asyncReceiver.unsubscribe(bodyReader);
+                return readProgress = State.DONE;
+            default:
+                throw new InternalError("can't advance from " + previous);
+        }
+    }
+
+    Receiver<?> receiver(State state) {
+        switch(state) {
+            case READING_HEADERS: return headersReader;
+            case READING_BODY: return bodyReader;
+            default: return null;
+        }
+
+    }
+
+    static abstract class Receiver<T>
+            implements Http1AsyncReceiver.Http1AsyncDelegate {
+        abstract void start(T parser);
+        abstract CompletableFuture<State> completion();
+        // accepts a buffer from upstream.
+        // this should be implemented as a simple call to
+        // accept(ref, parser, cf)
+        public abstract boolean tryAsyncReceive(ByteBuffer buffer);
+        public abstract void onReadError(Throwable t);
+        // handle a byte buffer received from upstream.
+        // this method should set the value of Http1Response.buffer
+        // to ref.get() before beginning parsing.
+        abstract void handle(ByteBuffer buf, T parser,
+                             CompletableFuture<State> cf);
+        // resets this objects state so that it can be reused later on
+        // typically puts the reference to parser and completion to null
+        abstract void reset();
+
+        // accepts a byte buffer received from upstream
+        // returns true if the buffer is fully parsed and more data can
+        // be accepted, false otherwise.
+        final boolean accept(ByteBuffer buf, T parser,
+                CompletableFuture<State> cf) {
+            if (cf == null || parser == null || cf.isDone()) return false;
+            handle(buf, parser, cf);
+            return !cf.isDone();
+        }
+        public abstract void onSubscribe(AbstractSubscription s);
+        public abstract AbstractSubscription subscription();
+
+    }
+
+    // Invoked with each new ByteBuffer when reading headers...
+    final class HeadersReader extends Receiver<Http1HeaderParser> {
+        final Consumer<State> onComplete;
+        volatile Http1HeaderParser parser;
+        volatile CompletableFuture<State> cf;
+        volatile long count; // bytes parsed (for debug)
+        volatile AbstractSubscription subscription;
+
+        HeadersReader(Consumer<State> onComplete) {
+            this.onComplete = onComplete;
+        }
+
+        @Override
+        public AbstractSubscription subscription() {
+            return subscription;
+        }
+
+        @Override
+        public void onSubscribe(AbstractSubscription s) {
+            this.subscription = s;
+            s.request(1);
+        }
+
+        @Override
+        void reset() {
+            cf = null;
+            parser = null;
+            count = 0;
+            subscription = null;
+        }
+
+        // Revisit: do we need to support restarting?
+        @Override
+        final void start(Http1HeaderParser hp) {
+            count = 0;
+            cf = new MinimalFuture<>();
+            parser = hp;
+        }
+
+        @Override
+        CompletableFuture<State> completion() {
+            return cf;
+        }
+
+        @Override
+        public final boolean tryAsyncReceive(ByteBuffer ref) {
+            boolean hasDemand = subscription.demand().tryDecrement();
+            assert hasDemand;
+            boolean needsMore = accept(ref, parser, cf);
+            if (needsMore) subscription.request(1);
+            return needsMore;
+        }
+
+        @Override
+        public final void onReadError(Throwable t) {
+            Http1Response.this.onReadError(t);
+        }
+
+        @Override
+        final void handle(ByteBuffer b,
+                          Http1HeaderParser parser,
+                          CompletableFuture<State> cf) {
+            assert cf != null : "parsing not started";
+            assert parser != null : "no parser";
+            try {
+                count += b.remaining();
+                debug.log(Level.DEBUG, () -> "Sending " + b.remaining()
+                        + "/" + b.capacity() + " bytes to header parser");
+                if (parser.parse(b)) {
+                    count -= b.remaining();
+                    debug.log(Level.DEBUG, () ->
+                            "Parsing headers completed. bytes=" + count);
+                    onComplete.accept(State.READING_HEADERS);
+                    cf.complete(State.READING_HEADERS);
+                }
+            } catch (Throwable t) {
+                debug.log(Level.DEBUG,
+                        () -> "Header parser failed to handle buffer: " + t);
+                cf.completeExceptionally(t);
+            }
+        }
+    }
+
+    // Invoked with each new ByteBuffer when reading bodies...
+    final class BodyReader extends Receiver<BodyParser> {
+        final Consumer<State> onComplete;
+        volatile BodyParser parser;
+        volatile CompletableFuture<State> cf;
+        volatile AbstractSubscription subscription;
+        BodyReader(Consumer<State> onComplete) {
+            this.onComplete = onComplete;
+        }
+
+        @Override
+        void reset() {
+            parser = null;
+            cf = null;
+            subscription = null;
+        }
+
+        // Revisit: do we need to support restarting?
+        @Override
+        final void start(BodyParser parser) {
+            cf = new MinimalFuture<>();
+            this.parser = parser;
+        }
+
+        @Override
+        CompletableFuture<State> completion() {
+            return cf;
+        }
+
+        @Override
+        public final boolean tryAsyncReceive(ByteBuffer b) {
+            return accept(b, parser, cf);
+        }
+
+        @Override
+        public final void onReadError(Throwable t) {
+            Http1Response.this.onReadError(t);
+        }
+
+        @Override
+        public AbstractSubscription subscription() {
+            return subscription;
+        }
+
+        @Override
+        public void onSubscribe(AbstractSubscription s) {
+            this.subscription = s;
+            parser.onSubscribe(s);
+        }
+
+        @Override
+        final void handle(ByteBuffer b,
+                          BodyParser parser,
+                          CompletableFuture<State> cf) {
+            assert cf != null : "parsing not started";
+            assert parser != null : "no parser";
+            try {
+                debug.log(Level.DEBUG, () -> "Sending " + b.remaining()
+                        + "/" + b.capacity() + " bytes to body parser");
+                parser.accept(b);
+            } catch (Throwable t) {
+                debug.log(Level.DEBUG,
+                        () -> "Body parser failed to handle buffer: " + t);
+                if (!cf.isDone()) {
+                    cf.completeExceptionally(t);
+                }
+            }
+        }
+
+        final void onComplete(Throwable closedExceptionally) {
+            if (cf.isDone()) return;
+            if (closedExceptionally != null) {
+                cf.completeExceptionally(closedExceptionally);
+            } else {
+                onComplete.accept(State.READING_BODY);
+                cf.complete(State.READING_BODY);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "/parser=" + String.valueOf(parser);
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http2ClientImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CompletableFuture;
+
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.Utils;
+import jdk.incubator.http.internal.frame.SettingsFrame;
+import static jdk.incubator.http.internal.frame.SettingsFrame.INITIAL_WINDOW_SIZE;
+import static jdk.incubator.http.internal.frame.SettingsFrame.ENABLE_PUSH;
+import static jdk.incubator.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE;
+import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_CONCURRENT_STREAMS;
+import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_FRAME_SIZE;
+
+/**
+ *  Http2 specific aspects of HttpClientImpl
+ */
+class Http2ClientImpl {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final static System.Logger debug =
+            Utils.getDebugLogger("Http2ClientImpl"::toString, DEBUG);
+
+    private final HttpClientImpl client;
+
+    Http2ClientImpl(HttpClientImpl client) {
+        this.client = client;
+    }
+
+    /* Map key is "scheme:host:port" */
+    private final Map<String,Http2Connection> connections = new ConcurrentHashMap<>();
+
+    private final Set<String> failures = Collections.synchronizedSet(new HashSet<>());
+
+    /**
+     * When HTTP/2 requested only. The following describes the aggregate behavior including the
+     * calling code. In all cases, the HTTP2 connection cache
+     * is checked first for a suitable connection and that is returned if available.
+     * If not, a new connection is opened, except in https case when a previous negotiate failed.
+     * In that case, we want to continue using http/1.1. When a connection is to be opened and
+     * if multiple requests are sent in parallel then each will open a new connection.
+     *
+     * If negotiation/upgrade succeeds then
+     * one connection will be put in the cache and the others will be closed
+     * after the initial request completes (not strictly necessary for h2, only for h2c)
+     *
+     * If negotiate/upgrade fails, then any opened connections remain open (as http/1.1)
+     * and will be used and cached in the http/1 cache. Note, this method handles the
+     * https failure case only (by completing the CF with an ALPN exception, handled externally)
+     * The h2c upgrade is handled externally also.
+     *
+     * Specific CF behavior of this method.
+     * 1. completes with ALPN exception: h2 negotiate failed for first time. failure recorded.
+     * 2. completes with other exception: failure not recorded. Caller must handle
+     * 3. completes normally with null: no connection in cache for h2c or h2 failed previously
+     * 4. completes normally with connection: h2 or h2c connection in cache. Use it.
+     */
+    CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req) {
+        URI uri = req.uri();
+        InetSocketAddress proxy = req.proxy();
+        String key = Http2Connection.keyFor(uri, proxy);
+
+        synchronized (this) {
+            Http2Connection connection = connections.get(key);
+            if (connection != null) { // fast path if connection already exists
+                return MinimalFuture.completedFuture(connection);
+            }
+
+            if (!req.secure() || failures.contains(key)) {
+                // secure: negotiate failed before. Use http/1.1
+                // !secure: no connection available in cache. Attempt upgrade
+                return MinimalFuture.completedFuture(null);
+            }
+        }
+        return Http2Connection
+                .createAsync(req, this)
+                .whenComplete((conn, t) -> {
+                    synchronized (Http2ClientImpl.this) {
+                        if (conn != null) {
+                            offerConnection(conn);
+                        } else {
+                            Throwable cause = Utils.getCompletionCause(t);
+                            if (cause instanceof Http2Connection.ALPNException)
+                                failures.add(key);
+                        }
+                    }
+                });
+    }
+
+    /*
+     * Cache the given connection, if no connection to the same
+     * destination exists. If one exists, then we let the initial stream
+     * complete but allow it to close itself upon completion.
+     * This situation should not arise with https because the request
+     * has not been sent as part of the initial alpn negotiation
+     */
+    boolean offerConnection(Http2Connection c) {
+        String key = c.key();
+        Http2Connection c1 = connections.putIfAbsent(key, c);
+        if (c1 != null) {
+            c.setSingleStream(true);
+            return false;
+        }
+        return true;
+    }
+
+    void deleteConnection(Http2Connection c) {
+        connections.remove(c.key());
+    }
+
+    void stop() {
+        debug.log(Level.DEBUG, "stopping");
+        connections.values().forEach(this::close);
+        connections.clear();
+    }
+
+    private void close(Http2Connection h2c) {
+        try { h2c.close(); } catch (Throwable t) {}
+    }
+
+    HttpClientImpl client() {
+        return client;
+    }
+
+    /** Returns the client settings as a base64 (url) encoded string */
+    String getSettingsString() {
+        SettingsFrame sf = getClientSettings();
+        byte[] settings = sf.toByteArray(); // without the header
+        Base64.Encoder encoder = Base64.getUrlEncoder()
+                                       .withoutPadding();
+        return encoder.encodeToString(settings);
+    }
+
+    private static final int K = 1024;
+
+    private static int getParameter(String property, int min, int max, int defaultValue) {
+        int value =  Utils.getIntegerNetProperty(property, defaultValue);
+        // use default value if misconfigured
+        if (value < min || value > max) {
+            Log.logError("Property value for {0}={1} not in [{2}..{3}]: " +
+                    "using default={4}", property, value, min, max, defaultValue);
+            value = defaultValue;
+        }
+        return value;
+    }
+
+    // used for the connection window, to have a connection window size
+    // bigger than the initial stream window size.
+    int getConnectionWindowSize(SettingsFrame clientSettings) {
+        // Maximum size is 2^31-1. Don't allow window size to be less
+        // than the stream window size. HTTP/2 specify a default of 64 * K -1,
+        // but we use 2^26 by default for better performance.
+        int streamWindow = clientSettings.getParameter(INITIAL_WINDOW_SIZE);
+
+        // The default is the max between the stream window size
+        // and the connection window size.
+        int defaultValue = Math.min(Integer.MAX_VALUE,
+                Math.max(streamWindow, K*K*32));
+
+        return getParameter(
+                "jdk.httpclient.connectionWindowSize",
+                streamWindow, Integer.MAX_VALUE, defaultValue);
+    }
+
+    SettingsFrame getClientSettings() {
+        SettingsFrame frame = new SettingsFrame();
+        // default defined for HTTP/2 is 4 K, we use 16 K.
+        frame.setParameter(HEADER_TABLE_SIZE, getParameter(
+                "jdk.httpclient.hpack.maxheadertablesize",
+                0, Integer.MAX_VALUE, 16 * K));
+        // O: does not accept push streams. 1: accepts push streams.
+        frame.setParameter(ENABLE_PUSH, getParameter(
+                "jdk.httpclient.enablepush",
+                0, 1, 1));
+        // HTTP/2 recommends to set the number of concurrent streams
+        // no lower than 100. We use 100. 0 means no stream would be
+        // accepted. That would render the client to be non functional,
+        // so we won't let 0 be configured for our Http2ClientImpl.
+        frame.setParameter(MAX_CONCURRENT_STREAMS, getParameter(
+                "jdk.httpclient.maxstreams",
+                1, Integer.MAX_VALUE, 100));
+        // Maximum size is 2^31-1. Don't allow window size to be less
+        // than the minimum frame size as this is likely to be a
+        // configuration error. HTTP/2 specify a default of 64 * K -1,
+        // but we use 16 M  for better performance.
+        frame.setParameter(INITIAL_WINDOW_SIZE, getParameter(
+                "jdk.httpclient.windowsize",
+                16 * K, Integer.MAX_VALUE, 16*K*K));
+        // HTTP/2 specify a minimum size of 16 K, a maximum size of 2^24-1,
+        // and a default of 16 K. We use 16 K as default.
+        frame.setParameter(MAX_FRAME_SIZE, getParameter(
+                "jdk.httpclient.maxframesize",
+                16 * K, 16 * K * K -1, 16 * K));
+        return frame;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Http2Connection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,1290 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Flow;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.internal.HttpConnection.HttpPublisher;
+import jdk.incubator.http.internal.common.FlowTube;
+import jdk.incubator.http.internal.common.FlowTube.TubeSubscriber;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.SequentialScheduler;
+import jdk.incubator.http.internal.common.Utils;
+import jdk.incubator.http.internal.frame.ContinuationFrame;
+import jdk.incubator.http.internal.frame.DataFrame;
+import jdk.incubator.http.internal.frame.ErrorFrame;
+import jdk.incubator.http.internal.frame.FramesDecoder;
+import jdk.incubator.http.internal.frame.FramesEncoder;
+import jdk.incubator.http.internal.frame.GoAwayFrame;
+import jdk.incubator.http.internal.frame.HeaderFrame;
+import jdk.incubator.http.internal.frame.HeadersFrame;
+import jdk.incubator.http.internal.frame.Http2Frame;
+import jdk.incubator.http.internal.frame.MalformedFrame;
+import jdk.incubator.http.internal.frame.OutgoingHeaders;
+import jdk.incubator.http.internal.frame.PingFrame;
+import jdk.incubator.http.internal.frame.PushPromiseFrame;
+import jdk.incubator.http.internal.frame.ResetFrame;
+import jdk.incubator.http.internal.frame.SettingsFrame;
+import jdk.incubator.http.internal.frame.WindowUpdateFrame;
+import jdk.incubator.http.internal.hpack.Encoder;
+import jdk.incubator.http.internal.hpack.Decoder;
+import jdk.incubator.http.internal.hpack.DecodingCallback;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static jdk.incubator.http.internal.frame.SettingsFrame.*;
+
+
+/**
+ * An Http2Connection. Encapsulates the socket(channel) and any SSLEngine used
+ * over it. Contains an HttpConnection which hides the SocketChannel SSL stuff.
+ *
+ * Http2Connections belong to a Http2ClientImpl, (one of) which belongs
+ * to a HttpClientImpl.
+ *
+ * Creation cases:
+ * 1) upgraded HTTP/1.1 plain tcp connection
+ * 2) prior knowledge directly created plain tcp connection
+ * 3) directly created HTTP/2 SSL connection which uses ALPN.
+ *
+ * Sending is done by writing directly to underlying HttpConnection object which
+ * is operating in async mode. No flow control applies on output at this level
+ * and all writes are just executed as puts to an output Q belonging to HttpConnection
+ * Flow control is implemented by HTTP/2 protocol itself.
+ *
+ * Hpack header compression
+ * and outgoing stream creation is also done here, because these operations
+ * must be synchronized at the socket level. Stream objects send frames simply
+ * by placing them on the connection's output Queue. sendFrame() is called
+ * from a higher level (Stream) thread.
+ *
+ * asyncReceive(ByteBuffer) is always called from the selector thread. It assembles
+ * incoming Http2Frames, and directs them to the appropriate Stream.incoming()
+ * or handles them directly itself. This thread performs hpack decompression
+ * and incoming stream creation (Server push). Incoming frames destined for a
+ * stream are provided by calling Stream.incoming().
+ */
+class Http2Connection  {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    static final boolean DEBUG_HPACK = Utils.DEBUG_HPACK; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+    final static System.Logger  DEBUG_LOGGER =
+            Utils.getDebugLogger("Http2Connection"::toString, DEBUG);
+    private final System.Logger debugHpack =
+                  Utils.getHpackLogger(this::dbgString, DEBUG_HPACK);
+    static final ByteBuffer EMPTY_TRIGGER = ByteBuffer.allocate(0);
+
+    private boolean singleStream; // used only for stream 1, then closed
+
+    /*
+     *  ByteBuffer pooling strategy for HTTP/2 protocol:
+     *
+     * In general there are 4 points where ByteBuffers are used:
+     *  - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing encrypted data
+     *    in case of SSL connection.
+     *
+     * 1. Outgoing frames encoded to ByteBuffers.
+     *    Outgoing ByteBuffers are created with requited size and frequently small (except DataFrames, etc)
+     *    At this place no pools at all. All outgoing buffers should be collected by GC.
+     *
+     * 2. Incoming ByteBuffers (decoded to frames).
+     *    Here, total elimination of BB pool is not a good idea.
+     *    We don't know how many bytes we will receive through network.
+     * So here we allocate buffer of reasonable size. The following life of the BB:
+     * - If all frames decoded from the BB are other than DataFrame and HeaderFrame (and HeaderFrame subclasses)
+     *     BB is returned to pool,
+     * - If we decoded DataFrame from the BB. In that case DataFrame refers to subbuffer obtained by slice() method.
+     *     Such BB is never returned to pool and will be GCed.
+     * - If we decoded HeadersFrame from the BB. Then header decoding is performed inside processFrame method and
+     *     the buffer could be release to pool.
+     *
+     * 3. SLL encrypted buffers. Here another pool was introduced and all net buffers are to/from the pool,
+     *    because of we can't predict size encrypted packets.
+     *
+     */
+
+
+    // A small class that allows to control frames with respect to the state of
+    // the connection preface. Any data received before the connection
+    // preface is sent will be buffered.
+    private final class FramesController {
+        volatile boolean prefaceSent;
+        volatile List<ByteBuffer> pending;
+
+        boolean processReceivedData(FramesDecoder decoder, ByteBuffer buf)
+                throws IOException
+        {
+            // if preface is not sent, buffers data in the pending list
+            if (!prefaceSent) {
+                debug.log(Level.DEBUG, "Preface is not sent: buffering %d",
+                          buf.remaining());
+                synchronized (this) {
+                    if (!prefaceSent) {
+                        if (pending == null) pending = new ArrayList<>();
+                        pending.add(buf);
+                        debug.log(Level.DEBUG, () -> "there are now "
+                              + Utils.remaining(pending)
+                              + " bytes buffered waiting for preface to be sent");
+                        return false;
+                    }
+                }
+            }
+
+            // Preface is sent. Checks for pending data and flush it.
+            // We rely on this method being called from within the Http2TubeSubscriber
+            // scheduler, so we know that no other thread could execute this method
+            // concurrently while we're here.
+            // This ensures that later incoming buffers will not
+            // be processed before we have flushed the pending queue.
+            // No additional synchronization is therefore necessary here.
+            List<ByteBuffer> pending = this.pending;
+            this.pending = null;
+            if (pending != null) {
+                // flush pending data
+                debug.log(Level.DEBUG, () -> "Processing buffered data: "
+                      + Utils.remaining(pending));
+                for (ByteBuffer b : pending) {
+                    decoder.decode(b);
+                }
+            }
+            // push the received buffer to the frames decoder.
+            if (buf != EMPTY_TRIGGER) {
+                debug.log(Level.DEBUG, "Processing %d", buf.remaining());
+                decoder.decode(buf);
+            }
+            return true;
+        }
+
+        // Mark that the connection preface is sent
+        void markPrefaceSent() {
+            assert !prefaceSent;
+            synchronized (this) {
+                prefaceSent = true;
+            }
+        }
+    }
+
+    volatile boolean closed;
+
+    //-------------------------------------
+    final HttpConnection connection;
+    private final Http2ClientImpl client2;
+    private final Map<Integer,Stream<?>> streams = new ConcurrentHashMap<>();
+    private int nextstreamid;
+    private int nextPushStream = 2;
+    private final Encoder hpackOut;
+    private final Decoder hpackIn;
+    final SettingsFrame clientSettings;
+    private volatile SettingsFrame serverSettings;
+    private final String key; // for HttpClientImpl.connections map
+    private final FramesDecoder framesDecoder;
+    private final FramesEncoder framesEncoder = new FramesEncoder();
+
+    /**
+     * Send Window controller for both connection and stream windows.
+     * Each of this connection's Streams MUST use this controller.
+     */
+    private final WindowController windowController = new WindowController();
+    private final FramesController framesController = new FramesController();
+    private final Http2TubeSubscriber subscriber = new Http2TubeSubscriber();
+    final ConnectionWindowUpdateSender windowUpdater;
+    private volatile Throwable cause;
+    private volatile Supplier<ByteBuffer> initial;
+
+    static final int DEFAULT_FRAME_SIZE = 16 * 1024;
+
+
+    // TODO: need list of control frames from other threads
+    // that need to be sent
+
+    private Http2Connection(HttpConnection connection,
+                            Http2ClientImpl client2,
+                            int nextstreamid,
+                            String key) {
+        this.connection = connection;
+        this.client2 = client2;
+        this.nextstreamid = nextstreamid;
+        this.key = key;
+        this.clientSettings = this.client2.getClientSettings();
+        this.framesDecoder = new FramesDecoder(this::processFrame,
+                clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE));
+        // serverSettings will be updated by server
+        this.serverSettings = SettingsFrame.getDefaultSettings();
+        this.hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE));
+        this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
+        debugHpack.log(Level.DEBUG, () -> "For the record:" + super.toString());
+        debugHpack.log(Level.DEBUG, "Decoder created: %s", hpackIn);
+        debugHpack.log(Level.DEBUG, "Encoder created: %s", hpackOut);
+        this.windowUpdater = new ConnectionWindowUpdateSender(this,
+                client2.getConnectionWindowSize(clientSettings));
+    }
+
+    /**
+     * Case 1) Create from upgraded HTTP/1.1 connection.
+     * Is ready to use. Can be SSL. exchange is the Exchange
+     * that initiated the connection, whose response will be delivered
+     * on a Stream.
+     */
+    private Http2Connection(HttpConnection connection,
+                    Http2ClientImpl client2,
+                    Exchange<?> exchange,
+                    Supplier<ByteBuffer> initial)
+        throws IOException, InterruptedException
+    {
+        this(connection,
+                client2,
+                3, // stream 1 is registered during the upgrade
+                keyFor(connection));
+        Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
+
+        Stream<?> initialStream = createStream(exchange);
+        initialStream.registerStream(1);
+        windowController.registerStream(1, getInitialSendWindowSize());
+        initialStream.requestSent();
+        // Upgrading:
+        //    set callbacks before sending preface - makes sure anything that
+        //    might be sent by the server will come our way.
+        this.initial = initial;
+        connectFlows(connection);
+        sendConnectionPreface();
+    }
+
+    // Used when upgrading an HTTP/1.1 connection to HTTP/2 after receiving
+    // agreement from the server. Async style but completes immediately, because
+    // the connection is already connected.
+    static CompletableFuture<Http2Connection> createAsync(HttpConnection connection,
+                                                          Http2ClientImpl client2,
+                                                          Exchange<?> exchange,
+                                                          Supplier<ByteBuffer> initial)
+    {
+        return MinimalFuture.supply(() -> new Http2Connection(connection, client2, exchange, initial));
+    }
+
+    // Requires TLS handshake. So, is really async
+    static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,
+                                                          Http2ClientImpl h2client) {
+        assert request.secure();
+        AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)
+        HttpConnection.getConnection(request.getAddress(),
+                                     h2client.client(),
+                                     request,
+                                     HttpClient.Version.HTTP_2);
+
+        return connection.connectAsync()
+                  .thenCompose(unused -> checkSSLConfig(connection))
+                  .thenCompose(notused-> {
+                      CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
+                      try {
+                          Http2Connection hc = new Http2Connection(request, h2client, connection);
+                          cf.complete(hc);
+                      } catch (IOException e) {
+                          cf.completeExceptionally(e);
+                      }
+                      return cf; } );
+    }
+
+    /**
+     * Cases 2) 3)
+     *
+     * request is request to be sent.
+     */
+    private Http2Connection(HttpRequestImpl request,
+                            Http2ClientImpl h2client,
+                            HttpConnection connection)
+        throws IOException
+    {
+        this(connection,
+             h2client,
+             1,
+             keyFor(request.uri(), request.proxy()));
+
+        Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
+
+        // safe to resume async reading now.
+        connectFlows(connection);
+        sendConnectionPreface();
+    }
+
+    private void connectFlows(HttpConnection connection) {
+        FlowTube tube =  connection.getConnectionFlow();
+        // Connect the flow to our Http2TubeSubscriber:
+        tube.connectFlows(connection.publisher(), subscriber);
+    }
+
+    final HttpClientImpl client() {
+        return client2.client();
+    }
+
+    /**
+     * Throws an IOException if h2 was not negotiated
+     */
+    private static CompletableFuture<?> checkSSLConfig(AbstractAsyncSSLConnection aconn) {
+        assert aconn.isSecure();
+
+        Function<String, CompletableFuture<Void>> checkAlpnCF = (alpn) -> {
+            CompletableFuture<Void> cf = new MinimalFuture<>();
+            SSLEngine engine = aconn.getEngine();
+            assert Objects.equals(alpn, engine.getApplicationProtocol());
+
+            DEBUG_LOGGER.log(Level.DEBUG, "checkSSLConfig: alpn: %s", alpn );
+
+            if (alpn == null || !alpn.equals("h2")) {
+                String msg;
+                if (alpn == null) {
+                    Log.logSSL("ALPN not supported");
+                    msg = "ALPN not supported";
+                } else {
+                    switch (alpn) {
+                        case "":
+                            Log.logSSL(msg = "No ALPN negotiated");
+                            break;
+                        case "http/1.1":
+                            Log.logSSL( msg = "HTTP/1.1 ALPN returned");
+                            break;
+                        default:
+                            Log.logSSL(msg = "Unexpected ALPN: " + alpn);
+                            cf.completeExceptionally(new IOException(msg));
+                    }
+                }
+                cf.completeExceptionally(new ALPNException(msg, aconn));
+                return cf;
+            }
+            cf.complete(null);
+            return cf;
+        };
+
+        return aconn.getALPN()
+                .whenComplete((r,t) -> {
+                    if (t != null && t instanceof SSLException) {
+                        // something went wrong during the initial handshake
+                        // close the connection
+                        aconn.close();
+                    }
+                })
+                .thenCompose(checkAlpnCF);
+    }
+
+    synchronized boolean singleStream() {
+        return singleStream;
+    }
+
+    synchronized void setSingleStream(boolean use) {
+        singleStream = use;
+    }
+
+    static String keyFor(HttpConnection connection) {
+        boolean isProxy = connection.isProxied();
+        boolean isSecure = connection.isSecure();
+        InetSocketAddress addr = connection.address();
+
+        return keyString(isSecure, isProxy, addr.getHostString(), addr.getPort());
+    }
+
+    static String keyFor(URI uri, InetSocketAddress proxy) {
+        boolean isSecure = uri.getScheme().equalsIgnoreCase("https");
+        boolean isProxy = proxy != null;
+
+        String host;
+        int port;
+
+        if (proxy != null) {
+            host = proxy.getHostString();
+            port = proxy.getPort();
+        } else {
+            host = uri.getHost();
+            port = uri.getPort();
+        }
+        return keyString(isSecure, isProxy, host, port);
+    }
+
+    // {C,S}:{H:P}:host:port
+    // C indicates clear text connection "http"
+    // S indicates secure "https"
+    // H indicates host (direct) connection
+    // P indicates proxy
+    // Eg: "S:H:foo.com:80"
+    static String keyString(boolean secure, boolean proxy, String host, int port) {
+        if (secure && port == -1)
+            port = 443;
+        else if (!secure && port == -1)
+            port = 80;
+        return (secure ? "S:" : "C:") + (proxy ? "P:" : "H:") + host + ":" + port;
+    }
+
+    String key() {
+        return this.key;
+    }
+
+    boolean offerConnection() {
+        return client2.offerConnection(this);
+    }
+
+    private HttpPublisher publisher() {
+        return connection.publisher();
+    }
+
+    private void decodeHeaders(HeaderFrame frame, DecodingCallback decoder)
+            throws IOException
+    {
+        debugHpack.log(Level.DEBUG, "decodeHeaders(%s)", decoder);
+
+        boolean endOfHeaders = frame.getFlag(HeaderFrame.END_HEADERS);
+
+        List<ByteBuffer> buffers = frame.getHeaderBlock();
+        int len = buffers.size();
+        for (int i = 0; i < len; i++) {
+            ByteBuffer b = buffers.get(i);
+            hpackIn.decode(b, endOfHeaders && (i == len - 1), decoder);
+        }
+    }
+
+    final int getInitialSendWindowSize() {
+        return serverSettings.getParameter(INITIAL_WINDOW_SIZE);
+    }
+
+    void close() {
+        Log.logTrace("Closing HTTP/2 connection: to {0}", connection.address());
+        GoAwayFrame f = new GoAwayFrame(0,
+                                        ErrorFrame.NO_ERROR,
+                                        "Requested by user".getBytes(UTF_8));
+        // TODO: set last stream. For now zero ok.
+        sendFrame(f);
+    }
+
+    long count;
+    final void asyncReceive(ByteBuffer buffer) {
+        // We don't need to read anything and
+        // we don't want to send anything back to the server
+        // until the connection preface has been sent.
+        // Therefore we're going to wait if needed before reading
+        // (and thus replying) to anything.
+        // Starting to reply to something (e.g send an ACK to a
+        // SettingsFrame sent by the server) before the connection
+        // preface is fully sent might result in the server
+        // sending a GOAWAY frame with 'invalid_preface'.
+        //
+        // Note: asyncReceive is only called from the Http2TubeSubscriber
+        //       sequential scheduler.
+        try {
+            Supplier<ByteBuffer> bs = initial;
+            // ensure that we always handle the initial buffer first,
+            // if any.
+            if (bs != null) {
+                initial = null;
+                ByteBuffer b = bs.get();
+                if (b.hasRemaining()) {
+                    long c = ++count;
+                    debug.log(Level.DEBUG, () -> "H2 Receiving Initial("
+                        + c +"): " + b.remaining());
+                    framesController.processReceivedData(framesDecoder, b);
+                }
+            }
+            ByteBuffer b = buffer;
+            // the Http2TubeSubscriber scheduler ensures that the order of incoming
+            // buffers is preserved.
+            if (b == EMPTY_TRIGGER) {
+                debug.log(Level.DEBUG, "H2 Received EMPTY_TRIGGER");
+                boolean prefaceSent = framesController.prefaceSent;
+                assert prefaceSent;
+                // call framesController.processReceivedData to potentially
+                // trigger the processing of all the data buffered there.
+                framesController.processReceivedData(framesDecoder, buffer);
+                debug.log(Level.DEBUG, "H2 processed buffered data");
+            } else {
+                long c = ++count;
+                debug.log(Level.DEBUG, "H2 Receiving(%d): %d", c, b.remaining());
+                framesController.processReceivedData(framesDecoder, buffer);
+                debug.log(Level.DEBUG, "H2 processed(%d)", c);
+            }
+        } catch (Throwable e) {
+            String msg = Utils.stackTrace(e);
+            Log.logTrace(msg);
+            shutdown(e);
+        }
+    }
+
+    Throwable getRecordedCause() {
+        return cause;
+    }
+
+    void shutdown(Throwable t) {
+        debug.log(Level.DEBUG, () -> "Shutting down h2c (closed="+closed+"): " + t);
+        if (closed == true) return;
+        synchronized (this) {
+            if (closed == true) return;
+            closed = true;
+        }
+        Log.logError(t);
+        Throwable initialCause = this.cause;
+        if (initialCause == null) this.cause = t;
+        client2.deleteConnection(this);
+        List<Stream<?>> c = new LinkedList<>(streams.values());
+        for (Stream<?> s : c) {
+            s.cancelImpl(t);
+        }
+        connection.close();
+    }
+
+    /**
+     * Streams initiated by a client MUST use odd-numbered stream
+     * identifiers; those initiated by the server MUST use even-numbered
+     * stream identifiers.
+     */
+    private static final boolean isSeverInitiatedStream(int streamid) {
+        return (streamid & 0x1) == 0;
+    }
+
+    /**
+     * Handles stream 0 (common) frames that apply to whole connection and passes
+     * other stream specific frames to that Stream object.
+     *
+     * Invokes Stream.incoming() which is expected to process frame without
+     * blocking.
+     */
+    void processFrame(Http2Frame frame) throws IOException {
+        Log.logFrames(frame, "IN");
+        int streamid = frame.streamid();
+        if (frame instanceof MalformedFrame) {
+            Log.logError(((MalformedFrame) frame).getMessage());
+            if (streamid == 0) {
+                framesDecoder.close("Malformed frame on stream 0");
+                protocolError(((MalformedFrame) frame).getErrorCode(),
+                        ((MalformedFrame) frame).getMessage());
+            } else {
+                debug.log(Level.DEBUG, () -> "Reset stream: "
+                          + ((MalformedFrame) frame).getMessage());
+                resetStream(streamid, ((MalformedFrame) frame).getErrorCode());
+            }
+            return;
+        }
+        if (streamid == 0) {
+            handleConnectionFrame(frame);
+        } else {
+            if (frame instanceof SettingsFrame) {
+                // The stream identifier for a SETTINGS frame MUST be zero
+                framesDecoder.close(
+                        "The stream identifier for a SETTINGS frame MUST be zero");
+                protocolError(GoAwayFrame.PROTOCOL_ERROR);
+                return;
+            }
+
+            Stream<?> stream = getStream(streamid);
+            if (stream == null) {
+                // Should never receive a frame with unknown stream id
+
+                if (frame instanceof HeaderFrame) {
+                    // always decode the headers as they may affect
+                    // connection-level HPACK decoding state
+                    HeaderDecoder decoder = new LoggingHeaderDecoder(new HeaderDecoder());
+                    decodeHeaders((HeaderFrame) frame, decoder);
+                }
+
+                if (!(frame instanceof ResetFrame)) {
+                    if (isSeverInitiatedStream(streamid)) {
+                        if (streamid < nextPushStream) {
+                            // trailing data on a cancelled push promise stream,
+                            // reset will already have been sent, ignore
+                            Log.logTrace("Ignoring cancelled push promise frame " + frame);
+                        } else {
+                            resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
+                        }
+                    } else if (streamid >= nextstreamid) {
+                        // otherwise the stream has already been reset/closed
+                        resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
+                    }
+                }
+                return;
+            }
+            if (frame instanceof PushPromiseFrame) {
+                PushPromiseFrame pp = (PushPromiseFrame)frame;
+                handlePushPromise(stream, pp);
+            } else if (frame instanceof HeaderFrame) {
+                // decode headers (or continuation)
+                decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer());
+                stream.incoming(frame);
+            } else {
+                stream.incoming(frame);
+            }
+        }
+    }
+
+    private <T> void handlePushPromise(Stream<T> parent, PushPromiseFrame pp)
+        throws IOException
+    {
+        // always decode the headers as they may affect connection-level HPACK
+        // decoding state
+        HeaderDecoder decoder = new LoggingHeaderDecoder(new HeaderDecoder());
+        decodeHeaders(pp, decoder);
+
+        HttpRequestImpl parentReq = parent.request;
+        int promisedStreamid = pp.getPromisedStream();
+        if (promisedStreamid != nextPushStream) {
+            resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);
+            return;
+        } else {
+            nextPushStream += 2;
+        }
+
+        HttpHeadersImpl headers = decoder.headers();
+        HttpRequestImpl pushReq = HttpRequestImpl.createPushRequest(parentReq, headers);
+        Exchange<T> pushExch = new Exchange<>(pushReq, parent.exchange.multi);
+        Stream.PushedStream<T> pushStream = createPushStream(parent, pushExch);
+        pushExch.exchImpl = pushStream;
+        pushStream.registerStream(promisedStreamid);
+        parent.incoming_pushPromise(pushReq, pushStream);
+    }
+
+    private void handleConnectionFrame(Http2Frame frame)
+        throws IOException
+    {
+        switch (frame.type()) {
+          case SettingsFrame.TYPE:
+              handleSettings((SettingsFrame)frame);
+              break;
+          case PingFrame.TYPE:
+              handlePing((PingFrame)frame);
+              break;
+          case GoAwayFrame.TYPE:
+              handleGoAway((GoAwayFrame)frame);
+              break;
+          case WindowUpdateFrame.TYPE:
+              handleWindowUpdate((WindowUpdateFrame)frame);
+              break;
+          default:
+            protocolError(ErrorFrame.PROTOCOL_ERROR);
+        }
+    }
+
+    void resetStream(int streamid, int code) throws IOException {
+        Log.logError(
+            "Resetting stream {0,number,integer} with error code {1,number,integer}",
+            streamid, code);
+        ResetFrame frame = new ResetFrame(streamid, code);
+        sendFrame(frame);
+        closeStream(streamid);
+    }
+
+    void closeStream(int streamid) {
+        debug.log(Level.DEBUG, "Closed stream %d", streamid);
+        Stream<?> s = streams.remove(streamid);
+        if (s != null) {
+            // decrement the reference count on the HttpClientImpl
+            // to allow the SelectorManager thread to exit if no
+            // other operation is pending and the facade is no
+            // longer referenced.
+            client().unreference();
+        }
+        // ## Remove s != null. It is a hack for delayed cancellation,reset
+        if (s != null && !(s instanceof Stream.PushedStream)) {
+            // Since PushStreams have no request body, then they have no
+            // corresponding entry in the window controller.
+            windowController.removeStream(streamid);
+        }
+        if (singleStream() && streams.isEmpty()) {
+            // should be only 1 stream, but there might be more if server push
+            close();
+        }
+    }
+
+    /**
+     * Increments this connection's send Window by the amount in the given frame.
+     */
+    private void handleWindowUpdate(WindowUpdateFrame f)
+        throws IOException
+    {
+        int amount = f.getUpdate();
+        if (amount <= 0) {
+            // ## temporarily disable to workaround a bug in Jetty where it
+            // ## sends Window updates with a 0 update value.
+            //protocolError(ErrorFrame.PROTOCOL_ERROR);
+        } else {
+            boolean success = windowController.increaseConnectionWindow(amount);
+            if (!success) {
+                protocolError(ErrorFrame.FLOW_CONTROL_ERROR);  // overflow
+            }
+        }
+    }
+
+    private void protocolError(int errorCode)
+        throws IOException
+    {
+        protocolError(errorCode, null);
+    }
+
+    private void protocolError(int errorCode, String msg)
+        throws IOException
+    {
+        GoAwayFrame frame = new GoAwayFrame(0, errorCode);
+        sendFrame(frame);
+        shutdown(new IOException("protocol error" + (msg == null?"":(": " + msg))));
+    }
+
+    private void handleSettings(SettingsFrame frame)
+        throws IOException
+    {
+        assert frame.streamid() == 0;
+        if (!frame.getFlag(SettingsFrame.ACK)) {
+            int oldWindowSize = serverSettings.getParameter(INITIAL_WINDOW_SIZE);
+            int newWindowSize = frame.getParameter(INITIAL_WINDOW_SIZE);
+            int diff = newWindowSize - oldWindowSize;
+            if (diff != 0) {
+                windowController.adjustActiveStreams(diff);
+            }
+            serverSettings = frame;
+            sendFrame(new SettingsFrame(SettingsFrame.ACK));
+        }
+    }
+
+    private void handlePing(PingFrame frame)
+        throws IOException
+    {
+        frame.setFlag(PingFrame.ACK);
+        sendUnorderedFrame(frame);
+    }
+
+    private void handleGoAway(GoAwayFrame frame)
+        throws IOException
+    {
+        shutdown(new IOException(
+                        String.valueOf(connection.channel().getLocalAddress())
+                        +": GOAWAY received"));
+    }
+
+    /**
+     * Max frame size we are allowed to send
+     */
+    public int getMaxSendFrameSize() {
+        int param = serverSettings.getParameter(MAX_FRAME_SIZE);
+        if (param == -1) {
+            param = DEFAULT_FRAME_SIZE;
+        }
+        return param;
+    }
+
+    /**
+     * Max frame size we will receive
+     */
+    public int getMaxReceiveFrameSize() {
+        return clientSettings.getParameter(MAX_FRAME_SIZE);
+    }
+
+    private static final String CLIENT_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+    private static final byte[] PREFACE_BYTES =
+        CLIENT_PREFACE.getBytes(StandardCharsets.ISO_8859_1);
+
+    /**
+     * Sends Connection preface and Settings frame with current preferred
+     * values
+     */
+    private void sendConnectionPreface() throws IOException {
+        Log.logTrace("{0}: start sending connection preface to {1}",
+                     connection.channel().getLocalAddress(),
+                     connection.address());
+        SettingsFrame sf = new SettingsFrame(clientSettings);
+        int initialWindowSize = sf.getParameter(INITIAL_WINDOW_SIZE);
+        ByteBuffer buf = framesEncoder.encodeConnectionPreface(PREFACE_BYTES, sf);
+        Log.logFrames(sf, "OUT");
+        // send preface bytes and SettingsFrame together
+        HttpPublisher publisher = publisher();
+        publisher.enqueue(List.of(buf));
+        publisher.signalEnqueued();
+        // mark preface sent.
+        framesController.markPrefaceSent();
+        Log.logTrace("PREFACE_BYTES sent");
+        Log.logTrace("Settings Frame sent");
+
+        // send a Window update for the receive buffer we are using
+        // minus the initial 64 K specified in protocol
+        final int len = windowUpdater.initialWindowSize - initialWindowSize;
+        if (len > 0) {
+            windowUpdater.sendWindowUpdate(len);
+        }
+        // there will be an ACK to the windows update - which should
+        // cause any pending data stored before the preface was sent to be
+        // flushed (see PrefaceController).
+        Log.logTrace("finished sending connection preface");
+        debug.log(Level.DEBUG, "Triggering processing of buffered data"
+                  + " after sending connection preface");
+        subscriber.onNext(List.of(EMPTY_TRIGGER));
+    }
+
+    /**
+     * Returns an existing Stream with given id, or null if doesn't exist
+     */
+    @SuppressWarnings("unchecked")
+    <T> Stream<T> getStream(int streamid) {
+        return (Stream<T>)streams.get(streamid);
+    }
+
+    /**
+     * Creates Stream with given id.
+     */
+    final <T> Stream<T> createStream(Exchange<T> exchange) {
+        Stream<T> stream = new Stream<>(this, exchange, windowController);
+        return stream;
+    }
+
+    <T> Stream.PushedStream<T> createPushStream(Stream<T> parent, Exchange<T> pushEx) {
+        PushGroup<T> pg = parent.exchange.getPushGroup();
+        return new Stream.PushedStream<>(pg, this, pushEx);
+    }
+
+    <T> void putStream(Stream<T> stream, int streamid) {
+        // increment the reference count on the HttpClientImpl
+        // to prevent the SelectorManager thread from exiting until
+        // the stream is closed.
+        client().reference();
+        streams.put(streamid, stream);
+    }
+
+    /**
+     * Encode the headers into a List<ByteBuffer> and then create HEADERS
+     * and CONTINUATION frames from the list and return the List<Http2Frame>.
+     */
+    private List<HeaderFrame> encodeHeaders(OutgoingHeaders<Stream<?>> frame) {
+        List<ByteBuffer> buffers = encodeHeadersImpl(
+                getMaxSendFrameSize(),
+                frame.getAttachment().getRequestPseudoHeaders(),
+                frame.getUserHeaders(),
+                frame.getSystemHeaders());
+
+        List<HeaderFrame> frames = new ArrayList<>(buffers.size());
+        Iterator<ByteBuffer> bufIterator = buffers.iterator();
+        HeaderFrame oframe = new HeadersFrame(frame.streamid(), frame.getFlags(), bufIterator.next());
+        frames.add(oframe);
+        while(bufIterator.hasNext()) {
+            oframe = new ContinuationFrame(frame.streamid(), bufIterator.next());
+            frames.add(oframe);
+        }
+        oframe.setFlag(HeaderFrame.END_HEADERS);
+        return frames;
+    }
+
+    // Dedicated cache for headers encoding ByteBuffer.
+    // There can be no concurrent access to this  buffer as all access to this buffer
+    // and its content happen within a single critical code block section protected
+    // by the sendLock. / (see sendFrame())
+    // private final ByteBufferPool headerEncodingPool = new ByteBufferPool();
+
+    private ByteBuffer getHeaderBuffer(int maxFrameSize) {
+        ByteBuffer buf = ByteBuffer.allocate(maxFrameSize);
+        buf.limit(maxFrameSize);
+        return buf;
+    }
+
+    /*
+     * Encodes all the headers from the given HttpHeaders into the given List
+     * of buffers.
+     *
+     * From https://tools.ietf.org/html/rfc7540#section-8.1.2 :
+     *
+     *     ...Just as in HTTP/1.x, header field names are strings of ASCII
+     *     characters that are compared in a case-insensitive fashion.  However,
+     *     header field names MUST be converted to lowercase prior to their
+     *     encoding in HTTP/2...
+     */
+    private List<ByteBuffer> encodeHeadersImpl(int maxFrameSize, HttpHeaders... headers) {
+        ByteBuffer buffer = getHeaderBuffer(maxFrameSize);
+        List<ByteBuffer> buffers = new ArrayList<>();
+        for(HttpHeaders header : headers) {
+            for (Map.Entry<String, List<String>> e : header.map().entrySet()) {
+                String lKey = e.getKey().toLowerCase();
+                List<String> values = e.getValue();
+                for (String value : values) {
+                    hpackOut.header(lKey, value);
+                    while (!hpackOut.encode(buffer)) {
+                        buffer.flip();
+                        buffers.add(buffer);
+                        buffer =  getHeaderBuffer(maxFrameSize);
+                    }
+                }
+            }
+        }
+        buffer.flip();
+        buffers.add(buffer);
+        return buffers;
+    }
+
+    private List<ByteBuffer> encodeHeaders(OutgoingHeaders<Stream<?>> oh, Stream<?> stream) {
+        oh.streamid(stream.streamid);
+        if (Log.headers()) {
+            StringBuilder sb = new StringBuilder("HEADERS FRAME (stream=");
+            sb.append(stream.streamid).append(")\n");
+            Log.dumpHeaders(sb, "    ", oh.getAttachment().getRequestPseudoHeaders());
+            Log.dumpHeaders(sb, "    ", oh.getSystemHeaders());
+            Log.dumpHeaders(sb, "    ", oh.getUserHeaders());
+            Log.logHeaders(sb.toString());
+        }
+        List<HeaderFrame> frames = encodeHeaders(oh);
+        return encodeFrames(frames);
+    }
+
+    private List<ByteBuffer> encodeFrames(List<HeaderFrame> frames) {
+        if (Log.frames()) {
+            frames.forEach(f -> Log.logFrames(f, "OUT"));
+        }
+        return framesEncoder.encodeFrames(frames);
+    }
+
+    private Stream<?> registerNewStream(OutgoingHeaders<Stream<?>> oh) {
+        Stream<?> stream = oh.getAttachment();
+        int streamid = nextstreamid;
+        nextstreamid += 2;
+        stream.registerStream(streamid);
+        // set outgoing window here. This allows thread sending
+        // body to proceed.
+        windowController.registerStream(streamid, getInitialSendWindowSize());
+        return stream;
+    }
+
+    private final Object sendlock = new Object();
+
+    void sendFrame(Http2Frame frame) {
+        try {
+            HttpPublisher publisher = publisher();
+            synchronized (sendlock) {
+                if (frame instanceof OutgoingHeaders) {
+                    @SuppressWarnings("unchecked")
+                    OutgoingHeaders<Stream<?>> oh = (OutgoingHeaders<Stream<?>>) frame;
+                    Stream<?> stream = registerNewStream(oh);
+                    // provide protection from inserting unordered frames between Headers and Continuation
+                    publisher.enqueue(encodeHeaders(oh, stream));
+                } else {
+                    publisher.enqueue(encodeFrame(frame));
+                }
+            }
+            publisher.signalEnqueued();
+        } catch (IOException e) {
+            if (!closed) {
+                Log.logError(e);
+                shutdown(e);
+            }
+        }
+    }
+
+    private List<ByteBuffer> encodeFrame(Http2Frame frame) {
+        Log.logFrames(frame, "OUT");
+        return framesEncoder.encodeFrame(frame);
+    }
+
+    void sendDataFrame(DataFrame frame) {
+        try {
+            HttpPublisher publisher = publisher();
+            publisher.enqueue(encodeFrame(frame));
+            publisher.signalEnqueued();
+        } catch (IOException e) {
+            if (!closed) {
+                Log.logError(e);
+                shutdown(e);
+            }
+        }
+    }
+
+    /*
+     * Direct call of the method bypasses synchronization on "sendlock" and
+     * allowed only of control frames: WindowUpdateFrame, PingFrame and etc.
+     * prohibited for such frames as DataFrame, HeadersFrame, ContinuationFrame.
+     */
+    void sendUnorderedFrame(Http2Frame frame) {
+        try {
+            HttpPublisher publisher = publisher();
+            publisher.enqueueUnordered(encodeFrame(frame));
+            publisher.signalEnqueued();
+        } catch (IOException e) {
+            if (!closed) {
+                Log.logError(e);
+                shutdown(e);
+            }
+        }
+    }
+
+    /**
+     * A simple tube subscriber for reading from the connection flow.
+     */
+    final class Http2TubeSubscriber implements TubeSubscriber {
+        volatile Flow.Subscription subscription;
+        volatile boolean completed;
+        volatile boolean dropped;
+        volatile Throwable error;
+        final ConcurrentLinkedQueue<ByteBuffer> queue
+                = new ConcurrentLinkedQueue<>();
+        final SequentialScheduler scheduler =
+                SequentialScheduler.synchronizedScheduler(this::processQueue);
+
+        final void processQueue() {
+            try {
+                while (!queue.isEmpty() && !scheduler.isStopped()) {
+                    ByteBuffer buffer = queue.poll();
+                    debug.log(Level.DEBUG,
+                              "sending %d to Http2Connection.asyncReceive",
+                              buffer.remaining());
+                    asyncReceive(buffer);
+                }
+            } catch (Throwable t) {
+                Throwable x = error;
+                if (x == null) error = t;
+            } finally {
+                Throwable x = error;
+                if (x != null) {
+                    debug.log(Level.DEBUG, "Stopping scheduler", x);
+                    scheduler.stop();
+                    Http2Connection.this.shutdown(x);
+                }
+            }
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            // supports being called multiple time.
+            // doesn't cancel the previous subscription, since that is
+            // most probably the same as the new subscription.
+            assert this.subscription == null || dropped == false;
+            this.subscription = subscription;
+            dropped = false;
+            // TODO FIXME: request(1) should be done by the delegate.
+            if (!completed) {
+                debug.log(Level.DEBUG, "onSubscribe: requesting Long.MAX_VALUE for reading");
+                subscription.request(Long.MAX_VALUE);
+            } else {
+                debug.log(Level.DEBUG, "onSubscribe: already completed");
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            debug.log(Level.DEBUG, () -> "onNext: got " + Utils.remaining(item)
+                    + " bytes in " + item.size() + " buffers");
+            queue.addAll(item);
+            scheduler.runOrSchedule(client().theExecutor());
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            debug.log(Level.DEBUG, () -> "onError: " + throwable);
+            error = throwable;
+            completed = true;
+            scheduler.runOrSchedule(client().theExecutor());
+        }
+
+        @Override
+        public void onComplete() {
+            debug.log(Level.DEBUG, "EOF");
+            error = new EOFException("EOF reached while reading");
+            completed = true;
+            scheduler.runOrSchedule(client().theExecutor());
+        }
+
+        @Override
+        public void dropSubscription() {
+            debug.log(Level.DEBUG, "dropSubscription");
+            // we could probably set subscription to null here...
+            // then we might not need the 'dropped' boolean?
+            dropped = true;
+        }
+    }
+
+    @Override
+    public final String toString() {
+        return dbgString();
+    }
+
+    final String dbgString() {
+        return "Http2Connection("
+                    + connection.getConnectionFlow() + ")";
+    }
+
+    final class LoggingHeaderDecoder extends HeaderDecoder {
+
+        private final HeaderDecoder delegate;
+        private final System.Logger debugHpack =
+                Utils.getHpackLogger(this::dbgString, DEBUG_HPACK);
+
+        LoggingHeaderDecoder(HeaderDecoder delegate) {
+            this.delegate = delegate;
+        }
+
+        String dbgString() {
+            return Http2Connection.this.dbgString() + "/LoggingHeaderDecoder";
+        }
+
+        @Override
+        public void onDecoded(CharSequence name, CharSequence value) {
+            delegate.onDecoded(name, value);
+        }
+
+        @Override
+        public void onIndexed(int index,
+                              CharSequence name,
+                              CharSequence value) {
+            debugHpack.log(Level.DEBUG, "onIndexed(%s, %s, %s)%n",
+                           index, name, value);
+            delegate.onIndexed(index, name, value);
+        }
+
+        @Override
+        public void onLiteral(int index,
+                              CharSequence name,
+                              CharSequence value,
+                              boolean valueHuffman) {
+            debugHpack.log(Level.DEBUG, "onLiteral(%s, %s, %s, %s)%n",
+                              index, name, value, valueHuffman);
+            delegate.onLiteral(index, name, value, valueHuffman);
+        }
+
+        @Override
+        public void onLiteral(CharSequence name,
+                              boolean nameHuffman,
+                              CharSequence value,
+                              boolean valueHuffman) {
+            debugHpack.log(Level.DEBUG, "onLiteral(%s, %s, %s, %s)%n",
+                           name, nameHuffman, value, valueHuffman);
+            delegate.onLiteral(name, nameHuffman, value, valueHuffman);
+        }
+
+        @Override
+        public void onLiteralNeverIndexed(int index,
+                                          CharSequence name,
+                                          CharSequence value,
+                                          boolean valueHuffman) {
+            debugHpack.log(Level.DEBUG, "onLiteralNeverIndexed(%s, %s, %s, %s)%n",
+                           index, name, value, valueHuffman);
+            delegate.onLiteralNeverIndexed(index, name, value, valueHuffman);
+        }
+
+        @Override
+        public void onLiteralNeverIndexed(CharSequence name,
+                                          boolean nameHuffman,
+                                          CharSequence value,
+                                          boolean valueHuffman) {
+            debugHpack.log(Level.DEBUG, "onLiteralNeverIndexed(%s, %s, %s, %s)%n",
+                           name, nameHuffman, value, valueHuffman);
+            delegate.onLiteralNeverIndexed(name, nameHuffman, value, valueHuffman);
+        }
+
+        @Override
+        public void onLiteralWithIndexing(int index,
+                                          CharSequence name,
+                                          CharSequence value,
+                                          boolean valueHuffman) {
+            debugHpack.log(Level.DEBUG, "onLiteralWithIndexing(%s, %s, %s, %s)%n",
+                           index, name, value, valueHuffman);
+            delegate.onLiteralWithIndexing(index, name, value, valueHuffman);
+        }
+
+        @Override
+        public void onLiteralWithIndexing(CharSequence name,
+                                          boolean nameHuffman,
+                                          CharSequence value,
+                                          boolean valueHuffman) {
+            debugHpack.log(Level.DEBUG, "onLiteralWithIndexing(%s, %s, %s, %s)%n",
+                              name, nameHuffman, value, valueHuffman);
+            delegate.onLiteralWithIndexing(name, nameHuffman, value, valueHuffman);
+        }
+
+        @Override
+        public void onSizeUpdate(int capacity) {
+            debugHpack.log(Level.DEBUG, "onSizeUpdate(%s)%n", capacity);
+            delegate.onSizeUpdate(capacity);
+        }
+
+        @Override
+        HttpHeadersImpl headers() {
+            return delegate.headers();
+        }
+    }
+
+    static class HeaderDecoder implements DecodingCallback {
+        HttpHeadersImpl headers;
+
+        HeaderDecoder() {
+            this.headers = new HttpHeadersImpl();
+        }
+
+        @Override
+        public void onDecoded(CharSequence name, CharSequence value) {
+            headers.addHeader(name.toString(), value.toString());
+        }
+
+        HttpHeadersImpl headers() {
+            return headers;
+        }
+    }
+
+    static final class ConnectionWindowUpdateSender extends WindowUpdateSender {
+
+        final int initialWindowSize;
+        public ConnectionWindowUpdateSender(Http2Connection connection,
+                                            int initialWindowSize) {
+            super(connection, initialWindowSize);
+            this.initialWindowSize = initialWindowSize;
+        }
+
+        @Override
+        int getStreamId() {
+            return 0;
+        }
+    }
+
+    /**
+     * Thrown when https handshake negotiates http/1.1 alpn instead of h2
+     */
+    static final class ALPNException extends IOException {
+        private static final long serialVersionUID = 0L;
+        final transient AbstractAsyncSSLConnection connection;
+
+        ALPNException(String msg, AbstractAsyncSSLConnection connection) {
+            super(msg);
+            this.connection = connection;
+        }
+
+        AbstractAsyncSSLConnection getConnection() {
+            return connection;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpClientBuilderImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.ProxySelector;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.internal.common.Utils;
+import static java.util.Objects.requireNonNull;
+
+public class HttpClientBuilderImpl extends HttpClient.Builder {
+
+    CookieHandler cookieHandler;
+    HttpClient.Redirect followRedirects;
+    ProxySelector proxy;
+    Authenticator authenticator;
+    HttpClient.Version version;
+    Executor executor;
+    // Security parameters
+    SSLContext sslContext;
+    SSLParameters sslParams;
+    int priority = -1;
+
+    @Override
+    public HttpClientBuilderImpl cookieHandler(CookieHandler cookieHandler) {
+        requireNonNull(cookieHandler);
+        this.cookieHandler = cookieHandler;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl sslContext(SSLContext sslContext) {
+        requireNonNull(sslContext);
+        this.sslContext = sslContext;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl sslParameters(SSLParameters sslParameters) {
+        requireNonNull(sslParameters);
+        this.sslParams = Utils.copySSLParameters(sslParameters);
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl executor(Executor s) {
+        requireNonNull(s);
+        this.executor = s;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl followRedirects(HttpClient.Redirect policy) {
+        requireNonNull(policy);
+        this.followRedirects = policy;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl version(HttpClient.Version version) {
+        requireNonNull(version);
+        this.version = version;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl priority(int priority) {
+        if (priority < 1 || priority > 256) {
+            throw new IllegalArgumentException("priority must be between 1 and 256");
+        }
+        this.priority = priority;
+        return this;
+    }
+
+    @Override
+    public HttpClientBuilderImpl proxy(ProxySelector proxy) {
+        requireNonNull(proxy);
+        this.proxy = proxy;
+        return this;
+    }
+
+
+    @Override
+    public HttpClientBuilderImpl authenticator(Authenticator a) {
+        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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpClientFacade.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.ProxySelector;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpResponse.BodyHandler;
+import jdk.incubator.http.HttpResponse.PushPromiseHandler;
+import jdk.incubator.http.WebSocket;
+
+/**
+ * An HttpClientFacade is a simple class that wraps an HttpClient implementation
+ * and delegates everything to its implementation delegate.
+ */
+final class HttpClientFacade extends HttpClient {
+
+    final HttpClientImpl impl;
+
+    /**
+     * Creates an HttpClientFacade.
+     */
+    HttpClientFacade(HttpClientImpl impl) {
+        this.impl = impl;
+    }
+
+    @Override
+    public Optional<CookieHandler> cookieHandler() {
+        return impl.cookieHandler();
+    }
+
+    @Override
+    public Redirect followRedirects() {
+        return impl.followRedirects();
+    }
+
+    @Override
+    public Optional<ProxySelector> proxy() {
+        return impl.proxy();
+    }
+
+    @Override
+    public SSLContext sslContext() {
+        return impl.sslContext();
+    }
+
+    @Override
+    public SSLParameters sslParameters() {
+        return impl.sslParameters();
+    }
+
+    @Override
+    public Optional<Authenticator> authenticator() {
+        return impl.authenticator();
+    }
+
+    @Override
+    public HttpClient.Version version() {
+        return impl.version();
+    }
+
+    @Override
+    public Optional<Executor> executor() {
+        return impl.executor();
+    }
+
+    @Override
+    public <T> HttpResponse<T>
+    send(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)
+        throws IOException, InterruptedException
+    {
+        try {
+            return impl.send(req, responseBodyHandler);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler) {
+        try {
+            return impl.sendAsync(req, responseBodyHandler);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest req,
+              BodyHandler<T> responseBodyHandler,
+              PushPromiseHandler<T> pushPromiseHandler){
+        try {
+            return impl.sendAsync(req, responseBodyHandler, pushPromiseHandler);
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    @Override
+    public WebSocket.Builder newWebSocketBuilder() {
+        try {
+            return impl.newWebSocketBuilder();
+        } finally {
+            Reference.reachabilityFence(this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        // Used by tests to get the client's id.
+        return impl.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpClientImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,1017 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.lang.ref.WeakReference;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.ProxySelector;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedAction;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpResponse.BodyHandler;
+import jdk.incubator.http.HttpResponse.PushPromiseHandler;
+import jdk.incubator.http.WebSocket;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.Pair;
+import jdk.incubator.http.internal.common.Utils;
+import jdk.incubator.http.internal.websocket.BuilderImpl;
+import jdk.internal.misc.InnocuousThread;
+
+/**
+ * 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 {
+
+    static final boolean DEBUG = Utils.DEBUG;  // Revisit: temporary dev flag.
+    static final boolean DEBUGELAPSED = Utils.TESTING || DEBUG;  // Revisit: temporary dev flag.
+    static final boolean DEBUGTIMEOUT = false; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+    final System.Logger  debugelapsed = Utils.getDebugLogger(this::dbgString, DEBUGELAPSED);
+    final System.Logger  debugtimeout = Utils.getDebugLogger(this::dbgString, DEBUGTIMEOUT);
+    static final AtomicLong CLIENT_IDS = new AtomicLong();
+
+    // Define the default factory as a static inner class
+    // that embeds all the necessary logic to avoid
+    // the risk of using a lambda that might keep a reference on the
+    // HttpClient instance from which it was created (helps with
+    // heapdump analysis).
+    private static final class DefaultThreadFactory implements ThreadFactory {
+        private final String namePrefix;
+        private final AtomicInteger nextId = new AtomicInteger();
+
+        DefaultThreadFactory(long clientID) {
+            namePrefix = "HttpClient-" + clientID + "-Worker-";
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            String name = namePrefix + nextId.getAndIncrement();
+            Thread t;
+            if (System.getSecurityManager() == null) {
+                t = new Thread(null, r, name, 0, false);
+            } else {
+                t = InnocuousThread.newThread(name, r);
+            }
+            t.setDaemon(true);
+            return t;
+        }
+    }
+
+    private final CookieHandler cookieHandler;
+    private final Redirect followRedirects;
+    private final Optional<ProxySelector> userProxySelector;
+    private final ProxySelector proxySelector;
+    private final Authenticator authenticator;
+    private final Version version;
+    private final ConnectionPool connections;
+    private final Executor executor;
+    private final boolean isDefaultExecutor;
+    // Security parameters
+    private final SSLContext sslContext;
+    private final SSLParameters sslParams;
+    private final SelectorManager selmgr;
+    private final FilterFactory filters;
+    private final Http2ClientImpl client2;
+    private final long id;
+    private final String dbgTag;
+
+    // This reference is used to keep track of the facade HttpClient
+    // that was returned to the application code.
+    // It makes it possible to know when the application no longer
+    // holds any reference to the HttpClient.
+    // Unfortunately, this information is not enough to know when
+    // to exit the SelectorManager thread. Because of the asynchronous
+    // nature of the API, we also need to wait until all pending operations
+    // have completed.
+    private final WeakReference<HttpClientFacade> facadeRef;
+
+    // This counter keeps track of the number of operations pending
+    // on the HttpClient. The SelectorManager thread will wait
+    // until there are no longer any pending operations and the
+    // facadeRef is cleared before exiting.
+    //
+    // The pendingOperationCount is incremented every time a send/sendAsync
+    // operation is invoked on the HttpClient, and is decremented when
+    // the HttpResponse<T> object is returned to the user.
+    // However, at this point, the body may not have been fully read yet.
+    // This is the case when the response T is implemented as a streaming
+    // subscriber (such as an InputStream).
+    //
+    // To take care of this issue the pendingOperationCount will additionally
+    // be incremented/decremented in the following cases:
+    //
+    // 1. For HTTP/2  it is incremented when a stream is added to the
+    //    Http2Connection streams map, and decreased when the stream is removed
+    //    from the map. This should also take care of push promises.
+    // 2. For WebSocket the count is increased when creating a
+    //    DetachedConnectionChannel for the socket, and decreased
+    //    when the the channel is closed.
+    //    In addition, the HttpClient facade is passed to the WebSocket builder,
+    //    (instead of the client implementation delegate).
+    // 3. For HTTP/1.1 the count is incremented before starting to parse the body
+    //    response, and decremented when the parser has reached the end of the
+    //    response body flow.
+    //
+    // This should ensure that the selector manager thread remains alive until
+    // the response has been fully received or the web socket is closed.
+    private final AtomicLong pendingOperationCount = new AtomicLong();
+    private final AtomicLong pendingWebSocketCount = new AtomicLong();
+    private final AtomicLong pendingHttpRequestCount = new AtomicLong();
+
+    /** A Set of, deadline first, ordered timeout events. */
+    private final TreeSet<TimeoutEvent> timeouts;
+
+    /**
+     * This is a bit tricky:
+     * 1. an HttpClientFacade has a final HttpClientImpl field.
+     * 2. an HttpClientImpl has a final WeakReference<HttpClientFacade> field,
+     *    where the referent is the facade created for that instance.
+     * 3. We cannot just create the HttpClientFacade in the HttpClientImpl
+     *    constructor, because it would be only weakly referenced and could
+     *    be GC'ed before we can return it.
+     * The solution is to use an instance of SingleFacadeFactory which will
+     * allow the caller of new HttpClientImpl(...) to retrieve the facade
+     * after the HttpClientImpl has been created.
+     */
+    private static final class SingleFacadeFactory {
+        HttpClientFacade facade;
+        HttpClientFacade createFacade(HttpClientImpl impl) {
+            assert facade == null;
+            return (facade = new HttpClientFacade(impl));
+        }
+    }
+
+    static HttpClientFacade create(HttpClientBuilderImpl builder) {
+        SingleFacadeFactory facadeFactory = new SingleFacadeFactory();
+        HttpClientImpl impl = new HttpClientImpl(builder, facadeFactory);
+        impl.start();
+        assert facadeFactory.facade != null;
+        assert impl.facadeRef.get() == facadeFactory.facade;
+        return facadeFactory.facade;
+    }
+
+    private HttpClientImpl(HttpClientBuilderImpl builder,
+                           SingleFacadeFactory facadeFactory) {
+        id = CLIENT_IDS.incrementAndGet();
+        dbgTag = "HttpClientImpl(" + id +")";
+        if (builder.sslContext == null) {
+            try {
+                sslContext = SSLContext.getDefault();
+            } catch (NoSuchAlgorithmException ex) {
+                throw new InternalError(ex);
+            }
+        } else {
+            sslContext = builder.sslContext;
+        }
+        Executor ex = builder.executor;
+        if (ex == null) {
+            ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
+            isDefaultExecutor = true;
+        } else {
+            ex = builder.executor;
+            isDefaultExecutor = false;
+        }
+        facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
+        client2 = new Http2ClientImpl(this);
+        executor = ex;
+        cookieHandler = builder.cookieHandler;
+        followRedirects = builder.followRedirects == null ?
+                Redirect.NEVER : builder.followRedirects;
+        this.userProxySelector = Optional.ofNullable(builder.proxy);
+        this.proxySelector = userProxySelector
+                .orElseGet(HttpClientImpl::getDefaultProxySelector);
+        debug.log(Level.DEBUG, "proxySelector is %s (user-supplied=%s)",
+                this.proxySelector, userProxySelector.isPresent());
+        authenticator = builder.authenticator;
+        if (builder.version == null) {
+            version = HttpClient.Version.HTTP_2;
+        } else {
+            version = builder.version;
+        }
+        if (builder.sslParams == null) {
+            sslParams = getDefaultParams(sslContext);
+        } else {
+            sslParams = builder.sslParams;
+        }
+        connections = new ConnectionPool(id);
+        connections.start();
+        timeouts = new TreeSet<>();
+        try {
+            selmgr = new SelectorManager(this);
+        } catch (IOException e) {
+            // unlikely
+            throw new InternalError(e);
+        }
+        selmgr.setDaemon(true);
+        filters = new FilterFactory();
+        initFilters();
+        assert facadeRef.get() != null;
+    }
+
+    private void start() {
+        selmgr.start();
+    }
+
+    // Called from the SelectorManager thread, just before exiting.
+    // Clears the HTTP/1.1 and HTTP/2 cache, ensuring that the connections
+    // that may be still lingering there are properly closed (and their
+    // possibly still opened SocketChannel released).
+    private void stop() {
+        // Clears HTTP/1.1 cache and close its connections
+        connections.stop();
+        // Clears HTTP/2 cache and close its connections.
+        client2.stop();
+    }
+
+    private static SSLParameters getDefaultParams(SSLContext ctx) {
+        SSLParameters params = ctx.getSupportedSSLParameters();
+        params.setProtocols(new String[]{"TLSv1.2"});
+        return params;
+    }
+
+    private static ProxySelector getDefaultProxySelector() {
+        PrivilegedAction<ProxySelector> action = ProxySelector::getDefault;
+        return AccessController.doPrivileged(action);
+    }
+
+    // Returns the facade that was returned to the application code.
+    // May be null if that facade is no longer referenced.
+    final HttpClientFacade facade() {
+        return facadeRef.get();
+    }
+
+    // Increments the pendingOperationCount.
+    final long reference() {
+        pendingHttpRequestCount.incrementAndGet();
+        return pendingOperationCount.incrementAndGet();
+    }
+
+    // Decrements the pendingOperationCount.
+    final long unreference() {
+        final long count = pendingOperationCount.decrementAndGet();
+        final long httpCount = pendingHttpRequestCount.decrementAndGet();
+        final long webSocketCount = pendingWebSocketCount.get();
+        if (count == 0 && facade() == null) {
+            selmgr.wakeupSelector();
+        }
+        assert httpCount >= 0 : "count of HTTP operations < 0";
+        assert webSocketCount >= 0 : "count of WS operations < 0";
+        assert count >= 0 : "count of pending operations < 0";
+        return count;
+    }
+
+    // Increments the pendingOperationCount.
+    final long webSocketOpen() {
+        pendingWebSocketCount.incrementAndGet();
+        return pendingOperationCount.incrementAndGet();
+    }
+
+    // Decrements the pendingOperationCount.
+    final long webSocketClose() {
+        final long count = pendingOperationCount.decrementAndGet();
+        final long webSocketCount = pendingWebSocketCount.decrementAndGet();
+        final long httpCount = pendingHttpRequestCount.get();
+        if (count == 0 && facade() == null) {
+            selmgr.wakeupSelector();
+        }
+        assert httpCount >= 0 : "count of HTTP operations < 0";
+        assert webSocketCount >= 0 : "count of WS operations < 0";
+        assert count >= 0 : "count of pending operations < 0";
+        return count;
+    }
+
+    // Returns the pendingOperationCount.
+    final long referenceCount() {
+        return pendingOperationCount.get();
+    }
+
+    // Called by the SelectorManager thread to figure out whether it's time
+    // to terminate.
+    final boolean isReferenced() {
+        HttpClient facade = facade();
+        return facade != null || referenceCount() > 0;
+    }
+
+    /**
+     * Wait for activity on given exchange.
+     * The following occurs in the SelectorManager thread.
+     *
+     *  1) add to selector
+     *  2) If selector fires for this exchange then
+     *     call AsyncEvent.handle()
+     *
+     * If exchange needs to change interest ops, then call registerEvent() again.
+     */
+    void registerEvent(AsyncEvent exchange) throws IOException {
+        selmgr.register(exchange);
+    }
+
+    /**
+     * Only used from RawChannel to disconnect the channel from
+     * the selector
+     */
+    void cancelRegistration(SocketChannel s) {
+        selmgr.cancel(s);
+    }
+
+    /**
+     * Allows an AsyncEvent to modify its interestOps.
+     * @param event The modified event.
+     */
+    void eventUpdated(AsyncEvent event) throws ClosedChannelException {
+        assert !(event instanceof AsyncTriggerEvent);
+        selmgr.eventUpdated(event);
+    }
+
+    boolean isSelectorThread() {
+        return Thread.currentThread() == selmgr;
+    }
+
+    Http2ClientImpl client2() {
+        return client2;
+    }
+
+    private void debugCompleted(String tag, long startNanos, HttpRequest req) {
+        if (debugelapsed.isLoggable(Level.DEBUG)) {
+            debugelapsed.log(Level.DEBUG, () -> tag + " elapsed "
+                    + (System.nanoTime() - startNanos)/1000_000L
+                    + " millis for " + req.method()
+                    + " to " + req.uri());
+        }
+    }
+
+    @Override
+    public <T> HttpResponse<T>
+    send(HttpRequest req, BodyHandler<T> responseHandler)
+        throws IOException, InterruptedException
+    {
+        try {
+            return sendAsync(req, responseHandler).get();
+        } catch (ExecutionException e) {
+            Throwable t = e.getCause();
+            if (t instanceof Error)
+                throw (Error)t;
+            if (t instanceof RuntimeException)
+                throw (RuntimeException)t;
+            else if (t instanceof IOException)
+                throw Utils.getIOException(t);
+            else
+                throw new InternalError("Unexpected exception", t);
+        }
+    }
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest userRequest, BodyHandler<T> responseHandler)
+    {
+        return sendAsync(userRequest, responseHandler, null);
+    }
+
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest userRequest,
+              BodyHandler<T> responseHandler,
+              PushPromiseHandler<T> pushPromiseHandler)
+    {
+        AccessControlContext acc = null;
+        if (System.getSecurityManager() != null)
+            acc = AccessController.getContext();
+
+        // Clone the, possibly untrusted, HttpRequest
+        HttpRequestImpl requestImpl = new HttpRequestImpl(userRequest, proxySelector, acc);
+        if (requestImpl.method().equals("CONNECT"))
+            throw new IllegalArgumentException("Unsupported method CONNECT");
+
+        long start = DEBUGELAPSED ? System.nanoTime() : 0;
+        reference();
+        try {
+            debugelapsed.log(Level.DEBUG, "ClientImpl (async) send %s", userRequest);
+
+            MultiExchange<T> mex = new MultiExchange<>(userRequest,
+                                                            requestImpl,
+                                                            this,
+                                                            responseHandler,
+                                                            pushPromiseHandler,
+                                                            acc);
+            CompletableFuture<HttpResponse<T>> res =
+                    mex.responseAsync().whenComplete((b,t) -> unreference());
+            if (DEBUGELAPSED) {
+                res = res.whenComplete(
+                        (b,t) -> debugCompleted("ClientImpl (async)", start, userRequest));
+            }
+            // makes sure that any dependent actions happen in the executor
+            if (acc != null) {
+                res.whenCompleteAsync((r, t) -> { /* do nothing */},
+                                      new PrivilegedExecutor(executor, acc));
+            }
+
+            return res;
+        } catch(Throwable t) {
+            unreference();
+            debugCompleted("ClientImpl (async)", start, userRequest);
+            throw t;
+        }
+    }
+
+    // Main loop for this client's selector
+    private final static class SelectorManager extends Thread {
+
+        // For testing purposes we have an internal System property that
+        // can control the frequency at which the selector manager will wake
+        // up when there are no pending operations.
+        // Increasing the frequency (shorter delays) might allow the selector
+        // to observe that the facade is no longer referenced and might allow
+        // the selector thread to terminate more timely - for when nothing is
+        // ongoing it will only check for that condition every NODEADLINE ms.
+        // To avoid misuse of the property, the delay that can be specified
+        // is comprised between [MIN_NODEADLINE, MAX_NODEADLINE], and its default
+        // value if unspecified (or <= 0) is DEF_NODEADLINE = 3000ms
+        // The property is -Djdk.httpclient.internal.selector.timeout=<millis>
+        private static final int MIN_NODEADLINE = 1000; // ms
+        private static final int MAX_NODEADLINE = 1000 * 1200; // ms
+        private static final int DEF_NODEADLINE = 3000; // ms
+        private static final long NODEADLINE; // default is DEF_NODEADLINE ms
+        static {
+            // ensure NODEADLINE is initialized with some valid value.
+            long deadline =  Utils.getIntegerNetProperty(
+                "jdk.httpclient.internal.selector.timeout",
+                DEF_NODEADLINE); // millis
+            if (deadline <= 0) deadline = DEF_NODEADLINE;
+            deadline = Math.max(deadline, MIN_NODEADLINE);
+            NODEADLINE = Math.min(deadline, MAX_NODEADLINE);
+        }
+
+        private final Selector selector;
+        private volatile boolean closed;
+        private final List<AsyncEvent> registrations;
+        private final System.Logger debug;
+        private final System.Logger debugtimeout;
+        HttpClientImpl owner;
+        ConnectionPool pool;
+
+        SelectorManager(HttpClientImpl ref) throws IOException {
+            super(null, null, "HttpClient-" + ref.id + "-SelectorManager", 0, false);
+            owner = ref;
+            debug = ref.debug;
+            debugtimeout = ref.debugtimeout;
+            pool = ref.connectionPool();
+            registrations = new ArrayList<>();
+            selector = Selector.open();
+        }
+
+        void eventUpdated(AsyncEvent e) throws ClosedChannelException {
+            if (Thread.currentThread() == this) {
+                SelectionKey key = e.channel().keyFor(selector);
+                SelectorAttachment sa = (SelectorAttachment) key.attachment();
+                if (sa != null) sa.register(e);
+            } else {
+                register(e);
+            }
+        }
+
+        // This returns immediately. So caller not allowed to send/receive
+        // on connection.
+        synchronized void register(AsyncEvent e) {
+            registrations.add(e);
+            selector.wakeup();
+        }
+
+        synchronized void cancel(SocketChannel e) {
+            SelectionKey key = e.keyFor(selector);
+            if (key != null) {
+                key.cancel();
+            }
+            selector.wakeup();
+        }
+
+        void wakeupSelector() {
+            selector.wakeup();
+        }
+
+        synchronized void shutdown() {
+            debug.log(Level.DEBUG, "SelectorManager shutting down");
+            closed = true;
+            try {
+                selector.close();
+            } catch (IOException ignored) {
+            } finally {
+                owner.stop();
+            }
+        }
+
+        @Override
+        public void run() {
+            List<Pair<AsyncEvent,IOException>> errorList = new ArrayList<>();
+            List<AsyncEvent> readyList = new ArrayList<>();
+            try {
+                while (!Thread.currentThread().isInterrupted()) {
+                    synchronized (this) {
+                        assert errorList.isEmpty();
+                        assert readyList.isEmpty();
+                        for (AsyncEvent event : registrations) {
+                            if (event instanceof AsyncTriggerEvent) {
+                                readyList.add(event);
+                                continue;
+                            }
+                            SelectableChannel chan = event.channel();
+                            SelectionKey key = null;
+                            try {
+                                key = chan.keyFor(selector);
+                                SelectorAttachment sa;
+                                if (key == null || !key.isValid()) {
+                                    if (key != null) {
+                                        // key is canceled.
+                                        // invoke selectNow() to purge it
+                                        // before registering the new event.
+                                        selector.selectNow();
+                                    }
+                                    sa = new SelectorAttachment(chan, selector);
+                                } else {
+                                    sa = (SelectorAttachment) key.attachment();
+                                }
+                                // may throw IOE if channel closed: that's OK
+                                sa.register(event);
+                                if (!chan.isOpen()) {
+                                    throw new IOException("Channel closed");
+                                }
+                            } catch (IOException e) {
+                                Log.logTrace("HttpClientImpl: " + e);
+                                debug.log(Level.DEBUG, () ->
+                                        "Got " + e.getClass().getName()
+                                                 + " while handling"
+                                                 + " registration events");
+                                chan.close();
+                                // let the event abort deal with it
+                                errorList.add(new Pair<>(event, e));
+                                if (key != null) {
+                                    key.cancel();
+                                    selector.selectNow();
+                                }
+                            }
+                        }
+                        registrations.clear();
+                        selector.selectedKeys().clear();
+                    }
+
+                    for (AsyncEvent event : readyList) {
+                        assert event instanceof AsyncTriggerEvent;
+                        event.handle();
+                    }
+                    readyList.clear();
+
+                    for (Pair<AsyncEvent,IOException> error : errorList) {
+                        // an IOException was raised and the channel closed.
+                        handleEvent(error.first, error.second);
+                    }
+                    errorList.clear();
+
+                    // Check whether client is still alive, and if not,
+                    // gracefully stop this thread
+                    if (!owner.isReferenced()) {
+                        Log.logTrace("HttpClient no longer referenced. Exiting...");
+                        return;
+                    }
+
+                    // Timeouts will have milliseconds granularity. It is important
+                    // to handle them in a timely fashion.
+                    long nextTimeout = owner.purgeTimeoutsAndReturnNextDeadline();
+                    debugtimeout.log(Level.DEBUG, "next timeout: %d", nextTimeout);
+
+                    // Keep-alive have seconds granularity. It's not really an
+                    // issue if we keep connections linger a bit more in the keep
+                    // alive cache.
+                    long nextExpiry = pool.purgeExpiredConnectionsAndReturnNextDeadline();
+                    debugtimeout.log(Level.DEBUG, "next expired: %d", nextExpiry);
+
+                    assert nextTimeout >= 0;
+                    assert nextExpiry >= 0;
+
+                    // Don't wait for ever as it might prevent the thread to
+                    // stop gracefully. millis will be 0 if no deadline was found.
+                    if (nextTimeout <= 0) nextTimeout = NODEADLINE;
+
+                    // Clip nextExpiry at NODEADLINE limit. The default
+                    // keep alive is 1200 seconds (half an hour) - we don't
+                    // want to wait that long.
+                    if (nextExpiry <= 0) nextExpiry = NODEADLINE;
+                    else nextExpiry = Math.min(NODEADLINE, nextExpiry);
+
+                    // takes the least of the two.
+                    long millis = Math.min(nextExpiry, nextTimeout);
+
+                    debugtimeout.log(Level.DEBUG, "Next deadline is %d",
+                                     (millis == 0 ? NODEADLINE : millis));
+                    //debugPrint(selector);
+                    int n = selector.select(millis == 0 ? NODEADLINE : millis);
+                    if (n == 0) {
+                        // Check whether client is still alive, and if not,
+                        // gracefully stop this thread
+                        if (!owner.isReferenced()) {
+                            Log.logTrace("HttpClient no longer referenced. Exiting...");
+                            return;
+                        }
+                        owner.purgeTimeoutsAndReturnNextDeadline();
+                        continue;
+                    }
+                    Set<SelectionKey> keys = selector.selectedKeys();
+
+                    assert errorList.isEmpty();
+                    for (SelectionKey key : keys) {
+                        SelectorAttachment sa = (SelectorAttachment) key.attachment();
+                        if (!key.isValid()) {
+                            IOException ex = sa.chan.isOpen()
+                                    ? new IOException("Invalid key")
+                                    : new ClosedChannelException();
+                            sa.pending.forEach(e -> errorList.add(new Pair<>(e,ex)));
+                            sa.pending.clear();
+                            continue;
+                        }
+
+                        int eventsOccurred;
+                        try {
+                            eventsOccurred = key.readyOps();
+                        } catch (CancelledKeyException ex) {
+                            IOException io = Utils.getIOException(ex);
+                            sa.pending.forEach(e -> errorList.add(new Pair<>(e,io)));
+                            sa.pending.clear();
+                            continue;
+                        }
+                        sa.events(eventsOccurred).forEach(readyList::add);
+                        sa.resetInterestOps(eventsOccurred);
+                    }
+                    selector.selectNow(); // complete cancellation
+                    selector.selectedKeys().clear();
+
+                    for (AsyncEvent event : readyList) {
+                        handleEvent(event, null); // will be delegated to executor
+                    }
+                    readyList.clear();
+                    errorList.forEach((p) -> handleEvent(p.first, p.second));
+                    errorList.clear();
+                }
+            } catch (Throwable e) {
+                //e.printStackTrace();
+                if (!closed) {
+                    // This terminates thread. So, better just print stack trace
+                    String err = Utils.stackTrace(e);
+                    Log.logError("HttpClientImpl: fatal error: " + err);
+                }
+                debug.log(Level.DEBUG, "shutting down", e);
+                if (Utils.ASSERTIONSENABLED && !debug.isLoggable(Level.DEBUG)) {
+                    e.printStackTrace(System.err); // always print the stack
+                }
+            } finally {
+                shutdown();
+            }
+        }
+
+//        void debugPrint(Selector selector) {
+//            System.err.println("Selector: debugprint start");
+//            Set<SelectionKey> keys = selector.keys();
+//            for (SelectionKey key : keys) {
+//                SelectableChannel c = key.channel();
+//                int ops = key.interestOps();
+//                System.err.printf("selector chan:%s ops:%d\n", c, ops);
+//            }
+//            System.err.println("Selector: debugprint end");
+//        }
+
+        /** Handles the given event. The given ioe may be null. */
+        void handleEvent(AsyncEvent event, IOException ioe) {
+            if (closed || ioe != null) {
+                event.abort(ioe);
+            } else {
+                event.handle();
+            }
+        }
+    }
+
+    /**
+     * Tracks multiple user level registrations associated with one NIO
+     * registration (SelectionKey). In this implementation, registrations
+     * are one-off and when an event is posted the registration is cancelled
+     * until explicitly registered again.
+     *
+     * <p> No external synchronization required as this class is only used
+     * by the SelectorManager thread. One of these objects required per
+     * connection.
+     */
+    private static class SelectorAttachment {
+        private final SelectableChannel chan;
+        private final Selector selector;
+        private final Set<AsyncEvent> pending;
+        private final static System.Logger debug =
+                Utils.getDebugLogger("SelectorAttachment"::toString, DEBUG);
+        private int interestOps;
+
+        SelectorAttachment(SelectableChannel chan, Selector selector) {
+            this.pending = new HashSet<>();
+            this.chan = chan;
+            this.selector = selector;
+        }
+
+        void register(AsyncEvent e) throws ClosedChannelException {
+            int newOps = e.interestOps();
+            boolean reRegister = (interestOps & newOps) != newOps;
+            interestOps |= newOps;
+            pending.add(e);
+            if (reRegister) {
+                // first time registration happens here also
+                chan.register(selector, interestOps, this);
+            }
+        }
+
+        /**
+         * Returns a Stream<AsyncEvents> containing only events that are
+         * registered with the given {@code interestOps}.
+         */
+        Stream<AsyncEvent> events(int interestOps) {
+            return pending.stream()
+                    .filter(ev -> (ev.interestOps() & interestOps) != 0);
+        }
+
+        /**
+         * Removes any events with the given {@code interestOps}, and if no
+         * events remaining, cancels the associated SelectionKey.
+         */
+        void resetInterestOps(int interestOps) {
+            int newOps = 0;
+
+            Iterator<AsyncEvent> itr = pending.iterator();
+            while (itr.hasNext()) {
+                AsyncEvent event = itr.next();
+                int evops = event.interestOps();
+                if (event.repeating()) {
+                    newOps |= evops;
+                    continue;
+                }
+                if ((evops & interestOps) != 0) {
+                    itr.remove();
+                } else {
+                    newOps |= evops;
+                }
+            }
+
+            this.interestOps = newOps;
+            SelectionKey key = chan.keyFor(selector);
+            if (newOps == 0 && pending.isEmpty()) {
+                key.cancel();
+            } else {
+                try {
+                    key.interestOps(newOps);
+                } catch (CancelledKeyException x) {
+                    // channel may have been closed
+                    debug.log(Level.DEBUG, "key cancelled for " + chan);
+                    abortPending(x);
+                }
+            }
+        }
+
+        void abortPending(Throwable x) {
+            if (!pending.isEmpty()) {
+                AsyncEvent[] evts = pending.toArray(new AsyncEvent[0]);
+                pending.clear();
+                IOException io = Utils.getIOException(x);
+                for (AsyncEvent event : evts) {
+                    event.abort(io);
+                }
+            }
+        }
+    }
+
+    /*package-private*/ SSLContext theSSLContext() {
+        return sslContext;
+    }
+
+    @Override
+    public SSLContext sslContext() {
+        return sslContext;
+    }
+
+    @Override
+    public SSLParameters sslParameters() {
+        return Utils.copySSLParameters(sslParams);
+    }
+
+    @Override
+    public Optional<Authenticator> authenticator() {
+        return Optional.ofNullable(authenticator);
+    }
+
+    /*package-private*/ final Executor theExecutor() {
+        return executor;
+    }
+
+    @Override
+    public final Optional<Executor> executor() {
+        return isDefaultExecutor ? Optional.empty() : Optional.of(executor);
+    }
+
+    ConnectionPool connectionPool() {
+        return connections;
+    }
+
+    @Override
+    public Redirect followRedirects() {
+        return followRedirects;
+    }
+
+
+    @Override
+    public Optional<CookieHandler> cookieHandler() {
+        return Optional.ofNullable(cookieHandler);
+    }
+
+    @Override
+    public Optional<ProxySelector> proxy() {
+        return this.userProxySelector;
+    }
+
+    // Return the effective proxy that this client uses.
+    ProxySelector proxySelector() {
+        return proxySelector;
+    }
+
+    @Override
+    public WebSocket.Builder newWebSocketBuilder() {
+        // Make sure to pass the HttpClientFacade to the WebSocket builder.
+        // This will ensure that the facade is not released before the
+        // WebSocket has been created, at which point the pendingOperationCount
+        // will have been incremented by the DetachedConnectionChannel
+        // (see PlainHttpConnection.detachChannel())
+        return new BuilderImpl(this.facade(), proxySelector);
+    }
+
+    @Override
+    public Version version() {
+        return version;
+    }
+
+    String dbgString() {
+        return dbgTag;
+    }
+
+    @Override
+    public String toString() {
+        // Used by tests to get the client's id and compute the
+        // name of the SelectorManager thread.
+        return super.toString() + ("(" + id + ")");
+    }
+
+    private void initFilters() {
+        addFilter(AuthenticationFilter.class);
+        addFilter(RedirectFilter.class);
+        if (this.cookieHandler != null) {
+            addFilter(CookieFilter.class);
+        }
+    }
+
+    private 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) {
+        Log.logTrace("Registering timer {0}", event);
+        timeouts.add(event);
+        selmgr.wakeupSelector();
+    }
+
+    synchronized void cancelTimer(TimeoutEvent event) {
+        Log.logTrace("Canceling timer {0}", event);
+        timeouts.remove(event);
+    }
+
+    /**
+     * Purges ( handles ) timer events that have passed their deadline, and
+     * returns the amount of time, in milliseconds, until the next earliest
+     * event. A return value of 0 means that there are no events.
+     */
+    private long purgeTimeoutsAndReturnNextDeadline() {
+        long diff = 0L;
+        List<TimeoutEvent> toHandle = null;
+        int remaining = 0;
+        // enter critical section to retrieve the timeout event to handle
+        synchronized(this) {
+            if (timeouts.isEmpty()) return 0L;
+
+            Instant now = Instant.now();
+            Iterator<TimeoutEvent> itr = timeouts.iterator();
+            while (itr.hasNext()) {
+                TimeoutEvent event = itr.next();
+                diff = now.until(event.deadline(), ChronoUnit.MILLIS);
+                if (diff <= 0) {
+                    itr.remove();
+                    toHandle = (toHandle == null) ? new ArrayList<>() : toHandle;
+                    toHandle.add(event);
+                } else {
+                    break;
+                }
+            }
+            remaining = timeouts.size();
+        }
+
+        // can be useful for debugging
+        if (toHandle != null && Log.trace()) {
+            Log.logTrace("purgeTimeoutsAndReturnNextDeadline: handling "
+                    +  toHandle.size() + " events, "
+                    + "remaining " + remaining
+                    + ", next deadline: " + (diff < 0 ? 0L : diff));
+        }
+
+        // handle timeout events out of critical section
+        if (toHandle != null) {
+            Throwable failed = null;
+            for (TimeoutEvent event : toHandle) {
+                try {
+                   Log.logTrace("Firing timer {0}", event);
+                   event.handle();
+                } catch (Error | RuntimeException e) {
+                    // Not expected. Handle remaining events then throw...
+                    // If e is an OOME or SOE it might simply trigger a new
+                    // error from here - but in this case there's not much we
+                    // could do anyway. Just let it flow...
+                    if (failed == null) failed = e;
+                    else failed.addSuppressed(e);
+                    Log.logTrace("Failed to handle event {0}: {1}", event, e);
+                }
+            }
+            if (failed instanceof Error) throw (Error) failed;
+            if (failed instanceof RuntimeException) throw (RuntimeException) failed;
+        }
+
+        // return time to wait until next event. 0L if there's no more events.
+        return diff < 0 ? 0L : diff;
+    }
+
+    // used for the connection window
+    int getReceiveBufferSize() {
+        return Utils.getIntegerNetProperty(
+                "jdk.httpclient.receiveBufferSize", 2 * 1024 * 1024
+        );
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpConnection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Flow;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpClient.Version;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.internal.common.Demand;
+import jdk.incubator.http.internal.common.FlowTube;
+import jdk.incubator.http.internal.common.SequentialScheduler;
+import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.Utils;
+import static jdk.incubator.http.HttpClient.Version.HTTP_2;
+
+/**
+ * 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
+ *      AsyncSSLConnection: TLS channel direct to server
+ *      AsyncSSLTunnelConnection: TLS channel via (CONNECT) proxy tunnel
+ */
+abstract class HttpConnection implements Closeable {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+    final static System.Logger DEBUG_LOGGER = Utils.getDebugLogger(
+            () -> "HttpConnection(SocketTube(?))", DEBUG);
+
+    /** The address this connection is connected to. Could be a server or a proxy. */
+    final InetSocketAddress address;
+    private final HttpClientImpl client;
+    private final TrailingOperations trailingOperations;
+
+    HttpConnection(InetSocketAddress address, HttpClientImpl client) {
+        this.address = address;
+        this.client = client;
+        trailingOperations = new TrailingOperations();
+    }
+
+    private static final class TrailingOperations {
+        private final Map<CompletionStage<?>, Boolean> operations =
+                new IdentityHashMap<>();
+        void add(CompletionStage<?> cf) {
+            synchronized(operations) {
+                cf.whenComplete((r,t)-> remove(cf));
+                operations.put(cf, Boolean.TRUE);
+            }
+        }
+        boolean remove(CompletionStage<?> cf) {
+            synchronized(operations) {
+                return operations.remove(cf);
+            }
+        }
+    }
+
+    final void addTrailingOperation(CompletionStage<?> cf) {
+        trailingOperations.add(cf);
+    }
+
+//    final void removeTrailingOperation(CompletableFuture<?> cf) {
+//        trailingOperations.remove(cf);
+//    }
+
+    final HttpClientImpl client() {
+        return client;
+    }
+
+    //public abstract void connect() throws IOException, InterruptedException;
+
+    public abstract CompletableFuture<Void> connectAsync();
+
+    /** Tells whether, or not, this connection is connected to its destination. */
+    abstract boolean connected();
+
+    /** Tells whether, or not, this connection is secure ( over SSL ) */
+    abstract boolean isSecure();
+
+    /** Tells whether, or not, this connection is proxied. */
+    abstract boolean isProxied();
+
+    /** Tells whether, or not, this connection is open. */
+    final boolean isOpen() {
+        return channel().isOpen() &&
+                (connected() ? !getConnectionFlow().isFinished() : true);
+    }
+
+    interface HttpPublisher extends FlowTube.TubePublisher {
+        void enqueue(List<ByteBuffer> buffers) throws IOException;
+        void enqueueUnordered(List<ByteBuffer> buffers) throws IOException;
+        void signalEnqueued() throws IOException;
+    }
+
+    /**
+     * Returns the HTTP publisher associated with this connection.  May be null
+     * if invoked before connecting.
+     */
+    abstract HttpPublisher publisher();
+
+    // HTTP/2 MUST use TLS version 1.2 or higher for HTTP/2 over TLS
+    private static final Predicate<String> testRequiredHTTP2TLSVersion = proto ->
+            proto.equals("TLSv1.2") || proto.equals("TLSv1.3");
+
+   /**
+    * Returns true if the given client's SSL parameter protocols contains at
+    * least one TLS version that HTTP/2 requires.
+    */
+   private static final boolean hasRequiredHTTP2TLSVersion(HttpClient client) {
+       String[] protos = client.sslParameters().getProtocols();
+       if (protos != null) {
+           return Arrays.stream(protos).filter(testRequiredHTTP2TLSVersion).findAny().isPresent();
+       } else {
+           return false;
+       }
+   }
+
+    /**
+     * Factory for retrieving HttpConnections. A connection can be retrieved
+     * from the connection pool, or a new one created if none available.
+     *
+     * The given {@code addr} is the ultimate destination. Any proxies,
+     * etc, are determined from the request. Returns a concrete instance which
+     * is one of the following:
+     *      {@link PlainHttpConnection}
+     *      {@link PlainTunnelingConnection}
+     *
+     * The returned connection, if not from the connection pool, must have its,
+     * connect() or connectAsync() method invoked, which ( when it completes
+     * successfully ) renders the connection usable for requests.
+     */
+    public static HttpConnection getConnection(InetSocketAddress addr,
+                                               HttpClientImpl client,
+                                               HttpRequestImpl request,
+                                               Version version) {
+        HttpConnection c = null;
+        InetSocketAddress proxy = request.proxy();
+        if (proxy != null && proxy.isUnresolved()) {
+            // The default proxy selector may select a proxy whose  address is
+            // unresolved. We must resolve the address before connecting to it.
+            proxy = new InetSocketAddress(proxy.getHostString(), proxy.getPort());
+        }
+        boolean secure = request.secure();
+        ConnectionPool pool = client.connectionPool();
+
+        if (!secure) {
+            c = pool.getConnection(false, addr, proxy);
+            if (c != null && c.isOpen() /* may have been eof/closed when in the pool */) {
+                final HttpConnection conn = c;
+                DEBUG_LOGGER.log(Level.DEBUG, () -> conn.getConnectionFlow()
+                            + ": plain connection retrieved from HTTP/1.1 pool");
+                return c;
+            } else {
+                return getPlainConnection(addr, proxy, request, client);
+            }
+        } else {  // secure
+            if (version != HTTP_2) { // only HTTP/1.1 connections are in the pool
+                c = pool.getConnection(true, addr, proxy);
+            }
+            if (c != null && c.isOpen()) {
+                final HttpConnection conn = c;
+                DEBUG_LOGGER.log(Level.DEBUG, () -> conn.getConnectionFlow()
+                            + ": SSL connection retrieved from HTTP/1.1 pool");
+                return c;
+            } else {
+                String[] alpn = null;
+                if (version == HTTP_2 && hasRequiredHTTP2TLSVersion(client)) {
+                    alpn = new String[] { "h2", "http/1.1" };
+                }
+                return getSSLConnection(addr, proxy, alpn, request, client);
+            }
+        }
+    }
+
+    private static HttpConnection getSSLConnection(InetSocketAddress addr,
+                                                   InetSocketAddress proxy,
+                                                   String[] alpn,
+                                                   HttpRequestImpl request,
+                                                   HttpClientImpl client) {
+        if (proxy != null)
+            return new AsyncSSLTunnelConnection(addr, client, alpn, proxy,
+                                                proxyTunnelHeaders(request));
+        else
+            return new AsyncSSLConnection(addr, client, alpn);
+    }
+
+    /**
+     * This method is used to build a filter that will accept or
+     * veto (header-name, value) tuple for transmission on the
+     * wire.
+     * The filter is applied to the headers when sending the headers
+     * to the remote party.
+     * Which tuple is accepted/vetoed depends on:
+     * <pre>
+     *    - whether the connection is a tunnel connection
+     *      [talking to a server through a proxy tunnel]
+     *    - whether the method is CONNECT
+     *      [establishing a CONNECT tunnel through a proxy]
+     *    - whether the request is using a proxy
+     *      (and the connection is not a tunnel)
+     *      [talking to a server through a proxy]
+     *    - whether the request is a direct connection to
+     *      a server (no tunnel, no proxy).
+     * </pre>
+     * @param request
+     * @return
+     */
+    BiPredicate<String,List<String>> headerFilter(HttpRequestImpl request) {
+        if (isTunnel()) {
+            // talking to a server through a proxy tunnel
+            // don't send proxy-* headers to a plain server
+            assert !request.isConnect();
+            return Utils.NO_PROXY_HEADERS_FILTER;
+        } else if (request.isConnect()) {
+            // establishing a proxy tunnel
+            // check for proxy tunnel disabled schemes
+            // assert !this.isTunnel();
+            assert request.proxy() == null;
+            return Utils.PROXY_TUNNEL_FILTER;
+        } else if (request.proxy() != null) {
+            // talking to a server through a proxy (no tunnel)
+            // check for proxy disabled schemes
+            // assert !isTunnel() && !request.isConnect();
+            return Utils.PROXY_FILTER;
+        } else {
+            // talking to a server directly (no tunnel, no proxy)
+            // don't send proxy-* headers to a plain server
+            // assert request.proxy() == null && !request.isConnect();
+            return Utils.NO_PROXY_HEADERS_FILTER;
+        }
+    }
+
+    // Composes a new immutable HttpHeaders that combines the
+    // user and system header but only keeps those headers that
+    // start with "proxy-"
+    private static HttpHeaders proxyTunnelHeaders(HttpRequestImpl request) {
+        Map<String, List<String>> combined = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        combined.putAll(request.getSystemHeaders().map());
+        combined.putAll(request.headers().map()); // let user override system
+
+        // keep only proxy-* - and also strip authorization headers
+        // for disabled schemes
+        return ImmutableHeaders.of(combined, Utils.PROXY_TUNNEL_FILTER);
+    }
+
+    /* Returns either a plain HTTP connection or a plain tunnelling connection
+     * for proxied WebSocket */
+    private static HttpConnection getPlainConnection(InetSocketAddress addr,
+                                                     InetSocketAddress proxy,
+                                                     HttpRequestImpl request,
+                                                     HttpClientImpl client) {
+        if (request.isWebSocket() && proxy != null)
+            return new PlainTunnelingConnection(addr, proxy, client,
+                                                proxyTunnelHeaders(request));
+
+        if (proxy == null)
+            return new PlainHttpConnection(addr, client);
+        else
+            return new PlainProxyConnection(proxy, client);
+    }
+
+    void closeOrReturnToCache(HttpHeaders hdrs) {
+        if (hdrs == null) {
+            // the connection was closed by server, eof
+            close();
+            return;
+        }
+        if (!isOpen()) {
+            return;
+        }
+        HttpClientImpl client = client();
+        if (client == null) {
+            close();
+            return;
+        }
+        ConnectionPool pool = client.connectionPool();
+        boolean keepAlive = hdrs.firstValue("Connection")
+                .map((s) -> !s.equalsIgnoreCase("close"))
+                .orElse(true);
+
+        if (keepAlive) {
+            Log.logTrace("Returning connection to the pool: {0}", this);
+            pool.returnToPool(this);
+        } else {
+            close();
+        }
+    }
+
+    /* Tells whether or not this connection is a tunnel through a proxy */
+    boolean isTunnel() { return false; }
+
+    abstract SocketChannel channel();
+
+    final InetSocketAddress address() {
+        return address;
+    }
+
+    abstract ConnectionPool.CacheKey cacheKey();
+
+    /**
+     * Closes this connection, by returning the socket to its connection pool.
+     */
+    @Override
+    public abstract void close();
+
+    abstract void shutdownInput() throws IOException;
+
+    abstract void shutdownOutput() throws IOException;
+
+    // Support for WebSocket/RawChannelImpl which unfortunately
+    // still depends on synchronous read/writes.
+    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
+    abstract static class DetachedConnectionChannel implements Closeable {
+        DetachedConnectionChannel() {}
+        abstract SocketChannel channel();
+        abstract long write(ByteBuffer[] buffers, int start, int number)
+                throws IOException;
+        abstract void shutdownInput() throws IOException;
+        abstract void shutdownOutput() throws IOException;
+        abstract ByteBuffer read() throws IOException;
+        @Override
+        public abstract void close();
+        @Override
+        public String toString() {
+            return this.getClass().getSimpleName() + ": " + channel().toString();
+        }
+    }
+
+    // Support for WebSocket/RawChannelImpl which unfortunately
+    // still depends on synchronous read/writes.
+    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
+    abstract DetachedConnectionChannel detachChannel();
+
+    abstract FlowTube getConnectionFlow();
+
+    /**
+     * A publisher that makes it possible to publish (write)
+     * ordered (normal priority) and unordered (high priority)
+     * buffers downstream.
+     */
+    final class PlainHttpPublisher implements HttpPublisher {
+        final Object reading;
+        PlainHttpPublisher() {
+            this(new Object());
+        }
+        PlainHttpPublisher(Object readingLock) {
+            this.reading = readingLock;
+        }
+        final ConcurrentLinkedDeque<List<ByteBuffer>> queue = new ConcurrentLinkedDeque<>();
+        volatile Flow.Subscriber<? super List<ByteBuffer>> subscriber;
+        volatile HttpWriteSubscription subscription;
+        final SequentialScheduler writeScheduler =
+                    new SequentialScheduler(this::flushTask);
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            synchronized (reading) {
+                //assert this.subscription == null;
+                //assert this.subscriber == null;
+                if (subscription == null) {
+                    subscription = new HttpWriteSubscription();
+                }
+                this.subscriber = subscriber;
+            }
+            // TODO: should we do this in the flow?
+            subscriber.onSubscribe(subscription);
+            signal();
+        }
+
+        void flushTask(DeferredCompleter completer) {
+            try {
+                HttpWriteSubscription sub = subscription;
+                if (sub != null) sub.flush();
+            } finally {
+                completer.complete();
+            }
+        }
+
+        void signal() {
+            writeScheduler.runOrSchedule();
+        }
+
+        final class HttpWriteSubscription implements Flow.Subscription {
+            final Demand demand = new Demand();
+
+            @Override
+            public void request(long n) {
+                if (n <= 0) throw new IllegalArgumentException("non-positive request");
+                demand.increase(n);
+                debug.log(Level.DEBUG, () -> "HttpPublisher: got request of "
+                            + n + " from "
+                            + getConnectionFlow());
+                writeScheduler.runOrSchedule();
+            }
+
+            @Override
+            public void cancel() {
+                debug.log(Level.DEBUG, () -> "HttpPublisher: cancelled by "
+                          + getConnectionFlow());
+            }
+
+            void flush() {
+                while (!queue.isEmpty() && demand.tryDecrement()) {
+                    List<ByteBuffer> elem = queue.poll();
+                    debug.log(Level.DEBUG, () -> "HttpPublisher: sending "
+                                + Utils.remaining(elem) + " bytes ("
+                                + elem.size() + " buffers) to "
+                                + getConnectionFlow());
+                    subscriber.onNext(elem);
+                }
+            }
+        }
+
+        @Override
+        public void enqueue(List<ByteBuffer> buffers) throws IOException {
+            queue.add(buffers);
+            int bytes = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
+            debug.log(Level.DEBUG, "added %d bytes to the write queue", bytes);
+        }
+
+        @Override
+        public void enqueueUnordered(List<ByteBuffer> buffers) throws IOException {
+            // Unordered frames are sent before existing frames.
+            int bytes = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
+            queue.addFirst(buffers);
+            debug.log(Level.DEBUG, "inserted %d bytes in the write queue", bytes);
+        }
+
+        @Override
+        public void signalEnqueued() throws IOException {
+            debug.log(Level.DEBUG, "signalling the publisher of the write queue");
+            signal();
+        }
+    }
+
+    String dbgTag = null;
+    final String dbgString() {
+        FlowTube flow = getConnectionFlow();
+        String tag = dbgTag;
+        if (tag == null && flow != null) {
+            dbgTag = tag = this.getClass().getSimpleName() + "(" + flow + ")";
+        } else if (tag == null) {
+            tag = this.getClass().getSimpleName() + "(?)";
+        }
+        return tag;
+    }
+
+    @Override
+    public String toString() {
+        return "HttpConnection: " + channel().toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpRequestBuilderImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.Optional;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpRequest.BodyPublisher;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.incubator.http.internal.common.Utils;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static jdk.incubator.http.internal.common.Utils.isValidName;
+import static jdk.incubator.http.internal.common.Utils.isValidValue;
+
+public class HttpRequestBuilderImpl extends HttpRequest.Builder {
+
+    private HttpHeadersImpl userHeaders;
+    private URI uri;
+    private String method;
+    private boolean expectContinue;
+    private BodyPublisher bodyPublisher;
+    private volatile Optional<HttpClient.Version> version;
+    private Duration duration;
+
+    public HttpRequestBuilderImpl(URI uri) {
+        requireNonNull(uri, "uri must be non-null");
+        checkURI(uri);
+        this.uri = uri;
+        this.userHeaders = new HttpHeadersImpl();
+        this.method = "GET"; // default, as per spec
+        this.version = Optional.empty();
+    }
+
+    public HttpRequestBuilderImpl() {
+        this.userHeaders = new HttpHeadersImpl();
+        this.method = "GET"; // default, as per spec
+        this.version = Optional.empty();
+    }
+
+    @Override
+    public HttpRequestBuilderImpl uri(URI uri) {
+        requireNonNull(uri, "uri must be non-null");
+        checkURI(uri);
+        this.uri = uri;
+        return this;
+    }
+
+    private static IllegalArgumentException newIAE(String message, Object... args) {
+        return new IllegalArgumentException(format(message, args));
+    }
+
+    private static void checkURI(URI uri) {
+        String scheme = uri.getScheme();
+        if (scheme == null)
+            throw newIAE("URI with undefined scheme");
+        scheme = scheme.toLowerCase();
+        if (!(scheme.equals("https") || scheme.equals("http"))) {
+            throw newIAE("invalid URI scheme %s", scheme);
+        }
+        if (uri.getHost() == null) {
+            throw newIAE("unsupported URI %s", uri);
+        }
+    }
+
+    @Override
+    public HttpRequestBuilderImpl copy() {
+        HttpRequestBuilderImpl b = new HttpRequestBuilderImpl(this.uri);
+        b.userHeaders = this.userHeaders.deepCopy();
+        b.method = this.method;
+        b.expectContinue = this.expectContinue;
+        b.bodyPublisher = bodyPublisher;
+        b.uri = uri;
+        b.duration = duration;
+        b.version = version;
+        return b;
+    }
+
+    private void checkNameAndValue(String name, String value) {
+        requireNonNull(name, "name");
+        requireNonNull(value, "value");
+        if (!isValidName(name)) {
+            throw newIAE("invalid header name: \"%s\"", name);
+        }
+        if (!Utils.ALLOWED_HEADERS.test(name)) {
+            throw newIAE("restricted header name: \"%s\"", name);
+        }
+        if (!isValidValue(value)) {
+            throw newIAE("invalid header value: \"%s\"", value);
+        }
+    }
+
+    @Override
+    public HttpRequestBuilderImpl setHeader(String name, String value) {
+        checkNameAndValue(name, value);
+        userHeaders.setHeader(name, value);
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl header(String name, String value) {
+        checkNameAndValue(name, value);
+        userHeaders.addHeader(name, value);
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl headers(String... params) {
+        requireNonNull(params);
+        if (params.length == 0 || params.length % 2 != 0) {
+            throw newIAE("wrong number, %d, of parameters", params.length);
+        }
+        for (int i = 0; i < params.length; i += 2) {
+            String name  = params[i];
+            String value = params[i + 1];
+            header(name, value);
+        }
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl expectContinue(boolean enable) {
+        expectContinue = enable;
+        return this;
+    }
+
+    @Override
+    public HttpRequestBuilderImpl version(HttpClient.Version version) {
+        requireNonNull(version);
+        this.version = Optional.of(version);
+        return this;
+    }
+
+    HttpHeadersImpl headers() {  return userHeaders; }
+
+    URI uri() { return uri; }
+
+    String method() { return method; }
+
+    boolean expectContinue() { return expectContinue; }
+
+    BodyPublisher bodyPublisher() { return bodyPublisher; }
+
+    Optional<HttpClient.Version> version() { return version; }
+
+    @Override
+    public HttpRequest.Builder GET() {
+        return method0("GET", null);
+    }
+
+    @Override
+    public HttpRequest.Builder POST(BodyPublisher body) {
+        return method0("POST", requireNonNull(body));
+    }
+
+    @Override
+    public HttpRequest.Builder DELETE(BodyPublisher body) {
+        return method0("DELETE", requireNonNull(body));
+    }
+
+    @Override
+    public HttpRequest.Builder PUT(BodyPublisher body) {
+        return method0("PUT", requireNonNull(body));
+    }
+
+    @Override
+    public HttpRequest.Builder method(String method, BodyPublisher body) {
+        requireNonNull(method);
+        if (method.equals(""))
+            throw newIAE("illegal method <empty string>");
+        if (method.equals("CONNECT"))
+            throw newIAE("method CONNECT is not supported");
+        return method0(method, requireNonNull(body));
+    }
+
+    private HttpRequest.Builder method0(String method, BodyPublisher body) {
+        assert method != null;
+        assert !method.equals("GET") ? body != null : true;
+        assert !method.equals("");
+        this.method = method;
+        this.bodyPublisher = body;
+        return this;
+    }
+
+    @Override
+    public HttpRequest build() {
+        if (uri == null)
+            throw new IllegalStateException("uri is null");
+        assert method != null;
+        return new HttpRequestImpl(this);
+    }
+
+    @Override
+    public HttpRequest.Builder timeout(Duration duration) {
+        requireNonNull(duration);
+        if (duration.isNegative() || Duration.ZERO.equals(duration))
+            throw new IllegalArgumentException("Invalid duration: " + duration);
+        this.duration = duration;
+        return this;
+    }
+
+    Duration timeout() { return duration; }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpRequestImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.time.Duration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.incubator.http.internal.websocket.WebSocketRequest;
+
+import static jdk.incubator.http.internal.common.Utils.ALLOWED_HEADERS;
+
+class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
+
+    private final HttpHeaders userHeaders;
+    private final HttpHeadersImpl systemHeaders;
+    private final URI uri;
+    private volatile Proxy proxy; // ensure safe publishing
+    private final InetSocketAddress authority; // only used when URI not specified
+    private final String method;
+    final BodyPublisher requestPublisher;
+    final boolean secure;
+    final boolean expectContinue;
+    private volatile boolean isWebSocket;
+    private volatile AccessControlContext acc;
+    private final Duration timeout;  // may be null
+    private final Optional<HttpClient.Version> version;
+
+    private static String userAgent() {
+        PrivilegedAction<String> pa = () -> System.getProperty("java.version");
+        String version = AccessController.doPrivileged(pa);
+        return "Java-http-client/" + version;
+    }
+
+    /** The value of the User-Agent header for all requests sent by the client. */
+    public static final String USER_AGENT = userAgent();
+
+    /**
+     * Creates an HttpRequestImpl from the given builder.
+     */
+    public HttpRequestImpl(HttpRequestBuilderImpl builder) {
+        String method = builder.method();
+        this.method = method == null ? "GET" : method;
+        this.userHeaders = ImmutableHeaders.of(builder.headers().map(), ALLOWED_HEADERS);
+        this.systemHeaders = new HttpHeadersImpl();
+        this.uri = builder.uri();
+        assert uri != null;
+        this.proxy = null;
+        this.expectContinue = builder.expectContinue();
+        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
+        this.requestPublisher = builder.bodyPublisher();  // may be null
+        this.timeout = builder.timeout();
+        this.version = builder.version();
+        this.authority = null;
+    }
+
+    /**
+     * Creates an HttpRequestImpl from the given request.
+     */
+    public HttpRequestImpl(HttpRequest request, ProxySelector ps, AccessControlContext acc) {
+        String method = request.method();
+        this.method = method == null ? "GET" : method;
+        this.userHeaders = request.headers();
+        if (request instanceof HttpRequestImpl) {
+            this.systemHeaders = ((HttpRequestImpl) request).systemHeaders;
+            this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
+        } else {
+            this.systemHeaders = new HttpHeadersImpl();
+        }
+        this.systemHeaders.setHeader("User-Agent", USER_AGENT);
+        this.uri = request.uri();
+        if (isWebSocket) {
+            // WebSocket determines and sets the proxy itself
+            this.proxy = ((HttpRequestImpl) request).proxy;
+        } else {
+            if (ps != null)
+                this.proxy = retrieveProxy(ps, uri);
+            else
+                this.proxy = null;
+        }
+        this.expectContinue = request.expectContinue();
+        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
+        this.requestPublisher = request.bodyPublisher().orElse(null);
+        if (acc != null && requestPublisher instanceof RequestPublishers.FilePublisher) {
+            // Restricts the file publisher with the senders ACC, if any
+            ((RequestPublishers.FilePublisher)requestPublisher).setAccessControlContext(acc);
+        }
+        this.timeout = request.timeout().orElse(null);
+        this.version = request.version();
+        this.authority = null;
+    }
+
+    /** Creates a HttpRequestImpl using fields of an existing request impl. */
+    public HttpRequestImpl(URI uri,
+                           String method,
+                           HttpRequestImpl other) {
+        this.method = method == null? "GET" : method;
+        this.userHeaders = other.userHeaders;
+        this.isWebSocket = other.isWebSocket;
+        this.systemHeaders = other.systemHeaders;
+        this.uri = uri;
+        this.proxy = other.proxy;
+        this.expectContinue = other.expectContinue;
+        this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
+        this.requestPublisher = other.requestPublisher;  // may be null
+        this.acc = other.acc;
+        this.timeout = other.timeout;
+        this.version = other.version();
+        this.authority = null;
+    }
+
+    /* used for creating CONNECT requests  */
+    HttpRequestImpl(String method, InetSocketAddress authority, HttpHeaders headers) {
+        // TODO: isWebSocket flag is not specified, but the assumption is that
+        // such a request will never be made on a connection that will be returned
+        // to the connection pool (we might need to revisit this constructor later)
+        assert "CONNECT".equalsIgnoreCase(method);
+        this.method = method;
+        this.systemHeaders = new HttpHeadersImpl();
+        this.userHeaders = ImmutableHeaders.of(headers);
+        this.uri = URI.create("socket://" + authority.getHostString() + ":"
+                              + Integer.toString(authority.getPort()) + "/");
+        this.proxy = null;
+        this.requestPublisher = null;
+        this.authority = authority;
+        this.secure = false;
+        this.expectContinue = false;
+        this.timeout = null;
+        // The CONNECT request sent for tunneling is only used in two cases:
+        //   1. websocket, which only supports HTTP/1.1
+        //   2. SSL tunneling through a HTTP/1.1 proxy
+        // In either case we do not want to upgrade the connection to the proxy.
+        // What we want to possibly upgrade is the tunneled connection to the
+        // target server (so not the CONNECT request itself)
+        this.version = Optional.of(HttpClient.Version.HTTP_1_1);
+    }
+
+    final boolean isConnect() {
+        return "CONNECT".equalsIgnoreCase(method);
+    }
+
+    /**
+     * Creates a HttpRequestImpl from the given set of Headers and the associated
+     * "parent" request. Fields not taken from the headers are taken from the
+     * parent.
+     */
+    static HttpRequestImpl createPushRequest(HttpRequestImpl parent,
+                                             HttpHeadersImpl headers)
+        throws IOException
+    {
+        return new HttpRequestImpl(parent, headers);
+    }
+
+    // only used for push requests
+    private HttpRequestImpl(HttpRequestImpl parent, HttpHeadersImpl headers)
+        throws IOException
+    {
+        this.method = headers.firstValue(":method")
+                .orElseThrow(() -> new IOException("No method in Push Promise"));
+        String path = headers.firstValue(":path")
+                .orElseThrow(() -> new IOException("No path in Push Promise"));
+        String scheme = headers.firstValue(":scheme")
+                .orElseThrow(() -> new IOException("No scheme in Push Promise"));
+        String authority = headers.firstValue(":authority")
+                .orElseThrow(() -> new IOException("No authority in Push Promise"));
+        StringBuilder sb = new StringBuilder();
+        sb.append(scheme).append("://").append(authority).append(path);
+        this.uri = URI.create(sb.toString());
+        this.proxy = null;
+        this.userHeaders = ImmutableHeaders.of(headers.map(), ALLOWED_HEADERS);
+        this.systemHeaders = parent.systemHeaders;
+        this.expectContinue = parent.expectContinue;
+        this.secure = parent.secure;
+        this.requestPublisher = parent.requestPublisher;
+        this.acc = parent.acc;
+        this.timeout = parent.timeout;
+        this.version = parent.version;
+        this.authority = null;
+    }
+
+    @Override
+    public String toString() {
+        return (uri == null ? "" : uri.toString()) + " " + method;
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        return userHeaders;
+    }
+
+    InetSocketAddress authority() { return authority; }
+
+    void setH2Upgrade(Http2ClientImpl h2client) {
+        systemHeaders.setHeader("Connection", "Upgrade, HTTP2-Settings");
+        systemHeaders.setHeader("Upgrade", "h2c");
+        systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
+    }
+
+    @Override
+    public boolean expectContinue() { return expectContinue; }
+
+    /** Retrieves the proxy, from the given ProxySelector, if there is one. */
+    private static Proxy retrieveProxy(ProxySelector ps, URI uri) {
+        Proxy proxy = null;
+        List<Proxy> pl = ps.select(uri);
+        if (!pl.isEmpty()) {
+            Proxy p = pl.get(0);
+            if (p.type() == Proxy.Type.HTTP)
+                proxy = p;
+        }
+        return proxy;
+    }
+
+    InetSocketAddress proxy() {
+        if (proxy == null || proxy.type() != Proxy.Type.HTTP
+                || method.equalsIgnoreCase("CONNECT")) {
+            return null;
+        }
+        return (InetSocketAddress)proxy.address();
+    }
+
+    boolean secure() { return secure; }
+
+    @Override
+    public void setProxy(Proxy proxy) {
+        assert isWebSocket;
+        this.proxy = proxy;
+    }
+
+    @Override
+    public void isWebSocket(boolean is) {
+        isWebSocket = is;
+    }
+
+    boolean isWebSocket() {
+        return isWebSocket;
+    }
+
+    @Override
+    public Optional<BodyPublisher> bodyPublisher() {
+        return requestPublisher == null ? Optional.empty()
+                                        : Optional.of(requestPublisher);
+    }
+
+    /**
+     * 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; }
+
+    @Override
+    public Optional<Duration> timeout() {
+        return timeout == null ? Optional.empty() : Optional.of(timeout);
+    }
+
+    HttpHeaders getUserHeaders() { return userHeaders; }
+
+    HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
+
+    @Override
+    public Optional<HttpClient.Version> version() { return version; }
+
+    void addSystemHeader(String name, String value) {
+        systemHeaders.addHeader(name, value);
+    }
+
+    @Override
+    public void setSystemHeader(String name, String value) {
+        systemHeaders.setHeader(name, value);
+    }
+
+    InetSocketAddress getAddress() {
+        URI uri = uri();
+        if (uri == null) {
+            return authority();
+        }
+        int p = uri.getPort();
+        if (p == -1) {
+            if (uri.getScheme().equalsIgnoreCase("https")) {
+                p = 443;
+            } else {
+                p = 80;
+            }
+        }
+        final String host = uri.getHost();
+        final int port = p;
+        if (proxy() == null) {
+            PrivilegedAction<InetSocketAddress> pa = () -> new InetSocketAddress(host, port);
+            return AccessController.doPrivileged(pa);
+        } else {
+            return InetSocketAddress.createUnresolved(host, port);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/HttpResponseImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import javax.net.ssl.SSLParameters;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.internal.websocket.RawChannel;
+
+/**
+ * The implementation class for HttpResponse
+ */
+class HttpResponseImpl<T> extends HttpResponse<T> implements RawChannel.Provider {
+
+    final int responseCode;
+    final Exchange<T> exchange;
+    final HttpRequest initialRequest;
+    final Optional<HttpResponse<T>> previousResponse;
+    final HttpHeaders headers;
+    final SSLParameters sslParameters;
+    final URI uri;
+    final HttpClient.Version version;
+    RawChannel rawchan;
+    final HttpConnection connection;
+    final Stream<T> stream;
+    final T body;
+
+    public HttpResponseImpl(HttpRequest initialRequest,
+                            Response response,
+                            HttpResponse<T> previousResponse,
+                            T body,
+                            Exchange<T> exch) {
+        this.responseCode = response.statusCode();
+        this.exchange = exch;
+        this.initialRequest = initialRequest;
+        this.previousResponse = Optional.ofNullable(previousResponse);
+        this.headers = response.headers();
+        //this.trailers = trailers;
+        this.sslParameters = exch.client().sslParameters();
+        this.uri = response.request().uri();
+        this.version = response.version();
+        this.connection = connection(exch);
+        this.stream = null;
+        this.body = body;
+    }
+
+    private HttpConnection connection(Exchange<?> exch) {
+        if (exch == null || exch.exchImpl == null) {
+            assert responseCode == 407;
+            return null; // case of Proxy 407
+        }
+        return exch.exchImpl.connection();
+    }
+
+    private ExchangeImpl<?> exchangeImpl() {
+        return exchange != null ? exchange.exchImpl : stream;
+    }
+
+    @Override
+    public int statusCode() {
+        return responseCode;
+    }
+
+    @Override
+    public HttpRequest request() {
+        return initialRequest;
+    }
+
+    @Override
+    public Optional<HttpResponse<T>> previousResponse() {
+        return previousResponse;
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        return headers;
+    }
+
+    @Override
+    public T body() {
+        return body;
+    }
+
+    @Override
+    public SSLParameters sslParameters() {
+        return sslParameters;
+    }
+
+    @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
+
+    /**
+     * Returns a RawChannel that may be used for WebSocket protocol.
+     * @implNote This implementation does not support RawChannel over
+     *           HTTP/2 connections.
+     * @return a RawChannel that may be used for WebSocket protocol.
+     * @throws UnsupportedOperationException if getting a RawChannel over
+     *         this connection is not supported.
+     * @throws IOException if an I/O exception occurs while retrieving
+     *         the channel.
+     */
+    @Override
+    public synchronized RawChannel rawChannel() throws IOException {
+        if (rawchan == null) {
+            ExchangeImpl<?> exchImpl = exchangeImpl();
+            if (!(exchImpl instanceof Http1Exchange)) {
+                // RawChannel is only used for WebSocket - and WebSocket
+                // is not supported over HTTP/2 yet, so we should not come
+                // here. Getting a RawChannel over HTTP/2 might be supported
+                // in the future, but it would entail retrieving any left over
+                // bytes that might have been read but not consumed by the
+                // HTTP/2 connection.
+                throw new UnsupportedOperationException("RawChannel is not supported over HTTP/2");
+            }
+            // Http1Exchange may have some remaining bytes in its
+            // internal buffer.
+            Supplier<ByteBuffer> initial = ((Http1Exchange<?>)exchImpl)::drainLeftOverBytes;
+            rawchan = new RawChannelImpl(exchange.client(), connection, initial);
+        }
+        return rawchan;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        String method = request().method();
+        URI uri = request().uri();
+        String uristring = uri == null ? "" : uri.toString();
+        sb.append('(')
+          .append(method)
+          .append(" ")
+          .append(uristring)
+          .append(") ")
+          .append(statusCode());
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ImmutableHeaders.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import jdk.incubator.http.HttpHeaders;
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.unmodifiableList;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Objects.requireNonNull;
+
+final class ImmutableHeaders extends HttpHeaders {
+
+    private final Map<String, List<String>> map;
+
+    public static ImmutableHeaders empty() {
+        return of(emptyMap());
+    }
+
+    public static ImmutableHeaders of(Map<String, List<String>> src) {
+        return of(src, x -> true);
+    }
+
+    public static ImmutableHeaders of(HttpHeaders headers) {
+        return (headers instanceof ImmutableHeaders)
+                ? (ImmutableHeaders)headers
+                : of(headers.map());
+    }
+
+    public static ImmutableHeaders of(Map<String, List<String>> src,
+                                      Predicate<? super String> keyAllowed) {
+        requireNonNull(src, "src");
+        requireNonNull(keyAllowed, "keyAllowed");
+        return new ImmutableHeaders(src, headerAllowed(keyAllowed));
+    }
+
+    public static ImmutableHeaders of(Map<String, List<String>> src,
+                                      BiPredicate<? super String, ? super List<String>> headerAllowed) {
+        requireNonNull(src, "src");
+        requireNonNull(headerAllowed, "headerAllowed");
+        return new ImmutableHeaders(src, headerAllowed);
+    }
+
+    private ImmutableHeaders(Map<String, List<String>> src,
+                             BiPredicate<? super String, ? super List<String>> headerAllowed) {
+        Map<String, List<String>> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        src.entrySet().stream()
+                .filter(e -> headerAllowed.test(e.getKey(), e.getValue()))
+                .forEach(e ->
+                        {
+                            List<String> values = new ArrayList<>(e.getValue());
+                            m.put(e.getKey(), unmodifiableList(values));
+                        }
+                );
+        this.map = unmodifiableMap(m);
+    }
+
+    private static BiPredicate<String, List<String>> headerAllowed(Predicate<? super String> keyAllowed) {
+        return (n,v) -> keyAllowed.test(n);
+    }
+
+    @Override
+    public Map<String, List<String>> map() {
+        return map;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/MultiExchange.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.time.Duration;
+import java.util.List;
+import java.security.AccessControlContext;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpResponse.PushPromiseHandler;
+import jdk.incubator.http.HttpTimeoutException;
+import jdk.incubator.http.internal.UntrustedBodyHandler;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.ConnectionExpiredException;
+import jdk.incubator.http.internal.common.Utils;
+import static jdk.incubator.http.internal.common.MinimalFuture.completedFuture;
+import static jdk.incubator.http.internal.common.MinimalFuture.failedFuture;
+
+/**
+ * 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<T> {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    static final System.Logger DEBUG_LOGGER =
+            Utils.getDebugLogger("MultiExchange"::toString, DEBUG);
+
+    private final HttpRequest userRequest; // the user request
+    private final HttpRequestImpl request; // a copy of the user request
+    final AccessControlContext acc;
+    final HttpClientImpl client;
+    final HttpResponse.BodyHandler<T> responseHandler;
+    final Executor executor;
+    final AtomicInteger attempts = new AtomicInteger();
+    HttpRequestImpl currentreq; // used for async only
+    Exchange<T> exchange; // the current exchange
+    Exchange<T> previous;
+    volatile Throwable retryCause;
+    volatile boolean expiredOnce;
+    volatile HttpResponse<T> response = null;
+
+    // Maximum number of times a request will be retried/redirected
+    // for any reason
+
+    static final int DEFAULT_MAX_ATTEMPTS = 5;
+    static final int max_attempts = Utils.getIntegerNetProperty(
+            "jdk.httpclient.redirects.retrylimit", DEFAULT_MAX_ATTEMPTS
+    );
+
+    private final List<HeaderFilter> filters;
+    TimedEvent timedEvent;
+    volatile boolean cancelled;
+    final PushGroup<T> pushGroup;
+
+    /**
+     * 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 with one final response.
+     */
+    MultiExchange(HttpRequest userRequest,
+                  HttpRequestImpl requestImpl,
+                  HttpClientImpl client,
+                  HttpResponse.BodyHandler<T> responseHandler,
+                  PushPromiseHandler<T> pushPromiseHandler,
+                  AccessControlContext acc) {
+        this.previous = null;
+        this.userRequest = userRequest;
+        this.request = requestImpl;
+        this.currentreq = request;
+        this.client = client;
+        this.filters = client.filterChain();
+        this.acc = acc;
+        this.executor = client.theExecutor();
+        this.responseHandler = responseHandler;
+        if (acc != null) {
+            // Restricts the file publisher with the senders ACC, if any
+            if (responseHandler instanceof UntrustedBodyHandler)
+                ((UntrustedBodyHandler)this.responseHandler).setAccessControlContext(acc);
+        }
+
+        if (pushPromiseHandler != null) {
+            this.pushGroup = new PushGroup<>(pushPromiseHandler, request, acc);
+        } else {
+            pushGroup = null;
+        }
+
+        this.exchange = new Exchange<>(request, this);
+    }
+
+    private synchronized Exchange<T> getExchange() {
+        return exchange;
+    }
+
+    HttpClientImpl client() {
+        return client;
+    }
+
+    HttpClient.Version version() {
+        HttpClient.Version vers = request.version().orElse(client.version());
+        if (vers == HttpClient.Version.HTTP_2 && !request.secure() && request.proxy() != null)
+            vers = HttpClient.Version.HTTP_1_1;
+        return vers;
+    }
+
+    private synchronized void setExchange(Exchange<T> exchange) {
+        if (this.exchange != null && exchange != this.exchange) {
+            this.exchange.released();
+        }
+        this.exchange = exchange;
+    }
+
+    private void cancelTimer() {
+        if (timedEvent != null) {
+            client.cancelTimer(timedEvent);
+        }
+    }
+
+    private void requestFilters(HttpRequestImpl r) throws IOException {
+        Log.logTrace("Applying request filters");
+        for (HeaderFilter filter : filters) {
+            Log.logTrace("Applying {0}", filter);
+            filter.request(r, this);
+        }
+        Log.logTrace("All filters applied");
+    }
+
+    private HttpRequestImpl responseFilters(Response response) throws IOException
+    {
+        Log.logTrace("Applying response filters");
+        for (HeaderFilter filter : filters) {
+            Log.logTrace("Applying {0}", filter);
+            HttpRequestImpl newreq = filter.response(response);
+            if (newreq != null) {
+                Log.logTrace("New request: stopping filters");
+                return newreq;
+            }
+        }
+        Log.logTrace("All filters applied");
+        return null;
+    }
+
+//    public void cancel() {
+//        cancelled = true;
+//        getExchange().cancel();
+//    }
+
+    public void cancel(IOException cause) {
+        cancelled = true;
+        getExchange().cancel(cause);
+    }
+
+    public CompletableFuture<HttpResponse<T>> responseAsync() {
+        CompletableFuture<Void> start = new MinimalFuture<>();
+        CompletableFuture<HttpResponse<T>> cf = responseAsync0(start);
+        start.completeAsync( () -> null, executor); // trigger execution
+        return cf;
+    }
+
+    private CompletableFuture<HttpResponse<T>>
+    responseAsync0(CompletableFuture<Void> start) {
+        return start.thenCompose( v -> responseAsyncImpl())
+                    .thenCompose((Response r) -> {
+                        Exchange<T> exch = getExchange();
+                        return exch.readBodyAsync(responseHandler)
+                            .thenApply((T body) -> {
+                                this.response =
+                                    new HttpResponseImpl<>(userRequest, r, this.response, body, exch);
+                                return this.response;
+                            });
+                    });
+    }
+
+    private CompletableFuture<Response> responseAsyncImpl() {
+        CompletableFuture<Response> cf;
+        if (attempts.incrementAndGet() > max_attempts) {
+            cf = failedFuture(new IOException("Too many retries", retryCause));
+        } else {
+            if (currentreq.timeout().isPresent()) {
+                timedEvent = new TimedEvent(currentreq.timeout().get());
+                client.registerTimer(timedEvent);
+            }
+            try {
+                // 1. apply request filters
+                requestFilters(currentreq);
+            } catch (IOException e) {
+                return failedFuture(e);
+            }
+            Exchange<T> exch = getExchange();
+            // 2. get response
+            cf = exch.responseAsync()
+                     .thenCompose((Response response) -> {
+                        HttpRequestImpl newrequest;
+                        try {
+                            // 3. apply response filters
+                            newrequest = responseFilters(response);
+                        } catch (IOException e) {
+                            return failedFuture(e);
+                        }
+                        // 4. check filter result and repeat or continue
+                        if (newrequest == null) {
+                            if (attempts.get() > 1) {
+                                Log.logError("Succeeded on attempt: " + attempts);
+                            }
+                            return completedFuture(response);
+                        } else {
+                            this.response =
+                                new HttpResponseImpl<>(currentreq, response, this.response, null, exch);
+                            Exchange<T> oldExch = exch;
+                            return exch.ignoreBody().handle((r,t) -> {
+                                currentreq = newrequest;
+                                expiredOnce = false;
+                                setExchange(new Exchange<>(currentreq, this, acc));
+                                return responseAsyncImpl();
+                            }).thenCompose(Function.identity());
+                        } })
+                     .handle((response, ex) -> {
+                        // 5. handle errors and cancel any timer set
+                        cancelTimer();
+                        if (ex == null) {
+                            assert response != null;
+                            return completedFuture(response);
+                        }
+                        // all exceptions thrown are handled here
+                        CompletableFuture<Response> errorCF = getExceptionalCF(ex);
+                        if (errorCF == null) {
+                            return responseAsyncImpl();
+                        } else {
+                            return errorCF;
+                        } })
+                     .thenCompose(Function.identity());
+        }
+        return cf;
+    }
+
+    /**
+     * Takes a Throwable and returns a suitable CompletableFuture that is
+     * completed exceptionally, or null.
+     */
+    private CompletableFuture<Response> getExceptionalCF(Throwable t) {
+        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");
+        } else if (t instanceof ConnectionExpiredException) {
+            // allow the retry mechanism to do its work
+            // ####: method (GET,HEAD, not POST?), no bytes written or read ( differentiate? )
+            if (t.getCause() != null) retryCause = t.getCause();
+            if (!expiredOnce) {
+                DEBUG_LOGGER.log(Level.DEBUG,
+                    "MultiExchange: ConnectionExpiredException (async): retrying...",
+                    t);
+                expiredOnce = true;
+                return null;
+            } else {
+                DEBUG_LOGGER.log(Level.DEBUG,
+                    "MultiExchange: ConnectionExpiredException (async): already retried once.",
+                    t);
+                if (t.getCause() != null) t = t.getCause();
+            }
+        }
+        return failedFuture(t);
+    }
+
+    class TimedEvent extends TimeoutEvent {
+        TimedEvent(Duration duration) {
+            super(duration);
+        }
+        @Override
+        public void handle() {
+            DEBUG_LOGGER.log(Level.DEBUG,
+                    "Cancelling MultiExchange due to timeout for request %s",
+                     request);
+            cancel(new HttpTimeoutException("request timed out"));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PlainHttpConnection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+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.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.CompletableFuture;
+import jdk.incubator.http.internal.common.FlowTube;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * Plain raw TCP connection direct to destination.
+ * The connection operates in asynchronous non-blocking mode.
+ * All reads and writes are done non-blocking.
+ */
+class PlainHttpConnection extends HttpConnection {
+
+    private final Object reading = new Object();
+    protected final SocketChannel chan;
+    private final FlowTube tube;
+    private final PlainHttpPublisher writePublisher = new PlainHttpPublisher(reading);
+    private volatile boolean connected;
+    private boolean closed;
+
+    // should be volatile to provide proper synchronization(visibility) action
+
+    final class ConnectEvent extends AsyncEvent {
+        private final 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 {
+                assert !connected : "Already connected";
+                assert !chan.isBlocking() : "Unexpected blocking channel";
+                debug.log(Level.DEBUG, "ConnectEvent: finishing connect");
+                boolean finished = chan.finishConnect();
+                assert finished : "Expected channel to be connected";
+                debug.log(Level.DEBUG,
+                          "ConnectEvent: connect finished: %s Local addr: %s", finished, chan.getLocalAddress());
+                connected = true;
+                // complete async since the event runs on the SelectorManager thread
+                cf.completeAsync(() -> null, client().theExecutor());
+            } catch (Throwable e) {
+                client().theExecutor().execute( () -> cf.completeExceptionally(e));
+            }
+        }
+
+        @Override
+        public void abort(IOException ioe) {
+            close();
+            client().theExecutor().execute( () -> cf.completeExceptionally(ioe));
+        }
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        CompletableFuture<Void> cf = new MinimalFuture<>();
+        try {
+            assert !connected : "Already connected";
+            assert !chan.isBlocking() : "Unexpected blocking channel";
+            boolean finished = false;
+            PrivilegedExceptionAction<Boolean> pa = () -> chan.connect(address);
+            try {
+                 finished = AccessController.doPrivileged(pa);
+            } catch (PrivilegedActionException e) {
+                cf.completeExceptionally(e.getCause());
+            }
+            if (finished) {
+                debug.log(Level.DEBUG, "connect finished without blocking");
+                connected = true;
+                cf.complete(null);
+            } else {
+                debug.log(Level.DEBUG, "registering connect event");
+                client().registerEvent(new ConnectEvent(cf));
+            }
+        } catch (Throwable throwable) {
+            cf.completeExceptionally(throwable);
+        }
+        return cf;
+    }
+
+    @Override
+    SocketChannel channel() {
+        return chan;
+    }
+
+    @Override
+    final FlowTube getConnectionFlow() {
+        return tube;
+    }
+
+    PlainHttpConnection(InetSocketAddress addr, HttpClientImpl client) {
+        super(addr, client);
+        try {
+            this.chan = SocketChannel.open();
+            chan.configureBlocking(false);
+            int bufsize = client.getReceiveBufferSize();
+            if (!trySetReceiveBufferSize(bufsize)) {
+                trySetReceiveBufferSize(256*1024);
+            }
+            chan.setOption(StandardSocketOptions.TCP_NODELAY, true);
+            // wrap the connected channel in a Tube for async reading and writing
+            tube = new SocketTube(client(), chan, Utils::getBuffer);
+        } catch (IOException e) {
+            throw new InternalError(e);
+        }
+    }
+
+    private boolean trySetReceiveBufferSize(int bufsize) {
+        try {
+            chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
+            return true;
+        } catch(IOException x) {
+            debug.log(Level.DEBUG,
+                    "Failed to set receive buffer size to %d on %s",
+                    bufsize, chan);
+        }
+        return false;
+    }
+
+    @Override
+    HttpPublisher publisher() { return writePublisher; }
+
+
+    @Override
+    public String toString() {
+        return "PlainHttpConnection: " + super.toString();
+    }
+
+    /**
+     * Closes this connection
+     */
+    @Override
+    public synchronized void close() {
+        if (closed) {
+            return;
+        }
+        closed = true;
+        try {
+            Log.logTrace("Closing: " + toString());
+            chan.close();
+        } catch (IOException e) {}
+    }
+
+    @Override
+    void shutdownInput() throws IOException {
+        debug.log(Level.DEBUG, "Shutting down input");
+        chan.shutdownInput();
+    }
+
+    @Override
+    void shutdownOutput() throws IOException {
+        debug.log(Level.DEBUG, "Shutting down output");
+        chan.shutdownOutput();
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return new ConnectionPool.CacheKey(address, null);
+    }
+
+    @Override
+    synchronized boolean connected() {
+        return connected;
+    }
+
+
+    @Override
+    boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    boolean isProxied() {
+        return false;
+    }
+
+    // Support for WebSocket/RawChannelImpl which unfortunately
+    // still depends on synchronous read/writes.
+    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
+    private static final class PlainDetachedChannel
+            extends DetachedConnectionChannel {
+        final PlainHttpConnection plainConnection;
+        boolean closed;
+        PlainDetachedChannel(PlainHttpConnection conn) {
+            // We're handing the connection channel over to a web socket.
+            // We need the selector manager's thread to stay alive until
+            // the WebSocket is closed.
+            conn.client().webSocketOpen();
+            this.plainConnection = conn;
+        }
+
+        @Override
+        SocketChannel channel() {
+            return plainConnection.channel();
+        }
+
+        @Override
+        ByteBuffer read() throws IOException {
+            ByteBuffer dst = ByteBuffer.allocate(8192);
+            int n = readImpl(dst);
+            if (n > 0) {
+                return dst;
+            } else if (n == 0) {
+                return Utils.EMPTY_BYTEBUFFER;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public void close() {
+            HttpClientImpl client = plainConnection.client();
+            try {
+                plainConnection.close();
+            } finally {
+                // notify the HttpClientImpl that the websocket is no
+                // no longer operating.
+                synchronized(this) {
+                    if (closed == true) return;
+                    closed = true;
+                }
+                client.webSocketClose();
+            }
+        }
+
+        @Override
+        public long write(ByteBuffer[] buffers, int start, int number)
+                throws IOException
+        {
+            return channel().write(buffers, start, number);
+        }
+
+        @Override
+        public void shutdownInput() throws IOException {
+            plainConnection.shutdownInput();
+        }
+
+        @Override
+        public void shutdownOutput() throws IOException {
+            plainConnection.shutdownOutput();
+        }
+
+        private int readImpl(ByteBuffer buf) throws IOException {
+            int mark = buf.position();
+            int n;
+            n = channel().read(buf);
+            if (n == -1) {
+                return -1;
+            }
+            Utils.flipToMark(buf, mark);
+            return n;
+        }
+    }
+
+    // Support for WebSocket/RawChannelImpl which unfortunately
+    // still depends on synchronous read/writes.
+    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
+    @Override
+    DetachedConnectionChannel detachChannel() {
+        client().cancelRegistration(channel());
+        return new PlainDetachedChannel(this);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PlainProxyConnection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PlainTunnelingConnection.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.internal.common.FlowTube;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
+
+/**
+ * A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
+ * encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.
+ * Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.
+ */
+final class PlainTunnelingConnection extends HttpConnection {
+
+    final PlainHttpConnection delegate;
+    final HttpHeaders proxyHeaders;
+    final InetSocketAddress proxyAddr;
+    private volatile boolean connected;
+
+    protected PlainTunnelingConnection(InetSocketAddress addr,
+                                       InetSocketAddress proxy,
+                                       HttpClientImpl client,
+                                       HttpHeaders proxyHeaders) {
+        super(addr, client);
+        this.proxyAddr = proxy;
+        this.proxyHeaders = proxyHeaders;
+        delegate = new PlainHttpConnection(proxy, client);
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        debug.log(Level.DEBUG, "Connecting plain connection");
+        return delegate.connectAsync()
+            .thenCompose((Void v) -> {
+                debug.log(Level.DEBUG, "sending HTTP/1.1 CONNECT");
+                HttpClientImpl client = client();
+                assert client != null;
+                HttpRequestImpl req = new HttpRequestImpl("CONNECT", address, proxyHeaders);
+                MultiExchange<Void> mulEx = new MultiExchange<>(null, req,
+                        client, discard(null), null, null);
+                Exchange<Void> connectExchange = new Exchange<>(req, mulEx);
+
+                return connectExchange
+                        .responseAsyncImpl(delegate)
+                        .thenCompose((Response resp) -> {
+                            CompletableFuture<Void> cf = new MinimalFuture<>();
+                            debug.log(Level.DEBUG, "got response: %d", resp.statusCode());
+                            if (resp.statusCode() == 407) {
+                                return connectExchange.ignoreBody().handle((r,t) -> {
+                                    // close delegate after reading body: we won't
+                                    // be reusing that connection anyway.
+                                    delegate.close();
+                                    ProxyAuthenticationRequired authenticationRequired =
+                                            new ProxyAuthenticationRequired(resp);
+                                    cf.completeExceptionally(authenticationRequired);
+                                    return cf;
+                                }).thenCompose(Function.identity());
+                            } else if (resp.statusCode() != 200) {
+                                delegate.close();
+                                cf.completeExceptionally(new IOException(
+                                        "Tunnel failed, got: "+ resp.statusCode()));
+                            } else {
+                                // get the initial/remaining bytes
+                                ByteBuffer b = ((Http1Exchange<?>)connectExchange.exchImpl).drainLeftOverBytes();
+                                int remaining = b.remaining();
+                                assert remaining == 0: "Unexpected remaining: " + remaining;
+                                connected = true;
+                                cf.complete(null);
+                            }
+                            return cf;
+                        });
+            });
+    }
+
+    @Override
+    boolean isTunnel() { return true; }
+
+    @Override
+    HttpPublisher publisher() { return delegate.publisher(); }
+
+    @Override
+    boolean connected() {
+        return connected;
+    }
+
+    @Override
+    SocketChannel channel() {
+        return delegate.channel();
+    }
+
+    @Override
+    FlowTube getConnectionFlow() {
+        return delegate.getConnectionFlow();
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return new ConnectionPool.CacheKey(null, proxyAddr);
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+        connected = false;
+    }
+
+    @Override
+    void shutdownInput() throws IOException {
+        delegate.shutdownInput();
+    }
+
+    @Override
+    void shutdownOutput() throws IOException {
+        delegate.shutdownOutput();
+    }
+
+    @Override
+    boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    boolean isProxied() {
+        return true;
+    }
+
+    // Support for WebSocket/RawChannelImpl which unfortunately
+    // still depends on synchronous read/writes.
+    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
+    @Override
+    DetachedConnectionChannel detachChannel() {
+        return delegate.detachChannel();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PrivilegedExecutor.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Executes tasks within a given access control context, and by a given executor.
+ */
+class PrivilegedExecutor implements Executor {
+
+    /** The underlying executor. May be provided by the user. */
+    final Executor executor;
+    /** The ACC to execute the tasks within. */
+    final AccessControlContext acc;
+
+    public PrivilegedExecutor(Executor executor, AccessControlContext acc) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(acc);
+        this.executor = executor;
+        this.acc = acc;
+    }
+
+    private static class PrivilegedRunnable implements Runnable {
+        private final Runnable r;
+        private final AccessControlContext acc;
+        PrivilegedRunnable(Runnable r, AccessControlContext acc) {
+            this.r = r;
+            this.acc = acc;
+        }
+        @Override
+        public void run() {
+            PrivilegedAction<Void> pa = () -> { r.run(); return null; };
+            AccessController.doPrivileged(pa, acc);
+        }
+    }
+
+    @Override
+    public void execute(Runnable r) {
+        executor.execute(new PrivilegedRunnable(r, acc));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ProxyAuthenticationRequired.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+
+/**
+ * Signals that a proxy has refused a CONNECT request with a
+ * 407 error code.
+ */
+final class ProxyAuthenticationRequired extends IOException {
+    private static final long serialVersionUID = 0;
+    final transient Response proxyResponse;
+
+    /**
+     * Constructs a {@code ConnectionExpiredException} with the specified detail
+     * message and cause.
+     *
+     * @param   proxyResponse the response from the proxy
+     */
+    public ProxyAuthenticationRequired(Response proxyResponse) {
+        super("Proxy Authentication Required");
+        assert proxyResponse.statusCode() == 407;
+        this.proxyResponse = proxyResponse;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PullPublisher.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.util.Iterator;
+import java.util.concurrent.Flow;
+import jdk.incubator.http.internal.common.Demand;
+import jdk.incubator.http.internal.common.SequentialScheduler;
+
+/**
+ * A Publisher that publishes items obtained from the given Iterable. Each new
+ * subscription gets a new Iterator.
+ */
+class PullPublisher<T> implements Flow.Publisher<T> {
+
+    // Only one of `iterable` and `throwable` can be non-null. throwable is
+    // non-null when an error has been encountered, by the creator of
+    // PullPublisher, while subscribing the subscriber, but before subscribe has
+    // completed.
+    private final Iterable<T> iterable;
+    private final Throwable throwable;
+
+    PullPublisher(Iterable<T> iterable, Throwable throwable) {
+        this.iterable = iterable;
+        this.throwable = throwable;
+    }
+
+    PullPublisher(Iterable<T> iterable) {
+        this(iterable, null);
+    }
+
+    @Override
+    public void subscribe(Flow.Subscriber<? super T> subscriber) {
+        Subscription sub;
+        if (throwable != null) {
+            assert iterable == null : "non-null iterable: " + iterable;
+            sub = new Subscription(subscriber, null, throwable);
+        } else {
+            assert throwable == null : "non-null exception: " + throwable;
+            sub = new Subscription(subscriber, iterable.iterator(), null);
+        }
+        subscriber.onSubscribe(sub);
+
+        if (throwable != null) {
+            sub.pullScheduler.runOrSchedule();
+        }
+    }
+
+    private class Subscription implements Flow.Subscription {
+
+        private final Flow.Subscriber<? super T> subscriber;
+        private final Iterator<T> iter;
+        private volatile boolean completed;
+        private volatile boolean cancelled;
+        private volatile Throwable error;
+        final SequentialScheduler pullScheduler = new SequentialScheduler(new PullTask());
+        private final Demand demand = new Demand();
+
+        Subscription(Flow.Subscriber<? super T> subscriber,
+                     Iterator<T> iter,
+                     Throwable throwable) {
+            this.subscriber = subscriber;
+            this.iter = iter;
+            this.error = throwable;
+        }
+
+        final class PullTask extends SequentialScheduler.CompleteRestartableTask {
+            @Override
+            protected void run() {
+                if (completed || cancelled) {
+                    return;
+                }
+
+                Throwable t = error;
+                if (t != null) {
+                    completed = true;
+                    pullScheduler.stop();
+                    subscriber.onError(t);
+                    return;
+                }
+
+                while (demand.tryDecrement() && !cancelled) {
+                    if (!iter.hasNext()) {
+                        break;
+                    } else {
+                        subscriber.onNext(iter.next());
+                    }
+                }
+                if (!iter.hasNext() && !cancelled) {
+                    completed = true;
+                    pullScheduler.stop();
+                    subscriber.onComplete();
+                }
+            }
+        }
+
+        @Override
+        public void request(long n) {
+            if (cancelled)
+                return;  // no-op
+
+            if (n <= 0) {
+                error = new IllegalArgumentException("illegal non-positive request:" + n);
+            } else {
+                demand.increase(n);
+            }
+            pullScheduler.runOrSchedule();
+        }
+
+        @Override
+        public void cancel() {
+            cancelled = true;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/PushGroup.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpResponse.BodyHandler;
+import jdk.incubator.http.HttpResponse.PushPromiseHandler;
+import jdk.incubator.http.internal.common.MinimalFuture;
+import jdk.incubator.http.internal.common.Log;
+
+/**
+ * One PushGroup object is associated with the parent Stream of the pushed
+ * Streams. This keeps track of all common state associated with the pushes.
+ */
+class PushGroup<T> {
+    private final HttpRequest initiatingRequest;
+
+    final CompletableFuture<Void> noMorePushesCF;
+
+    volatile Throwable error; // any exception that occurred during pushes
+
+    // user's subscriber object
+    final PushPromiseHandler<T> pushPromiseHandler;
+
+    private final AccessControlContext acc;
+
+    int numberOfPushes;
+    int remainingPushes;
+    boolean noMorePushes = false;
+
+    PushGroup(PushPromiseHandler<T> pushPromiseHandler,
+              HttpRequestImpl initiatingRequest,
+              AccessControlContext acc) {
+        this(pushPromiseHandler, initiatingRequest, new MinimalFuture<>(), acc);
+    }
+
+    // Check mainBodyHandler before calling nested constructor.
+    private PushGroup(HttpResponse.PushPromiseHandler<T> pushPromiseHandler,
+                      HttpRequestImpl initiatingRequest,
+                      CompletableFuture<HttpResponse<T>> mainResponse,
+                      AccessControlContext acc) {
+        this.noMorePushesCF = new MinimalFuture<>();
+        this.pushPromiseHandler = pushPromiseHandler;
+        this.initiatingRequest = initiatingRequest;
+        // Restricts the file publisher with the senders ACC, if any
+        if (pushPromiseHandler instanceof UntrustedBodyHandler)
+            ((UntrustedBodyHandler)this.pushPromiseHandler).setAccessControlContext(acc);
+        this.acc = acc;
+    }
+
+    interface Acceptor<T> {
+        BodyHandler<T> bodyHandler();
+        CompletableFuture<HttpResponse<T>> cf();
+        boolean accepted();
+    }
+
+    private static class AcceptorImpl<T> implements Acceptor<T> {
+        private volatile HttpResponse.BodyHandler<T> bodyHandler;
+        private volatile CompletableFuture<HttpResponse<T>> cf;
+
+        CompletableFuture<HttpResponse<T>> accept(BodyHandler<T> bodyHandler) {
+            Objects.requireNonNull(bodyHandler);
+            if (this.bodyHandler != null)
+                throw new IllegalStateException("non-null bodyHandler");
+            this.bodyHandler = bodyHandler;
+            cf = new MinimalFuture<>();
+            return cf;
+        }
+
+        @Override public BodyHandler<T> bodyHandler() { return bodyHandler; }
+
+        @Override public CompletableFuture<HttpResponse<T>> cf() { return cf; }
+
+        @Override public boolean accepted() { return cf != null; }
+    }
+
+    Acceptor<T> acceptPushRequest(HttpRequest pushRequest) {
+        AcceptorImpl<T> acceptor = new AcceptorImpl<>();
+
+        pushPromiseHandler.applyPushPromise(initiatingRequest, pushRequest, acceptor::accept);
+
+        synchronized (this) {
+            if (acceptor.accepted()) {
+                if (acceptor.bodyHandler instanceof UntrustedBodyHandler) {
+                    ((UntrustedBodyHandler) acceptor.bodyHandler).setAccessControlContext(acc);
+                }
+                numberOfPushes++;
+                remainingPushes++;
+            }
+            return acceptor;
+        }
+    }
+
+    // This is called when the main body response completes because it means
+    // no more PUSH_PROMISEs are possible
+
+    synchronized void noMorePushes(boolean noMore) {
+        noMorePushes = noMore;
+        checkIfCompleted();
+        noMorePushesCF.complete(null);
+    }
+
+    synchronized CompletableFuture<Void> pushesCF() {
+        return noMorePushesCF;
+    }
+
+    synchronized boolean noMorePushes() {
+        return noMorePushes;
+    }
+
+    synchronized void pushCompleted() {
+        remainingPushes--;
+        checkIfCompleted();
+    }
+
+    synchronized void checkIfCompleted() {
+        if (Log.trace()) {
+            Log.logTrace("PushGroup remainingPushes={0} error={1} noMorePushes={2}",
+                         remainingPushes,
+                         (error==null)?error:error.getClass().getSimpleName(),
+                         noMorePushes);
+        }
+        if (remainingPushes == 0 && error == null && noMorePushes) {
+            if (Log.trace()) {
+                Log.logTrace("push completed");
+            }
+        }
+    }
+
+    synchronized void pushError(Throwable t) {
+        if (t == null) {
+            return;
+        }
+        this.error = t;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/RawChannelImpl.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import jdk.incubator.http.internal.common.Utils;
+import jdk.incubator.http.internal.websocket.RawChannel;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SocketChannel;
+import java.util.function.Supplier;
+
+/*
+ * 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.
+ */
+final class RawChannelImpl implements RawChannel {
+
+    private final HttpClientImpl client;
+    private final HttpConnection.DetachedConnectionChannel detachedChannel;
+    private final Object         initialLock = new Object();
+    private Supplier<ByteBuffer> initial;
+
+    RawChannelImpl(HttpClientImpl client,
+                   HttpConnection connection,
+                   Supplier<ByteBuffer> initial)
+            throws IOException
+    {
+        this.client = client;
+        this.detachedChannel = connection.detachChannel();
+        this.initial = initial;
+
+        SocketChannel chan = connection.channel();
+        client.cancelRegistration(chan);
+        // Constructing a RawChannel is supposed to have a "hand over"
+        // semantics, in other words if construction fails, the channel won't be
+        // needed by anyone, in which case someone still needs to close it
+        try {
+            chan.configureBlocking(false);
+        } catch (IOException e) {
+            try {
+                chan.close();
+            } catch (IOException e1) {
+                e.addSuppressed(e1);
+            } finally {
+                detachedChannel.close();
+            }
+            throw e;
+        }
+    }
+
+    private class NonBlockingRawAsyncEvent extends AsyncEvent {
+
+        private final RawEvent re;
+
+        NonBlockingRawAsyncEvent(RawEvent re) {
+            // !BLOCKING & !REPEATING
+            this.re = re;
+        }
+
+        @Override
+        public SelectableChannel channel() {
+            return detachedChannel.channel();
+        }
+
+        @Override
+        public int interestOps() {
+            return re.interestOps();
+        }
+
+        @Override
+        public void handle() {
+            re.handle();
+        }
+
+        @Override
+        public void abort(IOException ioe) { }
+    }
+
+    @Override
+    public void registerEvent(RawEvent event) throws IOException {
+        client.registerEvent(new NonBlockingRawAsyncEvent(event));
+    }
+
+    @Override
+    public ByteBuffer read() throws IOException {
+        assert !detachedChannel.channel().isBlocking();
+        // connection.read() will no longer be available.
+        return detachedChannel.read();
+    }
+
+    @Override
+    public ByteBuffer initialByteBuffer() {
+        synchronized (initialLock) {
+            if (initial == null) {
+                throw new IllegalStateException();
+            }
+            ByteBuffer ref = initial.get();
+            ref = ref.hasRemaining() ? Utils.copy(ref)
+                    : Utils.EMPTY_BYTEBUFFER;
+            initial = null;
+            return ref;
+        }
+    }
+
+    @Override
+    public long write(ByteBuffer[] src, int offset, int len) throws IOException {
+        // this makes the whitebox driver test fail.
+        return detachedChannel.write(src, offset, len);
+    }
+
+    @Override
+    public void shutdownInput() throws IOException {
+        detachedChannel.shutdownInput();
+    }
+
+    @Override
+    public void shutdownOutput() throws IOException {
+        detachedChannel.shutdownOutput();
+    }
+
+    @Override
+    public void close() throws IOException {
+        detachedChannel.close();
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()+"("+ detachedChannel.toString() + ")";
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/RedirectFilter.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.internal.common.Utils;
+
+class RedirectFilter implements HeaderFilter {
+
+    HttpRequestImpl request;
+    HttpClientImpl client;
+    HttpClient.Redirect policy;
+    String method;
+    MultiExchange<?> exchange;
+    static final int DEFAULT_MAX_REDIRECTS = 5;
+    URI uri;
+
+    static final int max_redirects = Utils.getIntegerNetProperty(
+            "jdk.httpclient.redirects.retrylimit", DEFAULT_MAX_REDIRECTS
+    );
+
+    // A public no-arg constructor is required by FilterFactory
+    public RedirectFilter() {}
+
+    @Override
+    public synchronized void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException {
+        this.request = r;
+        this.client = e.client();
+        this.policy = client.followRedirects();
+
+        this.method = r.method();
+        this.uri = r.uri();
+        this.exchange = e;
+    }
+
+    @Override
+    public synchronized HttpRequestImpl response(Response 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(Response r) {
+        int rcode = r.statusCode();
+        if (rcode == 200 || policy == HttpClient.Redirect.NEVER) {
+            return null;
+        }
+        if (rcode >= 300 && rcode <= 399) {
+            URI redir = getRedirectedURI(r.headers());
+            if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) {
+                //System.out.println("Redirecting to: " + redir);
+                return new HttpRequestImpl(redir, method, request);
+            } else {
+                //System.out.println("Redirect: giving up");
+                return null;
+            }
+        }
+        return null;
+    }
+
+    private URI getRedirectedURI(HttpHeaders headers) {
+        URI redirectedURI;
+        redirectedURI = headers.firstValue("Location")
+                .map(URI::create)
+                .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(URI redir) {
+        String newScheme = redir.getScheme();
+        String oldScheme = uri.getScheme();
+        switch (policy) {
+            case ALWAYS:
+                return true;
+            case NEVER:
+                return false;
+            case SECURE:
+                return newScheme.equalsIgnoreCase("https");
+            case SAME_PROTOCOL:
+                return newScheme.equalsIgnoreCase(oldScheme);
+            default:
+                throw new InternalError();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/RequestPublishers.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Publisher;
+import java.util.function.Supplier;
+import jdk.incubator.http.HttpRequest.BodyPublisher;
+import jdk.incubator.http.internal.common.Utils;
+
+public class RequestPublishers {
+
+    public static class ByteArrayPublisher implements BodyPublisher {
+        private volatile Flow.Publisher<ByteBuffer> delegate;
+        private final int length;
+        private final byte[] content;
+        private final int offset;
+        private final int bufSize;
+
+        public ByteArrayPublisher(byte[] content) {
+            this(content, 0, content.length);
+        }
+
+        public ByteArrayPublisher(byte[] content, int offset, int length) {
+            this(content, offset, length, Utils.BUFSIZE);
+        }
+
+        /* bufSize exposed for testing purposes */
+        ByteArrayPublisher(byte[] content, int offset, int length, int bufSize) {
+            this.content = content;
+            this.offset = offset;
+            this.length = length;
+            this.bufSize = bufSize;
+        }
+
+        List<ByteBuffer> copy(byte[] content, int offset, int length) {
+            List<ByteBuffer> bufs = new ArrayList<>();
+            while (length > 0) {
+                ByteBuffer b = ByteBuffer.allocate(Math.min(bufSize, length));
+                int max = b.capacity();
+                int tocopy = Math.min(max, length);
+                b.put(content, offset, tocopy);
+                offset += tocopy;
+                length -= tocopy;
+                b.flip();
+                bufs.add(b);
+            }
+            return bufs;
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            List<ByteBuffer> copy = copy(content, offset, length);
+            this.delegate = new PullPublisher<>(copy);
+            delegate.subscribe(subscriber);
+        }
+
+        @Override
+        public long contentLength() {
+            return length;
+        }
+    }
+
+    // This implementation has lots of room for improvement.
+    public static class IterablePublisher implements BodyPublisher {
+        private volatile Flow.Publisher<ByteBuffer> delegate;
+        private final Iterable<byte[]> content;
+        private volatile long contentLength;
+
+        public IterablePublisher(Iterable<byte[]> content) {
+            this.content = Objects.requireNonNull(content);
+        }
+
+        // The ByteBufferIterator will iterate over the byte[] arrays in
+        // the content one at the time.
+        //
+        class ByteBufferIterator implements Iterator<ByteBuffer> {
+            final ConcurrentLinkedQueue<ByteBuffer> buffers = new ConcurrentLinkedQueue<>();
+            final Iterator<byte[]> iterator = content.iterator();
+            @Override
+            public boolean hasNext() {
+                return !buffers.isEmpty() || iterator.hasNext();
+            }
+
+            @Override
+            public ByteBuffer next() {
+                ByteBuffer buffer = buffers.poll();
+                while (buffer == null) {
+                    copy();
+                    buffer = buffers.poll();
+                }
+                return buffer;
+            }
+
+            ByteBuffer getBuffer() {
+                return Utils.getBuffer();
+            }
+
+            void copy() {
+                byte[] bytes = iterator.next();
+                int length = bytes.length;
+                if (length == 0 && iterator.hasNext()) {
+                    // avoid inserting empty buffers, except
+                    // if that's the last.
+                    return;
+                }
+                int offset = 0;
+                do {
+                    ByteBuffer b = getBuffer();
+                    int max = b.capacity();
+
+                    int tocopy = Math.min(max, length);
+                    b.put(bytes, offset, tocopy);
+                    offset += tocopy;
+                    length -= tocopy;
+                    b.flip();
+                    buffers.add(b);
+                } while (length > 0);
+            }
+        }
+
+        public Iterator<ByteBuffer> iterator() {
+            return new ByteBufferIterator();
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            Iterable<ByteBuffer> iterable = this::iterator;
+            this.delegate = new PullPublisher<>(iterable);
+            delegate.subscribe(subscriber);
+        }
+
+        static long computeLength(Iterable<byte[]> bytes) {
+            long len = 0;
+            for (byte[] b : bytes) {
+                len = Math.addExact(len, (long)b.length);
+            }
+            return len;
+        }
+
+        @Override
+        public long contentLength() {
+            if (contentLength == 0) {
+                synchronized(this) {
+                    if (contentLength == 0) {
+                        contentLength = computeLength(content);
+                    }
+                }
+            }
+            return contentLength;
+        }
+    }
+
+    public static class StringPublisher extends ByteArrayPublisher {
+        public StringPublisher(String content, Charset charset) {
+            super(content.getBytes(charset));
+        }
+    }
+
+    public static class EmptyPublisher implements BodyPublisher {
+        private final Flow.Publisher<ByteBuffer> delegate =
+                new PullPublisher<ByteBuffer>(Collections.emptyList(), null);
+
+        @Override
+        public long contentLength() {
+            return 0;
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            delegate.subscribe(subscriber);
+        }
+    }
+
+    public static class FilePublisher implements BodyPublisher  {
+        private final File file;
+        private volatile AccessControlContext acc;
+
+        public FilePublisher(Path name) {
+            file = name.toFile();
+        }
+
+        void setAccessControlContext(AccessControlContext acc) {
+            this.acc = acc;
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            if (System.getSecurityManager() != null && acc == null)
+                throw new InternalError(
+                        "Unexpected null acc when security manager has been installed");
+
+            InputStream is;
+            try {
+                PrivilegedExceptionAction<FileInputStream> pa =
+                        () -> new FileInputStream(file);
+                is = AccessController.doPrivileged(pa, acc);
+            } catch (PrivilegedActionException pae) {
+                throw new UncheckedIOException((IOException)pae.getCause());
+            }
+            PullPublisher<ByteBuffer> publisher =
+                    new PullPublisher<>(() -> new StreamIterator(is));
+            publisher.subscribe(subscriber);
+        }
+
+        @Override
+        public long contentLength() {
+            assert System.getSecurityManager() != null ? acc != null: true;
+            PrivilegedAction<Long> pa = () -> file.length();
+            return AccessController.doPrivileged(pa, acc);
+        }
+    }
+
+    /**
+     * Reads one buffer ahead all the time, blocking in hasNext()
+     */
+    public static class StreamIterator implements Iterator<ByteBuffer> {
+        final InputStream is;
+        final Supplier<? extends ByteBuffer> bufSupplier;
+        volatile ByteBuffer nextBuffer;
+        volatile boolean need2Read = true;
+        volatile boolean haveNext;
+
+        StreamIterator(InputStream is) {
+            this(is, Utils::getBuffer);
+        }
+
+        StreamIterator(InputStream is, Supplier<? extends ByteBuffer> bufSupplier) {
+            this.is = is;
+            this.bufSupplier = bufSupplier;
+        }
+
+//        Throwable error() {
+//            return error;
+//        }
+
+        private int read() {
+            nextBuffer = bufSupplier.get();
+            nextBuffer.clear();
+            byte[] buf = nextBuffer.array();
+            int offset = nextBuffer.arrayOffset();
+            int cap = nextBuffer.capacity();
+            try {
+                int n = is.read(buf, offset, cap);
+                if (n == -1) {
+                    is.close();
+                    return -1;
+                }
+                //flip
+                nextBuffer.limit(n);
+                nextBuffer.position(0);
+                return n;
+            } catch (IOException ex) {
+                return -1;
+            }
+        }
+
+        @Override
+        public synchronized boolean hasNext() {
+            if (need2Read) {
+                haveNext = read() != -1;
+                if (haveNext) {
+                    need2Read = false;
+                }
+                return haveNext;
+            }
+            return haveNext;
+        }
+
+        @Override
+        public synchronized ByteBuffer next() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            need2Read = true;
+            return nextBuffer;
+        }
+
+    }
+
+    public static class InputStreamPublisher implements BodyPublisher {
+        private final Supplier<? extends InputStream> streamSupplier;
+
+        public InputStreamPublisher(Supplier<? extends InputStream> streamSupplier) {
+            this.streamSupplier = Objects.requireNonNull(streamSupplier);
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            PullPublisher<ByteBuffer> publisher;
+            InputStream is = streamSupplier.get();
+            if (is == null) {
+                Throwable t = new IOException("streamSupplier returned null");
+                publisher = new PullPublisher<>(null, t);
+            } else  {
+                publisher = new PullPublisher<>(iterableOf(is), null);
+            }
+            publisher.subscribe(subscriber);
+        }
+
+        protected Iterable<ByteBuffer> iterableOf(InputStream is) {
+            return () -> new StreamIterator(is);
+        }
+
+        @Override
+        public long contentLength() {
+            return -1;
+        }
+    }
+
+    public static final class PublisherAdapter implements BodyPublisher {
+
+        private final Publisher<? extends ByteBuffer> publisher;
+        private final long contentLength;
+
+        public PublisherAdapter(Publisher<? extends ByteBuffer> publisher,
+                         long contentLength) {
+            this.publisher = Objects.requireNonNull(publisher);
+            this.contentLength = contentLength;
+        }
+
+        @Override
+        public final long contentLength() {
+            return contentLength;
+        }
+
+        @Override
+        public final void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            publisher.subscribe(subscriber);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Response.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.net.URI;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpHeaders;
+
+/**
+ * Response headers and status code.
+ */
+class Response {
+    final HttpHeaders headers;
+    final int statusCode;
+    final HttpRequestImpl request;
+    final Exchange<?> exchange;
+    final HttpClient.Version version;
+    final boolean isConnectResponse;
+
+    Response(HttpRequestImpl req,
+             Exchange<?> exchange,
+             HttpHeaders headers,
+             int statusCode,
+             HttpClient.Version version) {
+        this(req, exchange, headers, statusCode, version,
+                "CONNECT".equalsIgnoreCase(req.method()));
+    }
+
+    Response(HttpRequestImpl req,
+             Exchange<?> exchange,
+             HttpHeaders headers,
+             int statusCode,
+             HttpClient.Version version,
+             boolean isConnectResponse) {
+        this.headers = headers;
+        this.request = req;
+        this.version = version;
+        this.exchange = exchange;
+        this.statusCode = statusCode;
+        this.isConnectResponse = isConnectResponse;
+    }
+
+    HttpRequestImpl request() {
+        return request;
+    }
+
+    HttpClient.Version version() {
+        return version;
+    }
+
+    HttpHeaders headers() {
+        return headers;
+    }
+
+//    Exchange<?> exchange() {
+//        return exchange;
+//    }
+
+    int statusCode() {
+        return statusCode;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        String method = request().method();
+        URI uri = request().uri();
+        String uristring = uri == null ? "" : uri.toString();
+        sb.append('(')
+          .append(method)
+          .append(" ")
+          .append(uristring)
+          .append(") ")
+          .append(statusCode());
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ResponseBodyHandlers.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.AccessControlContext;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Function;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpResponse.BodyHandler;
+import jdk.incubator.http.HttpResponse.BodySubscriber;
+import jdk.incubator.http.internal.ResponseSubscribers.PathSubscriber;
+import static jdk.incubator.http.internal.common.Utils.unchecked;
+
+public class ResponseBodyHandlers {
+
+    /**
+     * A Path body handler.
+     *
+     * Note: Exists mainly too allow setting of the senders ACC post creation of
+     * the handler.
+     */
+    public static class PathBodyHandler implements UntrustedBodyHandler<Path> {
+        private final Path file;
+        private final OpenOption[]openOptions;
+        private volatile AccessControlContext acc;
+
+        public PathBodyHandler(Path file, OpenOption... openOptions) {
+            this.file = file;
+            this.openOptions = openOptions;
+        }
+
+        @Override
+        public void setAccessControlContext(AccessControlContext acc) {
+            this.acc = acc;
+        }
+
+        @Override
+        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
+            PathSubscriber bs = (PathSubscriber) asFileImpl(file, openOptions);
+            bs.setAccessControlContext(acc);
+            return bs;
+        }
+    }
+
+    /** With push promise Map implementation */
+    public static class PushPromisesHandlerWithMap<T>
+        implements HttpResponse.PushPromiseHandler<T>
+    {
+        private final ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap;
+        private final Function<HttpRequest,BodyHandler<T>> pushPromiseHandler;
+
+        public PushPromisesHandlerWithMap(Function<HttpRequest,BodyHandler<T>> pushPromiseHandler,
+                                          ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap) {
+            this.pushPromiseHandler = pushPromiseHandler;
+            this.pushPromisesMap = pushPromisesMap;
+        }
+
+        @Override
+        public void applyPushPromise(
+                HttpRequest initiatingRequest, HttpRequest pushRequest,
+                Function<BodyHandler<T>,CompletableFuture<HttpResponse<T>>> acceptor)
+        {
+            URI initiatingURI = initiatingRequest.uri();
+            URI pushRequestURI = pushRequest.uri();
+            if (!initiatingURI.getHost().equalsIgnoreCase(pushRequestURI.getHost()))
+                return;
+
+            int initiatingPort = initiatingURI.getPort();
+            if (initiatingPort == -1 ) {
+                if ("https".equalsIgnoreCase(initiatingURI.getScheme()))
+                    initiatingPort = 443;
+                else
+                    initiatingPort = 80;
+            }
+            int pushPort = pushRequestURI.getPort();
+            if (pushPort == -1 ) {
+                if ("https".equalsIgnoreCase(pushRequestURI.getScheme()))
+                    pushPort = 443;
+                else
+                    pushPort = 80;
+            }
+            if (initiatingPort != pushPort)
+                return;
+
+            CompletableFuture<HttpResponse<T>> cf =
+                    acceptor.apply(pushPromiseHandler.apply(pushRequest));
+            pushPromisesMap.put(pushRequest, cf);
+        }
+    }
+
+    // Similar to Path body handler, but for file download. Supports setting ACC.
+    public static class FileDownloadBodyHandler implements UntrustedBodyHandler<Path> {
+        private final Path directory;
+        private final OpenOption[]openOptions;
+        private volatile AccessControlContext acc;
+
+        public FileDownloadBodyHandler(Path directory, OpenOption... openOptions) {
+            this.directory = directory;
+            this.openOptions = openOptions;
+        }
+
+        @Override
+        public void setAccessControlContext(AccessControlContext acc) {
+            this.acc = acc;
+        }
+
+        @Override
+        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
+            String dispoHeader = headers.firstValue("Content-Disposition")
+                    .orElseThrow(() -> unchecked(new IOException("No Content-Disposition")));
+            if (!dispoHeader.startsWith("attachment;")) {
+                throw unchecked(new IOException("Unknown Content-Disposition type"));
+            }
+            int n = dispoHeader.indexOf("filename=");
+            if (n == -1) {
+                throw unchecked(new IOException("Bad Content-Disposition type"));
+            }
+            int lastsemi = dispoHeader.lastIndexOf(';');
+            String disposition;
+            if (lastsemi < n) {
+                disposition = dispoHeader.substring(n + 9);
+            } else {
+                disposition = dispoHeader.substring(n + 9, lastsemi);
+            }
+            Path file = Paths.get(directory.toString(), disposition);
+
+            PathSubscriber bs = (PathSubscriber)asFileImpl(file, openOptions);
+            bs.setAccessControlContext(acc);
+            return bs;
+        }
+    }
+
+    // no security check
+    private static BodySubscriber<Path> asFileImpl(Path file, OpenOption... openOptions) {
+        return new ResponseSubscribers.PathSubscriber(file, openOptions);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/ResponseContent.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * Implements chunked/fixed transfer encodings of HTTP/1.1 responses.
+ *
+ * Call pushBody() to read the body (blocking). Data and errors are provided
+ * to given Consumers. After final buffer delivered, empty optional delivered
+ */
+class ResponseContent {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+
+    final HttpResponse.BodySubscriber<?> pusher;
+    final int contentLength;
+    final HttpHeaders headers;
+    // this needs to run before we complete the body
+    // so that connection can be returned to pool
+    private final Runnable onFinished;
+    private final String dbgTag;
+
+    ResponseContent(HttpConnection connection,
+                    int contentLength,
+                    HttpHeaders h,
+                    HttpResponse.BodySubscriber<?> userSubscriber,
+                    Runnable onFinished)
+    {
+        this.pusher = userSubscriber;
+        this.contentLength = contentLength;
+        this.headers = h;
+        this.onFinished = onFinished;
+        this.dbgTag = connection.dbgString() + "/ResponseContent";
+    }
+
+    static final int LF = 10;
+    static final int CR = 13;
+
+    private boolean chunkedContent, chunkedContentInitialized;
+
+    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;
+    }
+
+    interface BodyParser extends Consumer<ByteBuffer> {
+        void onSubscribe(AbstractSubscription sub);
+    }
+
+    // Returns a parser that will take care of parsing the received byte
+    // buffers and forward them to the BodySubscriber.
+    // When the parser is done, it will call onComplete.
+    // If parsing was successful, the throwable parameter will be null.
+    // Otherwise it will be the exception that occurred
+    // Note: revisit: it might be better to use a CompletableFuture than
+    //       a completion handler.
+    BodyParser getBodyParser(Consumer<Throwable> onComplete)
+        throws IOException {
+        if (contentChunked()) {
+            return new ChunkedBodyParser(onComplete);
+        } else {
+            return new FixedLengthBodyParser(contentLength, onComplete);
+        }
+    }
+
+
+    static enum ChunkState {READING_LENGTH, READING_DATA, DONE}
+    class ChunkedBodyParser implements BodyParser {
+        final ByteBuffer READMORE = Utils.EMPTY_BYTEBUFFER;
+        final Consumer<Throwable> onComplete;
+        final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+        final String dbgTag = ResponseContent.this.dbgTag + "/ChunkedBodyParser";
+
+        volatile Throwable closedExceptionally;
+        volatile int partialChunklen = 0; // partially read chunk len
+        volatile int chunklen = -1;  // number of bytes in chunk
+        volatile int bytesremaining;  // number of bytes in chunk left to be read incl CRLF
+        volatile boolean cr = false;  // tryReadChunkLength has found CR
+        volatile int bytesToConsume;  // number of bytes that still need to be consumed before proceeding
+        volatile ChunkState state = ChunkState.READING_LENGTH; // current state
+        volatile AbstractSubscription sub;
+        ChunkedBodyParser(Consumer<Throwable> onComplete) {
+            this.onComplete = onComplete;
+        }
+
+        String dbgString() {
+            return dbgTag;
+        }
+
+        @Override
+        public void onSubscribe(AbstractSubscription sub) {
+            debug.log(Level.DEBUG, () ->  "onSubscribe: "
+                        + pusher.getClass().getName());
+            pusher.onSubscribe(this.sub = sub);
+        }
+
+        @Override
+        public void accept(ByteBuffer b) {
+            if (closedExceptionally != null) {
+                debug.log(Level.DEBUG, () ->  "already closed: "
+                            + closedExceptionally);
+                return;
+            }
+            boolean completed = false;
+            try {
+                List<ByteBuffer> out = new ArrayList<>();
+                do {
+                    if (tryPushOneHunk(b, out))  {
+                        // We're done! (true if the final chunk was parsed).
+                        if (!out.isEmpty()) {
+                            // push what we have and complete
+                            // only reduce demand if we actually push something.
+                            // we would not have come here if there was no
+                            // demand.
+                            boolean hasDemand = sub.demand().tryDecrement();
+                            assert hasDemand;
+                            pusher.onNext(Collections.unmodifiableList(out));
+                        }
+                        debug.log(Level.DEBUG, () ->  "done!");
+                        assert closedExceptionally == null;
+                        assert state == ChunkState.DONE;
+                        onFinished.run();
+                        pusher.onComplete();
+                        completed = true;
+                        onComplete.accept(closedExceptionally); // should be null
+                        break;
+                    }
+                    // the buffer may contain several hunks, and therefore
+                    // we must loop while it's not exhausted.
+                } while (b.hasRemaining());
+
+                if (!completed && !out.isEmpty()) {
+                    // push what we have.
+                    // only reduce demand if we actually push something.
+                    // we would not have come here if there was no
+                    // demand.
+                    boolean hasDemand = sub.demand().tryDecrement();
+                    assert hasDemand;
+                    pusher.onNext(Collections.unmodifiableList(out));
+                }
+                assert state == ChunkState.DONE || !b.hasRemaining();
+            } catch(Throwable t) {
+                closedExceptionally = t;
+                if (!completed) onComplete.accept(t);
+            }
+        }
+
+        // reads and returns chunklen. Position of chunkbuf is first byte
+        // of chunk on return. chunklen includes the CR LF at end of chunk
+        // returns -1 if needs more bytes
+        private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException {
+            assert state == ChunkState.READING_LENGTH;
+            while (chunkbuf.hasRemaining()) {
+                int c = chunkbuf.get();
+                if (cr) {
+                    if (c == LF) {
+                        return partialChunklen;
+                    } else {
+                        throw new IOException("invalid chunk header");
+                    }
+                }
+                if (c == CR) {
+                    cr = true;
+                } else {
+                    int digit = toDigit(c);
+                    partialChunklen = partialChunklen * 16 + digit;
+                }
+            }
+            return -1;
+        }
+
+
+        // try to consume as many bytes as specified by bytesToConsume.
+        // returns the number of bytes that still need to be consumed.
+        // In practice this method is only called to consume one CRLF pair
+        // with bytesToConsume set to 2, so it will only return 0 (if completed),
+        // 1, or 2 (if chunkbuf doesn't have the 2 chars).
+        private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException {
+            int n = bytesToConsume;
+            if (n > 0) {
+                int e = Math.min(chunkbuf.remaining(), n);
+
+                // verifies some assertions
+                // this methods is called only to consume CRLF
+                if (Utils.ASSERTIONSENABLED) {
+                    assert n <= 2 && e <= 2;
+                    ByteBuffer tmp = chunkbuf.slice();
+                    // if n == 2 assert that we will first consume CR
+                    assert (n == 2 && e > 0) ? tmp.get() == CR : true;
+                    // if n == 1 || n == 2 && e == 2 assert that we then consume LF
+                    assert (n == 1 || e == 2) ? tmp.get() == LF : true;
+                }
+
+                chunkbuf.position(chunkbuf.position() + e);
+                n -= e;
+                bytesToConsume = n;
+            }
+            assert n >= 0;
+            return n;
+        }
+
+        /**
+         * Returns a ByteBuffer containing chunk of data or a "hunk" of data
+         * (a chunk of a chunk if the chunk size is larger than our ByteBuffers).
+         * If the given chunk does not have enough data this method return
+         * an empty ByteBuffer (READMORE).
+         * If we encounter the final chunk (an empty chunk) this method
+         * returns null.
+         */
+        ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException {
+            int unfulfilled = bytesremaining;
+            int toconsume = bytesToConsume;
+            ChunkState st = state;
+            if (st == ChunkState.READING_LENGTH && chunklen == -1) {
+                debug.log(Level.DEBUG, () ->  "Trying to read chunk len"
+                        + " (remaining in buffer:"+chunk.remaining()+")");
+                int clen = chunklen = tryReadChunkLen(chunk);
+                if (clen == -1) return READMORE;
+                debug.log(Level.DEBUG, "Got chunk len %d", clen);
+                cr = false; partialChunklen = 0;
+                unfulfilled = bytesremaining =  clen;
+                if (clen == 0) toconsume = bytesToConsume = 2; // that was the last chunk
+                else st = state = ChunkState.READING_DATA; // read the data
+            }
+
+            if (toconsume > 0) {
+                debug.log(Level.DEBUG,
+                        "Trying to consume bytes: %d (remaining in buffer: %s)",
+                        toconsume, chunk.remaining());
+                if (tryConsumeBytes(chunk) > 0) {
+                    return READMORE;
+                }
+            }
+
+            toconsume = bytesToConsume;
+            assert toconsume == 0;
+
+
+            if (st == ChunkState.READING_LENGTH) {
+                // we will come here only if chunklen was 0, after having
+                // consumed the trailing CRLF
+                int clen = chunklen;
+                assert clen == 0;
+                debug.log(Level.DEBUG, "No more chunks: %d", clen);
+                // the DONE state is not really needed but it helps with
+                // assertions...
+                state = ChunkState.DONE;
+                return null;
+            }
+
+            int clen = chunklen;
+            assert clen > 0;
+            assert st == ChunkState.READING_DATA;
+
+            ByteBuffer returnBuffer = READMORE; // May be a hunk or a chunk
+            if (unfulfilled > 0) {
+                int bytesread = chunk.remaining();
+                debug.log(Level.DEBUG, "Reading chunk: available %d, needed %d",
+                          bytesread, unfulfilled);
+
+                int bytes2return = Math.min(bytesread, unfulfilled);
+                debug.log(Level.DEBUG,  "Returning chunk bytes: %d", bytes2return);
+                returnBuffer = Utils.sliceWithLimitedCapacity(chunk, bytes2return).asReadOnlyBuffer();
+                unfulfilled = bytesremaining -= bytes2return;
+                if (unfulfilled == 0) bytesToConsume = 2;
+            }
+
+            assert unfulfilled >= 0;
+
+            if (unfulfilled == 0) {
+                debug.log(Level.DEBUG,
+                        "No more bytes to read - %d yet to consume.",
+                        unfulfilled);
+                // check whether the trailing CRLF is consumed, try to
+                // consume it if not. If tryConsumeBytes needs more bytes
+                // then we will come back here later - skipping the block
+                // that reads data because remaining==0, and finding
+                // that the two bytes are now consumed.
+                if (tryConsumeBytes(chunk) == 0) {
+                    // we're done for this chunk! reset all states and
+                    // prepare to read the next chunk.
+                    chunklen = -1;
+                    partialChunklen = 0;
+                    cr = false;
+                    state = ChunkState.READING_LENGTH;
+                    debug.log(Level.DEBUG, "Ready to read next chunk");
+                }
+            }
+            if (returnBuffer == READMORE) {
+                debug.log(Level.DEBUG, "Need more data");
+            }
+            return returnBuffer;
+        }
+
+
+        // Attempt to parse and push one hunk from the buffer.
+        // Returns true if the final chunk was parsed.
+        // Returns false if we need to push more chunks.
+        private boolean tryPushOneHunk(ByteBuffer b, List<ByteBuffer> out)
+                throws IOException {
+            assert state != ChunkState.DONE;
+            ByteBuffer b1 = tryReadOneHunk(b);
+            if (b1 != null) {
+                //assert b1.hasRemaining() || b1 == READMORE;
+                if (b1.hasRemaining()) {
+                    debug.log(Level.DEBUG, "Sending chunk to consumer (%d)",
+                              b1.remaining());
+                    out.add(b1);
+                    debug.log(Level.DEBUG, "Chunk sent.");
+                }
+                return false; // we haven't parsed the final chunk yet.
+            } else {
+                return true; // we're done! the final chunk was parsed.
+            }
+        }
+
+        private int toDigit(int b) throws IOException {
+            if (b >= 0x30 && b <= 0x39) {
+                return b - 0x30;
+            }
+            if (b >= 0x41 && b <= 0x46) {
+                return b - 0x41 + 10;
+            }
+            if (b >= 0x61 && b <= 0x66) {
+                return b - 0x61 + 10;
+            }
+            throw new IOException("Invalid chunk header byte " + b);
+        }
+
+    }
+
+    class FixedLengthBodyParser implements BodyParser {
+        final int contentLength;
+        final Consumer<Throwable> onComplete;
+        final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+        final String dbgTag = ResponseContent.this.dbgTag + "/FixedLengthBodyParser";
+        volatile int remaining;
+        volatile Throwable closedExceptionally;
+        volatile AbstractSubscription sub;
+        FixedLengthBodyParser(int contentLength, Consumer<Throwable> onComplete) {
+            this.contentLength = this.remaining = contentLength;
+            this.onComplete = onComplete;
+        }
+
+        String dbgString() {
+            return dbgTag;
+        }
+
+        @Override
+        public void onSubscribe(AbstractSubscription sub) {
+            debug.log(Level.DEBUG, () -> "length="
+                        + contentLength +", onSubscribe: "
+                        + pusher.getClass().getName());
+            pusher.onSubscribe(this.sub = sub);
+            try {
+                if (contentLength == 0) {
+                    onFinished.run();
+                    pusher.onComplete();
+                    onComplete.accept(null);
+                }
+            } catch (Throwable t) {
+                closedExceptionally = t;
+                try {
+                    pusher.onError(t);
+                } finally {
+                    onComplete.accept(t);
+                }
+            }
+        }
+
+        @Override
+        public void accept(ByteBuffer b) {
+            if (closedExceptionally != null) {
+                debug.log(Level.DEBUG, () -> "already closed: "
+                            + closedExceptionally);
+                return;
+            }
+            boolean completed = false;
+            try {
+                int unfulfilled = remaining;
+                debug.log(Level.DEBUG, "Parser got %d bytes (%d remaining / %d)",
+                        b.remaining(), unfulfilled, contentLength);
+                assert unfulfilled != 0 || contentLength == 0 || b.remaining() == 0;
+
+                if (unfulfilled == 0 && contentLength > 0) return;
+
+                if (b.hasRemaining() && unfulfilled > 0) {
+                    // only reduce demand if we actually push something.
+                    // we would not have come here if there was no
+                    // demand.
+                    boolean hasDemand = sub.demand().tryDecrement();
+                    assert hasDemand;
+                    int amount = Math.min(b.remaining(), unfulfilled);
+                    unfulfilled = remaining -= amount;
+                    ByteBuffer buffer = Utils.sliceWithLimitedCapacity(b, amount);
+                    pusher.onNext(List.of(buffer.asReadOnlyBuffer()));
+                }
+                if (unfulfilled == 0) {
+                    // We're done! All data has been received.
+                    assert closedExceptionally == null;
+                    onFinished.run();
+                    pusher.onComplete();
+                    completed = true;
+                    onComplete.accept(closedExceptionally); // should be null
+                } else {
+                    assert b.remaining() == 0;
+                }
+            } catch (Throwable t) {
+                debug.log(Level.DEBUG, "Unexpected exception", t);
+                closedExceptionally = t;
+                if (!completed) {
+                    onComplete.accept(t);
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/SSLDelegate.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.*;
+import jdk.incubator.http.internal.common.Log;
+import jdk.incubator.http.internal.common.Utils;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
+
+/**
+ * Implements the mechanics of SSL by managing an SSLEngine object.
+ * <p>
+ * This class is only used to implement the {@link
+ * AbstractAsyncSSLConnection.SSLConnectionChannel} which is handed of
+ * to RawChannelImpl when creating a WebSocket.
+ */
+class SSLDelegate {
+
+    final SSLEngine engine;
+    final EngineWrapper wrapper;
+    final Lock handshaking = new ReentrantLock();
+    final SocketChannel chan;
+
+    SSLDelegate(SSLEngine eng, SocketChannel chan)
+    {
+        this.engine = eng;
+        this.chan = chan;
+        this.wrapper = new EngineWrapper(chan, engine);
+    }
+
+    // alpn[] may be null
+//    SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn, String sn)
+//        throws IOException
+//    {
+//        serverName = sn;
+//        SSLContext context = client.sslContext();
+//        engine = context.createSSLEngine();
+//        engine.setUseClientMode(true);
+//        SSLParameters sslp = client.sslParameters();
+//        sslParameters = Utils.copySSLParameters(sslp);
+//        if (sn != null) {
+//            SNIHostName sni = new SNIHostName(sn);
+//            sslParameters.setServerNames(List.of(sni));
+//        }
+//        if (alpn != null) {
+//            sslParameters.setApplicationProtocols(alpn);
+//            Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn));
+//        } else {
+//            Log.logSSL("SSLDelegate: No application protocols proposed");
+//        }
+//        engine.setSSLParameters(sslParameters);
+//        wrapper = new EngineWrapper(chan, engine);
+//        this.chan = chan;
+//        this.client = client;
+//    }
+
+//    SSLParameters getSSLParameters() {
+//        return sslParameters;
+//    }
+
+    static long countBytes(ByteBuffer[] buffers, int start, int number) {
+        long c = 0;
+        for (int i=0; i<number; i++) {
+            c+= buffers[start+i].remaining();
+        }
+        return c;
+    }
+
+
+    static class WrapperResult {
+        static WrapperResult createOK() {
+            WrapperResult r = new WrapperResult();
+            r.buf = null;
+            r.result = new SSLEngineResult(Status.OK, NOT_HANDSHAKING, 0, 0);
+            return r;
+        }
+        SSLEngineResult result;
+
+        ByteBuffer buf; // buffer containing result data
+    }
+
+    int app_buf_size;
+    int packet_buf_size;
+
+    enum BufType {
+        PACKET,
+        APPLICATION
+    }
+
+    ByteBuffer allocate (BufType type) {
+        return allocate (type, -1);
+    }
+
+    // TODO: Use buffer pool for this
+    ByteBuffer allocate (BufType type, int len) {
+        assert engine != null;
+        synchronized (this) {
+            int size;
+            if (type == BufType.PACKET) {
+                if (packet_buf_size == 0) {
+                    SSLSession sess = engine.getSession();
+                    packet_buf_size = sess.getPacketBufferSize();
+                }
+                if (len > packet_buf_size) {
+                    packet_buf_size = len;
+                }
+                size = packet_buf_size;
+            } else {
+                if (app_buf_size == 0) {
+                    SSLSession sess = engine.getSession();
+                    app_buf_size = sess.getApplicationBufferSize();
+                }
+                if (len > app_buf_size) {
+                    app_buf_size = len;
+                }
+                size = app_buf_size;
+            }
+            return ByteBuffer.allocate (size);
+        }
+    }
+
+    /* reallocates the buffer by :-
+     * 1. creating a new buffer double the size of the old one
+     * 2. putting the contents of the old buffer into the new one
+     * 3. set xx_buf_size to the new size if it was smaller than new size
+     *
+     * flip is set to true if the old buffer needs to be flipped
+     * before it is copied.
+     */
+    private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {
+        // TODO: there should be the linear growth, rather than exponential as
+        // we definitely know the maximum amount of space required to unwrap
+        synchronized (this) {
+            int nsize = 2 * b.capacity();
+            ByteBuffer n = allocate (type, nsize);
+            if (flip) {
+                b.flip();
+            }
+            n.put(b);
+            b = n;
+        }
+        return b;
+    }
+
+    /**
+     * This is a thin wrapper over SSLEngine and the SocketChannel, which
+     * guarantees the ordering of wraps/unwraps with respect to the underlying
+     * channel read/writes. It handles the UNDER/OVERFLOW status codes
+     * It does not handle the handshaking status codes, or the CLOSED status code
+     * though once the engine is closed, any attempt to read/write to it
+     * will get an exception.  The overall result is returned.
+     * It functions synchronously/blocking
+     */
+    class EngineWrapper {
+
+        SocketChannel chan;
+        SSLEngine engine;
+        final Object wrapLock;
+        final Object unwrapLock;
+        ByteBuffer unwrap_src, wrap_dst;
+        boolean closed = false;
+        int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
+
+        EngineWrapper (SocketChannel chan, SSLEngine engine) {
+            this.chan = chan;
+            this.engine = engine;
+            wrapLock = new Object();
+            unwrapLock = new Object();
+            unwrap_src = allocate(BufType.PACKET);
+            wrap_dst = allocate(BufType.PACKET);
+        }
+
+//        void close () throws IOException {
+//        }
+
+        WrapperResult wrapAndSend(ByteBuffer src, boolean ignoreClose)
+            throws IOException
+        {
+            ByteBuffer[] buffers = new ByteBuffer[1];
+            buffers[0] = src;
+            return wrapAndSend(buffers, 0, 1, ignoreClose);
+        }
+
+        /* try to wrap and send the data in src. Handles OVERFLOW.
+         * Might block if there is an outbound blockage or if another
+         * thread is calling wrap(). Also, might not send any data
+         * if an unwrap is needed.
+         */
+        WrapperResult wrapAndSend(ByteBuffer[] src,
+                                  int offset,
+                                  int len,
+                                  boolean ignoreClose)
+            throws IOException
+        {
+            if (closed && !ignoreClose) {
+                throw new IOException ("Engine is closed");
+            }
+            Status status;
+            WrapperResult r = new WrapperResult();
+            synchronized (wrapLock) {
+                wrap_dst.clear();
+                do {
+                    r.result = engine.wrap (src, offset, len, wrap_dst);
+                    status = r.result.getStatus();
+                    if (status == Status.BUFFER_OVERFLOW) {
+                        wrap_dst = realloc (wrap_dst, true, BufType.PACKET);
+                    }
+                } while (status == Status.BUFFER_OVERFLOW);
+                if (status == Status.CLOSED && !ignoreClose) {
+                    closed = true;
+                    return r;
+                }
+                if (r.result.bytesProduced() > 0) {
+                    wrap_dst.flip();
+                    int l = wrap_dst.remaining();
+                    assert l == r.result.bytesProduced();
+                    while (l>0) {
+                        l -= chan.write (wrap_dst);
+                    }
+                }
+            }
+            return r;
+        }
+
+        /* block until a complete message is available and return it
+         * in dst, together with the Result. dst may have been re-allocated
+         * so caller should check the returned value in Result
+         * If handshaking is in progress then, possibly no data is returned
+         */
+        WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
+            Status status;
+            WrapperResult r = new WrapperResult();
+            r.buf = dst;
+            if (closed) {
+                throw new IOException ("Engine is closed");
+            }
+            boolean needData;
+            if (u_remaining > 0) {
+                unwrap_src.compact();
+                unwrap_src.flip();
+                needData = false;
+            } else {
+                unwrap_src.clear();
+                needData = true;
+            }
+            synchronized (unwrapLock) {
+                int x;
+                do {
+                    if (needData) {
+                        x = chan.read (unwrap_src);
+                        if (x == -1) {
+                            throw new IOException ("connection closed for reading");
+                        }
+                        unwrap_src.flip();
+                    }
+                    r.result = engine.unwrap (unwrap_src, r.buf);
+                    status = r.result.getStatus();
+                    if (status == Status.BUFFER_UNDERFLOW) {
+                        if (unwrap_src.limit() == unwrap_src.capacity()) {
+                            /* buffer not big enough */
+                            unwrap_src = realloc (
+                                unwrap_src, false, BufType.PACKET
+                            );
+                        } else {
+                            /* Buffer not full, just need to read more
+                             * data off the channel. Reset pointers
+                             * for reading off SocketChannel
+                             */
+                            unwrap_src.position (unwrap_src.limit());
+                            unwrap_src.limit (unwrap_src.capacity());
+                        }
+                        needData = true;
+                    } else if (status == Status.BUFFER_OVERFLOW) {
+                        r.buf = realloc (r.buf, true, BufType.APPLICATION);
+                        needData = false;
+                    } else if (status == Status.CLOSED) {
+                        closed = true;
+                        r.buf.flip();
+                        return r;
+                    }
+                } while (status != Status.OK);
+            }
+            u_remaining = unwrap_src.remaining();
+            return r;
+        }
+    }
+
+//    WrapperResult sendData (ByteBuffer src) throws IOException {
+//        ByteBuffer[] buffers = new ByteBuffer[1];
+//        buffers[0] = src;
+//        return sendData(buffers, 0, 1);
+//    }
+
+    /**
+     * send the data in the given ByteBuffer. If a handshake is needed
+     * then this is handled within this method. When this call returns,
+     * all of the given user data has been sent and any handshake has been
+     * completed. Caller should check if engine has been closed.
+     */
+    WrapperResult sendData (ByteBuffer[] src, int offset, int len) throws IOException {
+        WrapperResult r = WrapperResult.createOK();
+        while (countBytes(src, offset, len) > 0) {
+            r = wrapper.wrapAndSend(src, offset, len, false);
+            Status status = r.result.getStatus();
+            if (status == Status.CLOSED) {
+                doClosure ();
+                return r;
+            }
+            HandshakeStatus hs_status = r.result.getHandshakeStatus();
+            if (hs_status != HandshakeStatus.FINISHED &&
+                hs_status != HandshakeStatus.NOT_HANDSHAKING)
+            {
+                doHandshake(hs_status);
+            }
+        }
+        return r;
+    }
+
+    /**
+     * read data thru the engine into the given ByteBuffer. If the
+     * given buffer was not large enough, a new one is allocated
+     * and returned. This call handles handshaking automatically.
+     * Caller should check if engine has been closed.
+     */
+    WrapperResult recvData (ByteBuffer dst) throws IOException {
+        /* we wait until some user data arrives */
+        int mark = dst.position();
+        WrapperResult r = null;
+        int pos = dst.position();
+        while (dst.position() == pos) {
+            r = wrapper.recvAndUnwrap (dst);
+            dst = (r.buf != dst) ? r.buf: dst;
+            Status status = r.result.getStatus();
+            if (status == Status.CLOSED) {
+                doClosure ();
+                return r;
+            }
+
+            HandshakeStatus hs_status = r.result.getHandshakeStatus();
+            if (hs_status != HandshakeStatus.FINISHED &&
+                hs_status != HandshakeStatus.NOT_HANDSHAKING)
+            {
+                doHandshake (hs_status);
+            }
+        }
+        Utils.flipToMark(dst, mark);
+        return r;
+    }
+
+    /* we've received a close notify. Need to call wrap to send
+     * the response
+     */
+    void doClosure () throws IOException {
+        try {
+            handshaking.lock();
+            ByteBuffer tmp = allocate(BufType.APPLICATION);
+            WrapperResult r;
+            do {
+                tmp.clear();
+                tmp.flip ();
+                r = wrapper.wrapAndSend(tmp, true);
+            } while (r.result.getStatus() != Status.CLOSED);
+        } finally {
+            handshaking.unlock();
+        }
+    }
+
+    /* do the (complete) handshake after acquiring the handshake lock.
+     * If two threads call this at the same time, then we depend
+     * on the wrapper methods being idempotent. eg. if wrapAndSend()
+     * is called with no data to send then there must be no problem
+     */
+    @SuppressWarnings("fallthrough")
+    void doHandshake (HandshakeStatus hs_status) throws IOException {
+        boolean wasBlocking;
+        try {
+            wasBlocking = chan.isBlocking();
+            handshaking.lock();
+            chan.configureBlocking(true);
+            ByteBuffer tmp = allocate(BufType.APPLICATION);
+            while (hs_status != HandshakeStatus.FINISHED &&
+                   hs_status != HandshakeStatus.NOT_HANDSHAKING)
+            {
+                WrapperResult r = null;
+                switch (hs_status) {
+                    case NEED_TASK:
+                        Runnable task;
+                        while ((task = engine.getDelegatedTask()) != null) {
+                            /* run in current thread, because we are already
+                             * running an external Executor
+                             */
+                            task.run();
+                        }
+                        /* fall thru - call wrap again */
+                    case NEED_WRAP:
+                        tmp.clear();
+                        tmp.flip();
+                        r = wrapper.wrapAndSend(tmp, false);
+                        break;
+
+                    case NEED_UNWRAP:
+                        tmp.clear();
+                        r = wrapper.recvAndUnwrap (tmp);
+                        if (r.buf != tmp) {
+                            tmp = r.buf;
+                        }
+                        assert tmp.position() == 0;
+                        break;
+                }
+                hs_status = r.result.getHandshakeStatus();
+            }
+            Log.logSSL(getSessionInfo());
+            if (!wasBlocking) {
+                chan.configureBlocking(false);
+            }
+        } finally {
+            handshaking.unlock();
+        }
+    }
+
+//    static void printParams(SSLParameters p) {
+//        System.out.println("SSLParameters:");
+//        if (p == null) {
+//            System.out.println("Null params");
+//            return;
+//        }
+//        for (String cipher : p.getCipherSuites()) {
+//                System.out.printf("cipher: %s\n", cipher);
+//        }
+//        // JDK 8 EXCL START
+//        for (String approto : p.getApplicationProtocols()) {
+//                System.out.printf("application protocol: %s\n", approto);
+//        }
+//        // JDK 8 EXCL END
+//        for (String protocol : p.getProtocols()) {
+//                System.out.printf("protocol: %s\n", protocol);
+//        }
+//        if (p.getServerNames() != null) {
+//            for (SNIServerName sname : p.getServerNames()) {
+//                System.out.printf("server name: %s\n", sname.toString());
+//            }
+//        }
+//    }
+
+    String getSessionInfo() {
+        StringBuilder sb = new StringBuilder();
+        String application = engine.getApplicationProtocol();
+        SSLSession sess = engine.getSession();
+        String cipher = sess.getCipherSuite();
+        String protocol = sess.getProtocol();
+        sb.append("Handshake complete alpn: ")
+                .append(application)
+                .append(", Cipher: ")
+                .append(cipher)
+                .append(", Protocol: ")
+                .append(protocol);
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/SocketTube.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,956 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import jdk.incubator.http.internal.common.Demand;
+import jdk.incubator.http.internal.common.FlowTube;
+import jdk.incubator.http.internal.common.SequentialScheduler;
+import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
+import jdk.incubator.http.internal.common.SequentialScheduler.RestartableTask;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * A SocketTube is a terminal tube plugged directly into the socket.
+ * The read subscriber should call {@code subscribe} on the SocketTube before
+ * the SocketTube can be subscribed to the write publisher.
+ */
+final class SocketTube implements FlowTube {
+
+    static final boolean DEBUG = Utils.DEBUG; // revisit: temporary developer's flag
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+    static final AtomicLong IDS = new AtomicLong();
+
+    private final HttpClientImpl client;
+    private final SocketChannel channel;
+    private final Supplier<ByteBuffer> buffersSource;
+    private final Object lock = new Object();
+    private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+    private final InternalReadPublisher readPublisher;
+    private final InternalWriteSubscriber writeSubscriber;
+    private final long id = IDS.incrementAndGet();
+
+    public SocketTube(HttpClientImpl client, SocketChannel channel,
+                      Supplier<ByteBuffer> buffersSource) {
+        this.client = client;
+        this.channel = channel;
+        this.buffersSource = buffersSource;
+        this.readPublisher = new InternalReadPublisher();
+        this.writeSubscriber = new InternalWriteSubscriber();
+    }
+
+//    private static Flow.Subscription nopSubscription() {
+//        return new Flow.Subscription() {
+//            @Override public void request(long n) { }
+//            @Override public void cancel() { }
+//        };
+//    }
+
+    /**
+     * Returns {@code true} if this flow is finished.
+     * This happens when this flow internal read subscription is completed,
+     * either normally (EOF reading) or exceptionally  (EOF writing, or
+     * underlying socket closed, or some exception occurred while reading or
+     * writing to the socket).
+     *
+     * @return {@code true} if this flow is finished.
+     */
+    public boolean isFinished() {
+        InternalReadPublisher.InternalReadSubscription subscription =
+                readPublisher.subscriptionImpl;
+        return subscription != null && subscription.completed
+                || subscription == null && errorRef.get() != null;
+    }
+
+    // ===================================================================== //
+    //                       Flow.Publisher                                  //
+    // ======================================================================//
+
+    /**
+     * {@inheritDoc }
+     * @apiNote This method should be called first. In particular, the caller
+     *          must ensure that this method must be called by the read
+     *          subscriber before the write publisher can call {@code onSubscribe}.
+     *          Failure to adhere to this contract may result in assertion errors.
+     */
+    @Override
+    public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
+        Objects.requireNonNull(s);
+        assert s instanceof TubeSubscriber : "Expected TubeSubscriber, got:" + s;
+        readPublisher.subscribe(s);
+    }
+
+
+    // ===================================================================== //
+    //                       Flow.Subscriber                                 //
+    // ======================================================================//
+
+    /**
+     * {@inheritDoc }
+     * @apiNote The caller must ensure that {@code subscribe} is called by
+     *          the read subscriber before {@code onSubscribe} is called by
+     *          the write publisher.
+     *          Failure to adhere to this contract may result in assertion errors.
+     */
+    @Override
+    public void onSubscribe(Flow.Subscription subscription) {
+        writeSubscriber.onSubscribe(subscription);
+    }
+
+    @Override
+    public void onNext(List<ByteBuffer> item) {
+        writeSubscriber.onNext(item);
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        writeSubscriber.onError(throwable);
+    }
+
+    @Override
+    public void onComplete() {
+        writeSubscriber.onComplete();
+    }
+
+    // ===================================================================== //
+    //                           Events                                      //
+    // ======================================================================//
+
+    /**
+     * A restartable task used to process tasks in sequence.
+     */
+    private static class SocketFlowTask implements RestartableTask {
+        final Runnable task;
+        private final Object monitor = new Object();
+        SocketFlowTask(Runnable task) {
+            this.task = task;
+        }
+        @Override
+        public final void run(DeferredCompleter taskCompleter) {
+            try {
+                // non contentious synchronized for visibility.
+                synchronized(monitor) {
+                    task.run();
+                }
+            } finally {
+                taskCompleter.complete();
+            }
+        }
+    }
+
+    // This is best effort - there's no guarantee that the printed set
+    // of values is consistent. It should only be considered as
+    // weakly accurate - in particular in what concerns the events states,
+    // especially when displaying a read event state from a write event
+    // callback and conversely.
+    void debugState(String when) {
+        if (debug.isLoggable(Level.DEBUG)) {
+            StringBuilder state = new StringBuilder();
+
+            InternalReadPublisher.InternalReadSubscription sub =
+                    readPublisher.subscriptionImpl;
+            InternalReadPublisher.ReadEvent readEvent =
+                    sub == null ? null : sub.readEvent;
+            Demand rdemand = sub == null ? null : sub.demand;
+            InternalWriteSubscriber.WriteEvent writeEvent =
+                    writeSubscriber.writeEvent;
+            AtomicLong wdemand = writeSubscriber.writeDemand;
+            int rops = readEvent == null ? 0 : readEvent.interestOps();
+            long rd = rdemand == null ? 0 : rdemand.get();
+            int wops = writeEvent == null ? 0 : writeEvent.interestOps();
+            long wd = wdemand == null ? 0 : wdemand.get();
+
+            state.append(when).append(" Reading: [ops=")
+                    .append(rops).append(", demand=").append(rd)
+                    .append(", stopped=")
+                    .append((sub == null ? false : sub.readScheduler.isStopped()))
+                    .append("], Writing: [ops=").append(wops)
+                    .append(", demand=").append(wd)
+                    .append("]");
+            debug.log(Level.DEBUG, state.toString());
+        }
+    }
+
+    /**
+     * A repeatable event that can be paused or resumed by changing
+     * its interestOps.
+     * When the event is fired, it is first paused before being signaled.
+     * It is the responsibility of the code triggered by {@code signalEvent}
+     * to resume the event if required.
+     */
+    private static abstract class SocketFlowEvent extends AsyncEvent {
+        final SocketChannel channel;
+        final int defaultInterest;
+        volatile int interestOps;
+        volatile boolean registered;
+        SocketFlowEvent(int defaultInterest, SocketChannel channel) {
+            super(AsyncEvent.REPEATING);
+            this.defaultInterest = defaultInterest;
+            this.channel = channel;
+        }
+        final boolean registered() {return registered;}
+        final void resume() {
+            interestOps = defaultInterest;
+            registered = true;
+        }
+        final void pause() {interestOps = 0;}
+        @Override
+        public final SelectableChannel channel() {return channel;}
+        @Override
+        public final int interestOps() {return interestOps;}
+
+        @Override
+        public final void handle() {
+            pause();       // pause, then signal
+            signalEvent(); // won't be fired again until resumed.
+        }
+        @Override
+        public final void abort(IOException error) {
+            debug().log(Level.DEBUG, () -> "abort: " + error);
+            pause();              // pause, then signal
+            signalError(error);   // should not be resumed after abort (not checked)
+        }
+
+        protected abstract void signalEvent();
+        protected abstract void signalError(Throwable error);
+        abstract System.Logger debug();
+    }
+
+    // ===================================================================== //
+    //                              Writing                                  //
+    // ======================================================================//
+
+    // This class makes the assumption that the publisher will call
+    // onNext sequentially, and that onNext won't be called if the demand
+    // has not been incremented by request(1).
+    // It has a 'queue of 1' meaning that it will call request(1) in
+    // onSubscribe, and then only after its 'current' buffer list has been
+    // fully written and current set to null;
+    private final class InternalWriteSubscriber
+            implements Flow.Subscriber<List<ByteBuffer>> {
+
+        volatile Flow.Subscription subscription;
+        volatile List<ByteBuffer> current;
+        volatile boolean completed;
+        final WriteEvent writeEvent = new WriteEvent(channel, this);
+        final AtomicLong writeDemand = new AtomicLong();
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            Flow.Subscription previous = this.subscription;
+            this.subscription = subscription;
+            debug.log(Level.DEBUG, "subscribed for writing");
+            if (current == null) {
+                if (previous == subscription || previous == null) {
+                    if (writeDemand.compareAndSet(0, 1)) {
+                        subscription.request(1);
+                    }
+                } else {
+                    writeDemand.set(1);
+                    subscription.request(1);
+                }
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> bufs) {
+            assert current == null; // this is a queue of 1.
+            assert subscription != null;
+            current = bufs;
+            tryFlushCurrent(client.isSelectorThread()); // may be in selector thread
+            // For instance in HTTP/2, a received SETTINGS frame might trigger
+            // the sending of a SETTINGS frame in turn which might cause
+            // onNext to be called from within the same selector thread that the
+            // original SETTINGS frames arrived on. If rs is the read-subscriber
+            // and ws is the write-subscriber then the following can occur:
+            // ReadEvent -> rs.onNext(bytes) -> process server SETTINGS -> write
+            // client SETTINGS -> ws.onNext(bytes) -> tryFlushCurrent
+            debugState("leaving w.onNext");
+        }
+
+        // we don't use a SequentialScheduler here: we rely on
+        // onNext() being called sequentially, and not being called
+        // if we haven't call request(1)
+        // onNext is usually called from within a user/executor thread.
+        // we will perform the initial writing in that thread.
+        // if for some reason, not all data can be written, the writeEvent
+        // will be resumed, and the rest of the data will be written from
+        // the selector manager thread when the writeEvent is fired.
+        // If we are in the selector manager thread, then we will use the executor
+        // to call request(1), ensuring that onNext() won't be called from
+        // within the selector thread.
+        // If we are not in the selector manager thread, then we don't care.
+        void tryFlushCurrent(boolean inSelectorThread) {
+            List<ByteBuffer> bufs = current;
+            if (bufs == null) return;
+            try {
+                assert inSelectorThread == client.isSelectorThread() :
+                       "should " + (inSelectorThread ? "" : "not ")
+                        + " be in the selector thread";
+                long remaining = Utils.remaining(bufs);
+                debug.log(Level.DEBUG, "trying to write: %d", remaining);
+                long written = writeAvailable(bufs);
+                debug.log(Level.DEBUG, "wrote: %d", remaining);
+                if (written == -1) {
+                    signalError(new EOFException("EOF reached while writing"));
+                    return;
+                }
+                assert written <= remaining;
+                if (remaining - written == 0) {
+                    current = null;
+                    writeDemand.decrementAndGet();
+                    Runnable requestMore = this::requestMore;
+                    if (inSelectorThread) {
+                        assert client.isSelectorThread();
+                        client.theExecutor().execute(requestMore);
+                    } else {
+                        assert !client.isSelectorThread();
+                        requestMore.run();
+                    }
+                } else {
+                    resumeWriteEvent(inSelectorThread);
+                }
+            } catch (Throwable t) {
+                signalError(t);
+                subscription.cancel();
+            }
+        }
+
+        void requestMore() {
+            try {
+                if (completed) return;
+                long d =  writeDemand.get();
+                if (writeDemand.compareAndSet(0,1)) {
+                    debug.log(Level.DEBUG, "write: requesting more...");
+                    subscription.request(1);
+                } else {
+                    debug.log(Level.DEBUG, "write: no need to request more: %d", d);
+                }
+            } catch (Throwable t) {
+                debug.log(Level.DEBUG, () ->
+                        "write: error while requesting more: " + t);
+                signalError(t);
+                subscription.cancel();
+            } finally {
+                debugState("leaving requestMore: ");
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            signalError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            completed = true;
+            // no need to pause the write event here: the write event will
+            // be paused if there is nothing more to write.
+            List<ByteBuffer> bufs = current;
+            long remaining = bufs == null ? 0 : Utils.remaining(bufs);
+            debug.log(Level.DEBUG,  "write completed, %d yet to send", remaining);
+            debugState("InternalWriteSubscriber::onComplete");
+        }
+
+        void resumeWriteEvent(boolean inSelectorThread) {
+            debug.log(Level.DEBUG, "scheduling write event");
+            resumeEvent(writeEvent, this::signalError);
+        }
+
+//        void pauseWriteEvent() {
+//            debug.log(Level.DEBUG, "pausing write event");
+//            pauseEvent(writeEvent, this::signalError);
+//        }
+
+        void signalWritable() {
+            debug.log(Level.DEBUG, "channel is writable");
+            tryFlushCurrent(true);
+        }
+
+        void signalError(Throwable error) {
+            debug.log(Level.DEBUG, () -> "write error: " + error);
+            completed = true;
+            readPublisher.signalError(error);
+        }
+
+        // A repeatable WriteEvent which is paused after firing and can
+        // be resumed if required - see SocketFlowEvent;
+        final class WriteEvent extends SocketFlowEvent {
+            final InternalWriteSubscriber sub;
+            WriteEvent(SocketChannel channel, InternalWriteSubscriber sub) {
+                super(SelectionKey.OP_WRITE, channel);
+                this.sub = sub;
+            }
+            @Override
+            protected final void signalEvent() {
+                try {
+                    client.eventUpdated(this);
+                    sub.signalWritable();
+                } catch(Throwable t) {
+                    sub.signalError(t);
+                }
+            }
+
+            @Override
+            protected void signalError(Throwable error) {
+                sub.signalError(error);
+            }
+
+            @Override
+            System.Logger debug() {
+                return debug;
+            }
+
+        }
+
+    }
+
+    // ===================================================================== //
+    //                              Reading                                  //
+    // ===================================================================== //
+
+    // The InternalReadPublisher uses a SequentialScheduler to ensure that
+    // onNext/onError/onComplete are called sequentially on the caller's
+    // subscriber.
+    // However, it relies on the fact that the only time where
+    // runOrSchedule() is called from a user/executor thread is in signalError,
+    // right after the errorRef has been set.
+    // Because the sequential scheduler's task always checks for errors first,
+    // and always terminate the scheduler on error, then it is safe to assume
+    // that if it reaches the point where it reads from the channel, then
+    // it is running in the SelectorManager thread. This is because all
+    // other invocation of runOrSchedule() are triggered from within a
+    // ReadEvent.
+    //
+    // When pausing/resuming the event, some shortcuts can then be taken
+    // when we know we're running in the selector manager thread
+    // (in that case there's no need to call client.eventUpdated(readEvent);
+    //
+    private final class InternalReadPublisher
+            implements Flow.Publisher<List<ByteBuffer>> {
+        private final InternalReadSubscription subscriptionImpl
+                = new InternalReadSubscription();
+        AtomicReference<ReadSubscription> pendingSubscription = new AtomicReference<>();
+        private volatile ReadSubscription subscription;
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
+            Objects.requireNonNull(s);
+
+            TubeSubscriber sub = FlowTube.asTubeSubscriber(s);
+            ReadSubscription target = new ReadSubscription(subscriptionImpl, sub);
+            ReadSubscription previous = pendingSubscription.getAndSet(target);
+
+            if (previous != null && previous != target) {
+                debug.log(Level.DEBUG,
+                        () -> "read publisher: dropping pending subscriber: "
+                        + previous.subscriber);
+                previous.errorRef.compareAndSet(null, errorRef.get());
+                previous.signalOnSubscribe();
+                if (subscriptionImpl.completed) {
+                    previous.signalCompletion();
+                } else {
+                    previous.subscriber.dropSubscription();
+                }
+            }
+
+            debug.log(Level.DEBUG, "read publisher got subscriber");
+            subscriptionImpl.signalSubscribe();
+            debugState("leaving read.subscribe: ");
+        }
+
+        void signalError(Throwable error) {
+            if (!errorRef.compareAndSet(null, error)) {
+                return;
+            }
+            subscriptionImpl.handleError();
+        }
+
+        final class ReadSubscription implements Flow.Subscription {
+            final InternalReadSubscription impl;
+            final TubeSubscriber  subscriber;
+            final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+            volatile boolean subscribed;
+            volatile boolean cancelled;
+            volatile boolean completed;
+
+            public ReadSubscription(InternalReadSubscription impl,
+                                    TubeSubscriber subscriber) {
+                this.impl = impl;
+                this.subscriber = subscriber;
+            }
+
+            @Override
+            public void cancel() {
+                cancelled = true;
+            }
+
+            @Override
+            public void request(long n) {
+                if (!cancelled) {
+                    impl.request(n);
+                } else {
+                    debug.log(Level.DEBUG,
+                              "subscription cancelled, ignoring request %d", n);
+                }
+            }
+
+            void signalCompletion() {
+                assert subscribed || cancelled;
+                if (completed || cancelled) return;
+                synchronized (this) {
+                    if (completed) return;
+                    completed = true;
+                }
+                Throwable error = errorRef.get();
+                if (error != null) {
+                    debug.log(Level.DEBUG, () ->
+                        "forwarding error to subscriber: "
+                        + error);
+                    subscriber.onError(error);
+                } else {
+                    debug.log(Level.DEBUG, "completing subscriber");
+                    subscriber.onComplete();
+                }
+            }
+
+            void signalOnSubscribe() {
+                if (subscribed || cancelled) return;
+                synchronized (this) {
+                    if (subscribed || cancelled) return;
+                    subscribed = true;
+                }
+                subscriber.onSubscribe(this);
+                debug.log(Level.DEBUG, "onSubscribe called");
+                if (errorRef.get() != null) {
+                    signalCompletion();
+                }
+            }
+        }
+
+        final class InternalReadSubscription implements Flow.Subscription {
+
+            private final Demand demand = new Demand();
+            final SequentialScheduler readScheduler;
+            private volatile boolean completed;
+            private final ReadEvent readEvent;
+            private final AsyncEvent subscribeEvent;
+
+            InternalReadSubscription() {
+                readScheduler = new SequentialScheduler(new SocketFlowTask(this::read));
+                subscribeEvent = new AsyncTriggerEvent(this::signalError,
+                                                       this::handleSubscribeEvent);
+                readEvent = new ReadEvent(channel, this);
+            }
+
+            /*
+             * This method must be invoked before any other method of this class.
+             */
+            final void signalSubscribe() {
+                if (readScheduler.isStopped() || completed) {
+                    // if already completed or stopped we can handle any
+                    // pending connection directly from here.
+                    debug.log(Level.DEBUG,
+                              "handling pending subscription while completed");
+                    handlePending();
+                } else {
+                    try {
+                        debug.log(Level.DEBUG,
+                                  "registering subscribe event");
+                        client.registerEvent(subscribeEvent);
+                    } catch (Throwable t) {
+                        signalError(t);
+                        handlePending();
+                    }
+                }
+            }
+
+            final void handleSubscribeEvent() {
+                assert client.isSelectorThread();
+                debug.log(Level.DEBUG, "subscribe event raised");
+                readScheduler.runOrSchedule();
+                if (readScheduler.isStopped() || completed) {
+                    // if already completed or stopped we can handle any
+                    // pending connection directly from here.
+                    debug.log(Level.DEBUG,
+                              "handling pending subscription when completed");
+                    handlePending();
+                }
+            }
+
+
+            /*
+             * Although this method is thread-safe, the Reactive-Streams spec seems
+             * to not require it to be as such. It's a responsibility of the
+             * subscriber to signal demand in a thread-safe manner.
+             *
+             * https://github.com/reactive-streams/reactive-streams-jvm/blob/dd24d2ab164d7de6c316f6d15546f957bec29eaa/README.md
+             * (rules 2.7 and 3.4)
+             */
+            @Override
+            public final void request(long n) {
+                if (n > 0L) {
+                    boolean wasFulfilled = demand.increase(n);
+                    if (wasFulfilled) {
+                        debug.log(Level.DEBUG, "got some demand for reading");
+                        resumeReadEvent();
+                        // if demand has been changed from fulfilled
+                        // to unfulfilled register read event;
+                    }
+                } else {
+                    signalError(new IllegalArgumentException("non-positive request"));
+                }
+                debugState("leaving request("+n+"): ");
+            }
+
+            @Override
+            public final void cancel() {
+                pauseReadEvent();
+                readScheduler.stop();
+            }
+
+            private void resumeReadEvent() {
+                debug.log(Level.DEBUG, "resuming read event");
+                resumeEvent(readEvent, this::signalError);
+            }
+
+            private void pauseReadEvent() {
+                debug.log(Level.DEBUG, "pausing read event");
+                pauseEvent(readEvent, this::signalError);
+            }
+
+
+            final void handleError() {
+                assert errorRef.get() != null;
+                readScheduler.runOrSchedule();
+            }
+
+            final void signalError(Throwable error) {
+                if (!errorRef.compareAndSet(null, error)) {
+                    return;
+                }
+                debug.log(Level.DEBUG, () -> "got read error: " + error);
+                readScheduler.runOrSchedule();
+            }
+
+            final void signalReadable() {
+                readScheduler.runOrSchedule();
+            }
+
+            /** The body of the task that runs in SequentialScheduler. */
+            final void read() {
+                // It is important to only call pauseReadEvent() when stopping
+                // the scheduler. The event is automatically paused before
+                // firing, and trying to pause it again could cause a race
+                // condition between this loop, which calls tryDecrementDemand(),
+                // and the thread that calls request(n), which will try to resume
+                // reading.
+                try {
+                    while(!readScheduler.isStopped()) {
+                        if (completed) return;
+
+                        // make sure we have a subscriber
+                        if (handlePending()) {
+                            debug.log(Level.DEBUG, "pending subscriber subscribed");
+                            return;
+                        }
+
+                        // If an error was signaled, we might not be in the
+                        // the selector thread, and that is OK, because we
+                        // will just call onError and return.
+                        ReadSubscription current = subscription;
+                        TubeSubscriber subscriber = current.subscriber;
+                        Throwable error = errorRef.get();
+                        if (error != null) {
+                            completed = true;
+                            // safe to pause here because we're finished anyway.
+                            pauseReadEvent();
+                            debug.log(Level.DEBUG, () -> "Sending error " + error
+                                  + " to subscriber " + subscriber);
+                            current.errorRef.compareAndSet(null, error);
+                            current.signalCompletion();
+                            readScheduler.stop();
+                            debugState("leaving read() loop with error: ");
+                            return;
+                        }
+
+                        // If we reach here then we must be in the selector thread.
+                        assert client.isSelectorThread();
+                        if (demand.tryDecrement()) {
+                            // we have demand.
+                            try {
+                                List<ByteBuffer> bytes = readAvailable();
+                                if (bytes == EOF) {
+                                    if (!completed) {
+                                        debug.log(Level.DEBUG, "got read EOF");
+                                        completed = true;
+                                        // safe to pause here because we're finished
+                                        // anyway.
+                                        pauseReadEvent();
+                                        current.signalCompletion();
+                                        readScheduler.stop();
+                                    }
+                                    debugState("leaving read() loop after EOF: ");
+                                    return;
+                                } else if (Utils.remaining(bytes) > 0) {
+                                    // the subscriber is responsible for offloading
+                                    // to another thread if needed.
+                                    debug.log(Level.DEBUG, () -> "read bytes: "
+                                            + Utils.remaining(bytes));
+                                    assert !current.completed;
+                                    subscriber.onNext(bytes);
+                                    // we could continue looping until the demand
+                                    // reaches 0. However, that would risk starving
+                                    // other connections (bound to other socket
+                                    // channels) - as other selected keys activated
+                                    // by the selector manager thread might be
+                                    // waiting for this event to terminate.
+                                    // So resume the read event and return now...
+                                    resumeReadEvent();
+                                    debugState("leaving read() loop after onNext: ");
+                                    return;
+                                } else {
+                                    // nothing available!
+                                    debug.log(Level.DEBUG, "no more bytes available");
+                                    // re-increment the demand and resume the read
+                                    // event. This ensures that this loop is
+                                    // executed again when the socket becomes
+                                    // readable again.
+                                    demand.increase(1);
+                                    resumeReadEvent();
+                                    debugState("leaving read() loop with no bytes");
+                                    return;
+                                }
+                            } catch (Throwable x) {
+                                signalError(x);
+                                continue;
+                            }
+                        } else {
+                            debug.log(Level.DEBUG, "no more demand for reading");
+                            // the event is paused just after firing, so it should
+                            // still be paused here, unless the demand was just
+                            // incremented from 0 to n, in which case, the
+                            // event will be resumed, causing this loop to be
+                            // invoked again when the socket becomes readable:
+                            // This is what we want.
+                            // Trying to pause the event here would actually
+                            // introduce a race condition between this loop and
+                            // request(n).
+                            debugState("leaving read() loop with no demand");
+                            break;
+                        }
+                    }
+                } catch (Throwable t) {
+                    debug.log(Level.DEBUG, "Unexpected exception in read loop", t);
+                    signalError(t);
+                } finally {
+                    handlePending();
+                }
+            }
+
+            boolean handlePending() {
+                ReadSubscription pending = pendingSubscription.getAndSet(null);
+                if (pending == null) return false;
+                debug.log(Level.DEBUG, "handling pending subscription for %s",
+                          pending.subscriber);
+                ReadSubscription current = subscription;
+                if (current != null && current != pending && !completed) {
+                    current.subscriber.dropSubscription();
+                }
+                debug.log(Level.DEBUG, "read demand reset to 0");
+                subscriptionImpl.demand.reset(); // subscriber will increase demand if it needs to.
+                pending.errorRef.compareAndSet(null, errorRef.get());
+                if (!readScheduler.isStopped()) {
+                    subscription = pending;
+                } else {
+                    debug.log(Level.DEBUG, "socket tube is already stopped");
+                }
+                debug.log(Level.DEBUG, "calling onSubscribe");
+                pending.signalOnSubscribe();
+                if (completed) {
+                    pending.errorRef.compareAndSet(null, errorRef.get());
+                    pending.signalCompletion();
+                }
+                return true;
+            }
+        }
+
+
+        // A repeatable ReadEvent which is paused after firing and can
+        // be resumed if required - see SocketFlowEvent;
+        final class ReadEvent extends SocketFlowEvent {
+            final InternalReadSubscription sub;
+            ReadEvent(SocketChannel channel, InternalReadSubscription sub) {
+                super(SelectionKey.OP_READ, channel);
+                this.sub = sub;
+            }
+            @Override
+            protected final void signalEvent() {
+                try {
+                    client.eventUpdated(this);
+                    sub.signalReadable();
+                } catch(Throwable t) {
+                    sub.signalError(t);
+                }
+            }
+
+            @Override
+            protected final void signalError(Throwable error) {
+                sub.signalError(error);
+            }
+
+            @Override
+            System.Logger debug() {
+                return debug;
+            }
+        }
+
+    }
+
+    // ===================================================================== //
+    //                   Socket Channel Read/Write                           //
+    // ===================================================================== //
+    static final int MAX_BUFFERS = 3;
+    static final List<ByteBuffer> EOF = List.of();
+
+    private List<ByteBuffer> readAvailable() throws IOException {
+        ByteBuffer buf = buffersSource.get();
+        assert buf.hasRemaining();
+
+        int read;
+        int pos = buf.position();
+        List<ByteBuffer> list = null;
+        while (buf.hasRemaining()) {
+            while ((read = channel.read(buf)) > 0) {
+               if (!buf.hasRemaining()) break;
+            }
+
+            // nothing read;
+            if (buf.position() == pos) {
+                // An empty list signal the end of data, and should only be
+                // returned if read == -1.
+                // If we already read some data, then we must return what we have
+                // read, and -1 will be returned next time the caller attempts to
+                // read something.
+                if (list == null && read == -1) {  // eof
+                    list = EOF;
+                    break;
+                }
+            }
+            buf.limit(buf.position());
+            buf.position(pos);
+            if (list == null) {
+                list = List.of(buf);
+            } else {
+                if (!(list instanceof ArrayList)) {
+                    list = new ArrayList<>(list);
+                }
+                list.add(buf);
+            }
+            if (read <= 0 || list.size() == MAX_BUFFERS) break;
+            buf = buffersSource.get();
+            pos = buf.position();
+            assert buf.hasRemaining();
+        }
+        return list;
+    }
+
+    private long writeAvailable(List<ByteBuffer> bytes) throws IOException {
+        ByteBuffer[] srcs = bytes.toArray(Utils.EMPTY_BB_ARRAY);
+        final long remaining = Utils.remaining(srcs);
+        long written = 0;
+        while (remaining > written) {
+            long w = channel.write(srcs);
+            if (w == -1 && written == 0) return -1;
+            if (w == 0) break;
+            written += w;
+        }
+        return written;
+    }
+
+    private void resumeEvent(SocketFlowEvent event,
+                             Consumer<Throwable> errorSignaler) {
+        boolean registrationRequired;
+        synchronized(lock) {
+            registrationRequired = !event.registered();
+            event.resume();
+        }
+        try {
+            if (registrationRequired) {
+                client.registerEvent(event);
+             } else {
+                client.eventUpdated(event);
+            }
+        } catch(Throwable t) {
+            errorSignaler.accept(t);
+        }
+   }
+
+    private void pauseEvent(SocketFlowEvent event,
+                            Consumer<Throwable> errorSignaler) {
+        synchronized(lock) {
+            event.pause();
+        }
+        try {
+            client.eventUpdated(event);
+        } catch(Throwable t) {
+            errorSignaler.accept(t);
+        }
+    }
+
+    @Override
+    public void connectFlows(TubePublisher writePublisher,
+                             TubeSubscriber readSubscriber) {
+        debug.log(Level.DEBUG, "connecting flows");
+        this.subscribe(readSubscriber);
+        writePublisher.subscribe(this);
+    }
+
+
+    @Override
+    public String toString() {
+        return dbgString();
+    }
+
+    final String dbgString() {
+        return "SocketTube("+id+")";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/Stream.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,1170 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpHeaders;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpResponse.BodySubscriber;
+import jdk.incubator.http.internal.common.*;
+import jdk.incubator.http.internal.frame.*;
+import jdk.incubator.http.internal.hpack.DecodingCallback;
+
+/**
+ * Http/2 Stream handling.
+ *
+ * REQUESTS
+ *
+ * sendHeadersOnly() -- assembles HEADERS frame and puts on connection outbound Q
+ *
+ * sendRequest() -- sendHeadersOnly() + sendBody()
+ *
+ * sendBodyAsync() -- calls sendBody() in an executor thread.
+ *
+ * sendHeadersAsync() -- calls sendHeadersOnly() which does not block
+ *
+ * sendRequestAsync() -- calls sendRequest() in an executor thread
+ *
+ * RESPONSES
+ *
+ * Multiple responses can be received per request. Responses are queued up on
+ * a LinkedList of CF<HttpResponse> and the the first one on the list is completed
+ * with the next response
+ *
+ * getResponseAsync() -- queries list of response CFs and returns first one
+ *               if one exists. Otherwise, creates one and adds it to list
+ *               and returns it. Completion is achieved through the
+ *               incoming() upcall from connection reader thread.
+ *
+ * getResponse() -- calls getResponseAsync() and waits for CF to complete
+ *
+ * responseBodyAsync() -- calls responseBody() in an executor thread.
+ *
+ * incoming() -- entry point called from connection reader thread. Frames are
+ *               either handled immediately without blocking or for data frames
+ *               placed on the stream's inputQ which is consumed by the stream's
+ *               reader thread.
+ *
+ * PushedStream sub class
+ * ======================
+ * Sending side methods are not used because the request comes from a PUSH_PROMISE
+ * frame sent by the server. When a PUSH_PROMISE is received the PushedStream
+ * is created. PushedStream does not use responseCF list as there can be only
+ * one response. The CF is created when the object created and when the response
+ * HEADERS frame is received the object is completed.
+ */
+class Stream<T> extends ExchangeImpl<T> {
+
+    final static boolean DEBUG = Utils.DEBUG; // Revisit: temporary developer's flag
+    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    final ConcurrentLinkedQueue<Http2Frame> inputQ = new ConcurrentLinkedQueue<>();
+    final SequentialScheduler sched =
+            SequentialScheduler.synchronizedScheduler(this::schedule);
+    final SubscriptionBase userSubscription = new SubscriptionBase(sched, this::cancel);
+
+    /**
+     * This stream's identifier. Assigned lazily by the HTTP2Connection before
+     * the stream's first frame is sent.
+     */
+    protected volatile int streamid;
+
+    long requestContentLen;
+
+    final Http2Connection connection;
+    final HttpRequestImpl request;
+    final DecodingCallback rspHeadersConsumer;
+    HttpHeadersImpl responseHeaders;
+    final HttpHeadersImpl requestPseudoHeaders;
+    volatile HttpResponse.BodySubscriber<T> responseSubscriber;
+    final HttpRequest.BodyPublisher requestPublisher;
+    volatile RequestSubscriber requestSubscriber;
+    volatile int responseCode;
+    volatile Response response;
+    volatile Throwable failed; // The exception with which this stream was canceled.
+    final CompletableFuture<Void> requestBodyCF = new MinimalFuture<>();
+    volatile CompletableFuture<T> responseBodyCF;
+
+    /** True if END_STREAM has been seen in a frame received on this stream. */
+    private volatile boolean remotelyClosed;
+    private volatile boolean closed;
+    private volatile boolean endStreamSent;
+
+    // state flags
+    private boolean requestSent, responseReceived;
+
+    /**
+     * A reference to this Stream's connection Send Window controller. The
+     * stream MUST acquire the appropriate amount of Send Window before
+     * sending any data. Will be null for PushStreams, as they cannot send data.
+     */
+    private final WindowController windowController;
+    private final WindowUpdateSender windowUpdater;
+
+    @Override
+    HttpConnection connection() {
+        return connection.connection;
+    }
+
+    /**
+     * Invoked either from incoming() -> {receiveDataFrame() or receiveResetFrame() }
+     * of after user subscription window has re-opened, from SubscriptionBase.request()
+     */
+    private void schedule() {
+        if (responseSubscriber == null)
+            // can't process anything yet
+            return;
+
+        try {
+            while (!inputQ.isEmpty()) {
+                Http2Frame frame = inputQ.peek();
+                if (frame instanceof ResetFrame) {
+                    inputQ.remove();
+                    handleReset((ResetFrame)frame);
+                    return;
+                }
+                DataFrame df = (DataFrame)frame;
+                boolean finished = df.getFlag(DataFrame.END_STREAM);
+
+                List<ByteBuffer> buffers = df.getData();
+                List<ByteBuffer> dsts = Collections.unmodifiableList(buffers);
+                int size = Utils.remaining(dsts, Integer.MAX_VALUE);
+                if (size == 0 && finished) {
+                    inputQ.remove();
+                    Log.logTrace("responseSubscriber.onComplete");
+                    debug.log(Level.DEBUG, "incoming: onComplete");
+                    sched.stop();
+                    responseSubscriber.onComplete();
+                    setEndStreamReceived();
+                    return;
+                } else if (userSubscription.tryDecrement()) {
+                    inputQ.remove();
+                    Log.logTrace("responseSubscriber.onNext {0}", size);
+                    debug.log(Level.DEBUG, "incoming: onNext(%d)", size);
+                    responseSubscriber.onNext(dsts);
+                    if (consumed(df)) {
+                        Log.logTrace("responseSubscriber.onComplete");
+                        debug.log(Level.DEBUG, "incoming: onComplete");
+                        sched.stop();
+                        responseSubscriber.onComplete();
+                        setEndStreamReceived();
+                        return;
+                    }
+                } else {
+                    return;
+                }
+            }
+        } catch (Throwable throwable) {
+            failed = throwable;
+        }
+
+        Throwable t = failed;
+        if (t != null) {
+            sched.stop();
+            responseSubscriber.onError(t);
+            close();
+        }
+    }
+
+    // Callback invoked after the Response BodySubscriber has consumed the
+    // buffers contained in a DataFrame.
+    // Returns true if END_STREAM is reached, false otherwise.
+    private boolean consumed(DataFrame df) {
+        // RFC 7540 6.1:
+        // The entire DATA frame payload is included in flow control,
+        // including the Pad Length and Padding fields if present
+        int len = df.payloadLength();
+        connection.windowUpdater.update(len);
+
+        if (!df.getFlag(DataFrame.END_STREAM)) {
+            // Don't send window update on a stream which is
+            // closed or half closed.
+            windowUpdater.update(len);
+            return false; // more data coming
+        }
+        return true; // end of stream
+    }
+
+    @Override
+    CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler,
+                                       boolean returnConnectionToPool,
+                                       Executor executor)
+    {
+        Log.logTrace("Reading body on stream {0}", streamid);
+        BodySubscriber<T> bodySubscriber = handler.apply(responseCode, responseHeaders);
+        CompletableFuture<T> cf = receiveData(bodySubscriber);
+
+        PushGroup<?> pg = exchange.getPushGroup();
+        if (pg != null) {
+            // if an error occurs make sure it is recorded in the PushGroup
+            cf = cf.whenComplete((t,e) -> pg.pushError(e));
+        }
+        return cf;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("streamid: ")
+                .append(streamid);
+        return sb.toString();
+    }
+
+    private void receiveDataFrame(DataFrame df) {
+        inputQ.add(df);
+        sched.runOrSchedule();
+    }
+
+    /** Handles a RESET frame. RESET is always handled inline in the queue. */
+    private void receiveResetFrame(ResetFrame frame) {
+        inputQ.add(frame);
+        sched.runOrSchedule();
+    }
+
+    // pushes entire response body into response subscriber
+    // blocking when required by local or remote flow control
+    CompletableFuture<T> receiveData(BodySubscriber<T> bodySubscriber) {
+        responseBodyCF = MinimalFuture.of(bodySubscriber.getBody());
+
+        if (isCanceled()) {
+            Throwable t = getCancelCause();
+            responseBodyCF.completeExceptionally(t);
+        } else {
+            bodySubscriber.onSubscribe(userSubscription);
+        }
+        // Set the responseSubscriber field now that onSubscribe has been called.
+        // This effectively allows the scheduler to start invoking the callbacks.
+        responseSubscriber = bodySubscriber;
+        sched.runOrSchedule(); // in case data waiting already to be processed
+        return responseBodyCF;
+    }
+
+    @Override
+    CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
+        return sendBodyImpl().thenApply( v -> this);
+    }
+
+    @SuppressWarnings("unchecked")
+    Stream(Http2Connection connection,
+           Exchange<T> e,
+           WindowController windowController)
+    {
+        super(e);
+        this.connection = connection;
+        this.windowController = windowController;
+        this.request = e.request();
+        this.requestPublisher = request.requestPublisher;  // may be null
+        responseHeaders = new HttpHeadersImpl();
+        rspHeadersConsumer = (name, value) -> {
+            responseHeaders.addHeader(name.toString(), value.toString());
+            if (Log.headers() && Log.trace()) {
+                Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}",
+                             streamid, name, value);
+            }
+        };
+        this.requestPseudoHeaders = new HttpHeadersImpl();
+        // NEW
+        this.windowUpdater = new StreamWindowUpdateSender(connection);
+    }
+
+    /**
+     * Entry point from Http2Connection reader thread.
+     *
+     * Data frames will be removed by response body thread.
+     */
+    void incoming(Http2Frame frame) throws IOException {
+        debug.log(Level.DEBUG, "incoming: %s", frame);
+        if ((frame instanceof HeaderFrame)) {
+            HeaderFrame hframe = (HeaderFrame)frame;
+            if (hframe.endHeaders()) {
+                Log.logTrace("handling response (streamid={0})", streamid);
+                handleResponse();
+                if (hframe.getFlag(HeaderFrame.END_STREAM)) {
+                    receiveDataFrame(new DataFrame(streamid, DataFrame.END_STREAM, List.of()));
+                }
+            }
+        } else if (frame instanceof DataFrame) {
+            receiveDataFrame((DataFrame)frame);
+        } else {
+            otherFrame(frame);
+        }
+    }
+
+    void otherFrame(Http2Frame frame) throws IOException {
+        switch (frame.type()) {
+            case WindowUpdateFrame.TYPE:
+                incoming_windowUpdate((WindowUpdateFrame) frame);
+                break;
+            case ResetFrame.TYPE:
+                incoming_reset((ResetFrame) frame);
+                break;
+            case PriorityFrame.TYPE:
+                incoming_priority((PriorityFrame) frame);
+                break;
+            default:
+                String msg = "Unexpected frame: " + frame.toString();
+                throw new IOException(msg);
+        }
+    }
+
+    // The Hpack decoder decodes into one of these consumers of name,value pairs
+
+    DecodingCallback rspHeadersConsumer() {
+        return rspHeadersConsumer;
+    }
+
+    protected void handleResponse() throws IOException {
+        responseCode = (int)responseHeaders
+                .firstValueAsLong(":status")
+                .orElseThrow(() -> new IOException("no statuscode in response"));
+
+        response = new Response(
+                request, exchange, responseHeaders,
+                responseCode, HttpClient.Version.HTTP_2);
+
+        /* TODO: review if needs to be removed
+           the value is not used, but in case `content-length` doesn't parse as
+           long, there will be NumberFormatException. If left as is, make sure
+           code up the stack handles NFE correctly. */
+        responseHeaders.firstValueAsLong("content-length");
+
+        if (Log.headers()) {
+            StringBuilder sb = new StringBuilder("RESPONSE HEADERS:\n");
+            Log.dumpHeaders(sb, "    ", responseHeaders);
+            Log.logHeaders(sb.toString());
+        }
+
+        completeResponse(response);
+    }
+
+    void incoming_reset(ResetFrame frame) {
+        Log.logTrace("Received RST_STREAM on stream {0}", streamid);
+        if (endStreamReceived()) {
+            Log.logTrace("Ignoring RST_STREAM frame received on remotely closed stream {0}", streamid);
+        } else if (closed) {
+            Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
+        } else {
+            // put it in the input queue in order to read all
+            // pending data frames first. Indeed, a server may send
+            // RST_STREAM after sending END_STREAM, in which case we should
+            // ignore it. However, we won't know if we have received END_STREAM
+            // or not until all pending data frames are read.
+            receiveResetFrame(frame);
+            // RST_STREAM was pushed to the queue. It will be handled by
+            // asyncReceive after all pending data frames have been
+            // processed.
+            Log.logTrace("RST_STREAM pushed in queue for stream {0}", streamid);
+        }
+    }
+
+    void handleReset(ResetFrame frame) {
+        Log.logTrace("Handling RST_STREAM on stream {0}", streamid);
+        if (!closed) {
+            close();
+            int error = frame.getErrorCode();
+            completeResponseExceptionally(new IOException(ErrorFrame.stringForCode(error)));
+        } else {
+            Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
+        }
+    }
+
+    void incoming_priority(PriorityFrame frame) {
+        // TODO: implement priority
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    private void incoming_windowUpdate(WindowUpdateFrame frame)
+        throws IOException
+    {
+        int amount = frame.getUpdate();
+        if (amount <= 0) {
+            Log.logTrace("Resetting stream: {0} %d, Window Update amount: %d\n",
+                         streamid, streamid, amount);
+            connection.resetStream(streamid, ResetFrame.FLOW_CONTROL_ERROR);
+        } else {
+            assert streamid != 0;
+            boolean success = windowController.increaseStreamWindow(amount, streamid);
+            if (!success) {  // overflow
+                connection.resetStream(streamid, ResetFrame.FLOW_CONTROL_ERROR);
+            }
+        }
+    }
+
+    void incoming_pushPromise(HttpRequestImpl pushRequest,
+                              PushedStream<T> pushStream)
+        throws IOException
+    {
+        if (Log.requests()) {
+            Log.logRequest("PUSH_PROMISE: " + pushRequest.toString());
+        }
+        PushGroup<T> pushGroup = exchange.getPushGroup();
+        if (pushGroup == null) {
+            Log.logTrace("Rejecting push promise stream " + streamid);
+            connection.resetStream(pushStream.streamid, ResetFrame.REFUSED_STREAM);
+            pushStream.close();
+            return;
+        }
+
+        PushGroup.Acceptor<T> acceptor = pushGroup.acceptPushRequest(pushRequest);
+
+        if (!acceptor.accepted()) {
+            // cancel / reject
+            IOException ex = new IOException("Stream " + streamid + " cancelled by users handler");
+            if (Log.trace()) {
+                Log.logTrace("No body subscriber for {0}: {1}", pushRequest,
+                        ex.getMessage());
+            }
+            pushStream.cancelImpl(ex);
+            return;
+        }
+
+        CompletableFuture<HttpResponse<T>> pushResponseCF = acceptor.cf();
+        HttpResponse.BodyHandler<T> pushHandler = acceptor.bodyHandler();
+        assert pushHandler != null;
+
+        pushStream.requestSent();
+        pushStream.setPushHandler(pushHandler);  // TODO: could wrap the handler to throw on acceptPushPromise ?
+        // setup housekeeping for when the push is received
+        // TODO: deal with ignoring of CF anti-pattern
+        CompletableFuture<HttpResponse<T>> cf = pushStream.responseCF();
+        cf.whenComplete((HttpResponse<T> resp, Throwable t) -> {
+            t = Utils.getCompletionCause(t);
+            if (Log.trace()) {
+                Log.logTrace("Push completed on stream {0} for {1}{2}",
+                             pushStream.streamid, resp,
+                             ((t==null) ? "": " with exception " + t));
+            }
+            if (t != null) {
+                pushGroup.pushError(t);
+                pushResponseCF.completeExceptionally(t);
+            } else {
+                pushResponseCF.complete(resp);
+            }
+            pushGroup.pushCompleted();
+        });
+
+    }
+
+    private OutgoingHeaders<Stream<T>> headerFrame(long contentLength) {
+        HttpHeadersImpl h = request.getSystemHeaders();
+        if (contentLength > 0) {
+            h.setHeader("content-length", Long.toString(contentLength));
+        }
+        setPseudoHeaderFields();
+        HttpHeaders sysh = filter(h);
+        HttpHeaders userh = filter(request.getUserHeaders());
+        OutgoingHeaders<Stream<T>> f = new OutgoingHeaders<>(sysh, userh, this);
+        if (contentLength == 0) {
+            f.setFlag(HeadersFrame.END_STREAM);
+            endStreamSent = true;
+        }
+        return f;
+    }
+
+    private boolean hasProxyAuthorization(HttpHeaders headers) {
+        return headers.firstValue("proxy-authorization")
+                      .isPresent();
+    }
+
+    // Determines whether we need to build a new HttpHeader object.
+    //
+    // Ideally we should pass the filter to OutgoingHeaders refactor the
+    // code that creates the HeaderFrame to honor the filter.
+    // We're not there yet - so depending on the filter we need to
+    // apply and the content of the header we will try to determine
+    //  whether anything might need to be filtered.
+    // If nothing needs filtering then we can just use the
+    // original headers.
+    private boolean needsFiltering(HttpHeaders headers,
+                                   BiPredicate<String, List<String>> filter) {
+        if (filter == Utils.PROXY_TUNNEL_FILTER || filter == Utils.PROXY_FILTER) {
+            // we're either connecting or proxying
+            // slight optimization: we only need to filter out
+            // disabled schemes, so if there are none just
+            // pass through.
+            return Utils.proxyHasDisabledSchemes(filter == Utils.PROXY_TUNNEL_FILTER)
+                    && hasProxyAuthorization(headers);
+        } else {
+            // we're talking to a server, either directly or through
+            // a tunnel.
+            // Slight optimization: we only need to filter out
+            // proxy authorization headers, so if there are none just
+            // pass through.
+            return hasProxyAuthorization(headers);
+        }
+    }
+
+    private HttpHeaders filter(HttpHeaders headers) {
+        HttpConnection conn = connection();
+        BiPredicate<String, List<String>> filter =
+                conn.headerFilter(request);
+        if (needsFiltering(headers, filter)) {
+            return ImmutableHeaders.of(headers.map(), filter);
+        }
+        return headers;
+    }
+
+    private void setPseudoHeaderFields() {
+        HttpHeadersImpl hdrs = requestPseudoHeaders;
+        String method = request.method();
+        hdrs.setHeader(":method", method);
+        URI uri = request.uri();
+        hdrs.setHeader(":scheme", uri.getScheme());
+        // TODO: userinfo deprecated. Needs to be removed
+        hdrs.setHeader(":authority", uri.getAuthority());
+        // TODO: ensure header names beginning with : not in user headers
+        String query = uri.getQuery();
+        String path = uri.getPath();
+        if (path == null || path.isEmpty()) {
+            if (method.equalsIgnoreCase("OPTIONS")) {
+                path = "*";
+            } else {
+                path = "/";
+            }
+        }
+        if (query != null) {
+            path += "?" + query;
+        }
+        hdrs.setHeader(":path", path);
+    }
+
+    HttpHeadersImpl getRequestPseudoHeaders() {
+        return requestPseudoHeaders;
+    }
+
+    /** Sets endStreamReceived. Should be called only once. */
+    void setEndStreamReceived() {
+        assert remotelyClosed == false: "Unexpected endStream already set";
+        remotelyClosed = true;
+        responseReceived();
+    }
+
+    /** Tells whether, or not, the END_STREAM Flag has been seen in any frame
+     *  received on this stream. */
+    private boolean endStreamReceived() {
+        return remotelyClosed;
+    }
+
+    @Override
+    CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
+        debug.log(Level.DEBUG, "sendHeadersOnly()");
+        if (Log.requests() && request != null) {
+            Log.logRequest(request.toString());
+        }
+        if (requestPublisher != null) {
+            requestContentLen = requestPublisher.contentLength();
+        } else {
+            requestContentLen = 0;
+        }
+        OutgoingHeaders<Stream<T>> f = headerFrame(requestContentLen);
+        connection.sendFrame(f);
+        CompletableFuture<ExchangeImpl<T>> cf = new MinimalFuture<>();
+        cf.complete(this);  // #### good enough for now
+        return cf;
+    }
+
+    @Override
+    void released() {
+        if (streamid > 0) {
+            debug.log(Level.DEBUG, "Released stream %d", streamid);
+            // remove this stream from the Http2Connection map.
+            connection.closeStream(streamid);
+        } else {
+            debug.log(Level.DEBUG, "Can't release stream %d", streamid);
+        }
+    }
+
+    @Override
+    void completed() {
+        // There should be nothing to do here: the stream should have
+        // been already closed (or will be closed shortly after).
+    }
+
+    void registerStream(int id) {
+        this.streamid = id;
+        connection.putStream(this, streamid);
+        debug.log(Level.DEBUG, "Registered stream %d", id);
+    }
+
+    void signalWindowUpdate() {
+        RequestSubscriber subscriber = requestSubscriber;
+        assert subscriber != null;
+        debug.log(Level.DEBUG, "Signalling window update");
+        subscriber.sendScheduler.runOrSchedule();
+    }
+
+    static final ByteBuffer COMPLETED = ByteBuffer.allocate(0);
+    class RequestSubscriber implements Flow.Subscriber<ByteBuffer> {
+        // can be < 0 if the actual length is not known.
+        private final long contentLength;
+        private volatile long remainingContentLength;
+        private volatile Subscription subscription;
+
+        // Holds the outgoing data. There will be at most 2 outgoing ByteBuffers.
+        //  1) The data that was published by the request body Publisher, and
+        //  2) the COMPLETED sentinel, since onComplete can be invoked without demand.
+        final ConcurrentLinkedDeque<ByteBuffer> outgoing = new ConcurrentLinkedDeque<>();
+
+        private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        // A scheduler used to honor window updates. Writing must be paused
+        // when the window is exhausted, and resumed when the window acquires
+        // some space. The sendScheduler makes it possible to implement this
+        // behaviour in an asynchronous non-blocking way.
+        // See RequestSubscriber::trySend below.
+        final SequentialScheduler sendScheduler;
+
+        RequestSubscriber(long contentLen) {
+            this.contentLength = contentLen;
+            this.remainingContentLength = contentLen;
+            this.sendScheduler =
+                    SequentialScheduler.synchronizedScheduler(this::trySend);
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (this.subscription != null) {
+                throw new IllegalStateException("already subscribed");
+            }
+            this.subscription = subscription;
+            debug.log(Level.DEBUG, "RequestSubscriber: onSubscribe, request 1");
+            subscription.request(1);
+        }
+
+        @Override
+        public void onNext(ByteBuffer item) {
+            debug.log(Level.DEBUG, "RequestSubscriber: onNext(%d)", item.remaining());
+            int size = outgoing.size();
+            assert size == 0 : "non-zero size: " + size;
+            onNextImpl(item);
+        }
+
+        private void onNextImpl(ByteBuffer item) {
+            // Got some more request body bytes to send.
+            if (requestBodyCF.isDone()) {
+                // stream already cancelled, probably in timeout
+                sendScheduler.stop();
+                subscription.cancel();
+                return;
+            }
+            outgoing.add(item);
+            sendScheduler.runOrSchedule();
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            debug.log(Level.DEBUG, () -> "RequestSubscriber: onError: " + throwable);
+            // ensure that errors are handled within the flow.
+            if (errorRef.compareAndSet(null, throwable)) {
+                sendScheduler.runOrSchedule();
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            debug.log(Level.DEBUG, "RequestSubscriber: onComplete");
+            int size = outgoing.size();
+            assert size == 0 || size == 1 : "non-zero or one size: " + size;
+            // last byte of request body has been obtained.
+            // ensure that everything is completed within the flow.
+            onNextImpl(COMPLETED);
+        }
+
+        // Attempts to send the data, if any.
+        // Handles errors and completion state.
+        // Pause writing if the send window is exhausted, resume it if the
+        // send window has some bytes that can be acquired.
+        void trySend() {
+            try {
+                // handle errors raised by onError;
+                Throwable t = errorRef.get();
+                if (t != null) {
+                    sendScheduler.stop();
+                    if (requestBodyCF.isDone()) return;
+                    subscription.cancel();
+                    requestBodyCF.completeExceptionally(t);
+                    return;
+                }
+
+                do {
+                    // handle COMPLETED;
+                    ByteBuffer item = outgoing.peekFirst();
+                    if (item == null) return;
+                    else if (item == COMPLETED) {
+                        sendScheduler.stop();
+                        complete();
+                        return;
+                    }
+
+                    // handle bytes to send downstream
+                    while (item.hasRemaining()) {
+                        debug.log(Level.DEBUG, "trySend: %d", item.remaining());
+                        assert !endStreamSent : "internal error, send data after END_STREAM flag";
+                        DataFrame df = getDataFrame(item);
+                        if (df == null) {
+                            debug.log(Level.DEBUG, "trySend: can't send yet: %d",
+                                    item.remaining());
+                            return; // the send window is exhausted: come back later
+                        }
+
+                        if (contentLength > 0) {
+                            remainingContentLength -= df.getDataLength();
+                            if (remainingContentLength < 0) {
+                                String msg = connection().getConnectionFlow()
+                                        + " stream=" + streamid + " "
+                                        + "[" + Thread.currentThread().getName() + "] "
+                                        + "Too many bytes in request body. Expected: "
+                                        + contentLength + ", got: "
+                                        + (contentLength - remainingContentLength);
+                                connection.resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
+                                throw new IOException(msg);
+                            } else if (remainingContentLength == 0) {
+                                df.setFlag(DataFrame.END_STREAM);
+                                endStreamSent = true;
+                            }
+                        }
+                        debug.log(Level.DEBUG, "trySend: sending: %d", df.getDataLength());
+                        connection.sendDataFrame(df);
+                    }
+                    assert !item.hasRemaining();
+                    ByteBuffer b = outgoing.removeFirst();
+                    assert b == item;
+                } while (outgoing.peekFirst() != null);
+
+                debug.log(Level.DEBUG, "trySend: request 1");
+                subscription.request(1);
+            } catch (Throwable ex) {
+                debug.log(Level.DEBUG, "trySend: ", ex);
+                sendScheduler.stop();
+                subscription.cancel();
+                requestBodyCF.completeExceptionally(ex);
+            }
+        }
+
+        private void complete() throws IOException {
+            long remaining = remainingContentLength;
+            long written = contentLength - remaining;
+            if (remaining > 0) {
+                connection.resetStream(streamid, ResetFrame.PROTOCOL_ERROR);
+                // let trySend() handle the exception
+                throw new IOException(connection().getConnectionFlow()
+                                     + " stream=" + streamid + " "
+                                     + "[" + Thread.currentThread().getName() +"] "
+                                     + "Too few bytes returned by the publisher ("
+                                              + written + "/"
+                                              + contentLength + ")");
+            }
+            if (!endStreamSent) {
+                endStreamSent = true;
+                connection.sendDataFrame(getEmptyEndStreamDataFrame());
+            }
+            requestBodyCF.complete(null);
+        }
+    }
+
+    /**
+     * Send a RESET frame to tell server to stop sending data on this stream
+     */
+    @Override
+    public CompletableFuture<Void> ignoreBody() {
+        try {
+            connection.resetStream(streamid, ResetFrame.STREAM_CLOSED);
+            return MinimalFuture.completedFuture(null);
+        } catch (Throwable e) {
+            Log.logTrace("Error resetting stream {0}", e.toString());
+            return MinimalFuture.failedFuture(e);
+        }
+    }
+
+    DataFrame getDataFrame(ByteBuffer buffer) {
+        int requestAmount = Math.min(connection.getMaxSendFrameSize(), buffer.remaining());
+        // blocks waiting for stream send window, if exhausted
+        int actualAmount = windowController.tryAcquire(requestAmount, streamid, this);
+        if (actualAmount <= 0) return null;
+        ByteBuffer outBuf = Utils.sliceWithLimitedCapacity(buffer,  actualAmount);
+        DataFrame df = new DataFrame(streamid, 0 , outBuf);
+        return df;
+    }
+
+    private DataFrame getEmptyEndStreamDataFrame()  {
+        return new DataFrame(streamid, DataFrame.END_STREAM, List.of());
+    }
+
+    /**
+     * A List of responses relating to this stream. Normally there is only
+     * one response, but intermediate responses like 100 are allowed
+     * and must be passed up to higher level before continuing. Deals with races
+     * such as if responses are returned before the CFs get created by
+     * getResponseAsync()
+     */
+
+    final List<CompletableFuture<Response>> response_cfs = new ArrayList<>(5);
+
+    @Override
+    CompletableFuture<Response> getResponseAsync(Executor executor) {
+        CompletableFuture<Response> cf;
+        // The code below deals with race condition that can be caused when
+        // completeResponse() is being called before getResponseAsync()
+        synchronized (response_cfs) {
+            if (!response_cfs.isEmpty()) {
+                // This CompletableFuture was created by completeResponse().
+                // it will be already completed.
+                cf = response_cfs.remove(0);
+                // if we find a cf here it should be already completed.
+                // finding a non completed cf should not happen. just assert it.
+                assert cf.isDone() : "Removing uncompleted response: could cause code to hang!";
+            } else {
+                // getResponseAsync() is called first. Create a CompletableFuture
+                // that will be completed by completeResponse() when
+                // completeResponse() is called.
+                cf = new MinimalFuture<>();
+                response_cfs.add(cf);
+            }
+        }
+        if (executor != null && !cf.isDone()) {
+            // protect from executing later chain of CompletableFuture operations from SelectorManager thread
+            cf = cf.thenApplyAsync(r -> r, executor);
+        }
+        Log.logTrace("Response future (stream={0}) is: {1}", streamid, cf);
+        PushGroup<?> pg = exchange.getPushGroup();
+        if (pg != null) {
+            // if an error occurs make sure it is recorded in the PushGroup
+            cf = cf.whenComplete((t,e) -> pg.pushError(Utils.getCompletionCause(e)));
+        }
+        return cf;
+    }
+
+    /**
+     * Completes the first uncompleted CF on list, and removes it. If there is no
+     * uncompleted CF then creates one (completes it) and adds to list
+     */
+    void completeResponse(Response resp) {
+        synchronized (response_cfs) {
+            CompletableFuture<Response> cf;
+            int cfs_len = response_cfs.size();
+            for (int i=0; i<cfs_len; i++) {
+                cf = response_cfs.get(i);
+                if (!cf.isDone()) {
+                    Log.logTrace("Completing response (streamid={0}): {1}",
+                                 streamid, cf);
+                    cf.complete(resp);
+                    response_cfs.remove(cf);
+                    return;
+                } // else we found the previous response: just leave it alone.
+            }
+            cf = MinimalFuture.completedFuture(resp);
+            Log.logTrace("Created completed future (streamid={0}): {1}",
+                         streamid, cf);
+            response_cfs.add(cf);
+        }
+    }
+
+    // methods to update state and remove stream when finished
+
+    synchronized void requestSent() {
+        requestSent = true;
+        if (responseReceived) {
+            close();
+        }
+    }
+
+    synchronized void responseReceived() {
+        responseReceived = true;
+        if (requestSent) {
+            close();
+        }
+    }
+
+    /**
+     * same as above but for errors
+     */
+    void completeResponseExceptionally(Throwable t) {
+        synchronized (response_cfs) {
+            // use index to avoid ConcurrentModificationException
+            // caused by removing the CF from within the loop.
+            for (int i = 0; i < response_cfs.size(); i++) {
+                CompletableFuture<Response> cf = response_cfs.get(i);
+                if (!cf.isDone()) {
+                    cf.completeExceptionally(t);
+                    response_cfs.remove(i);
+                    return;
+                }
+            }
+            response_cfs.add(MinimalFuture.failedFuture(t));
+        }
+    }
+
+    CompletableFuture<Void> sendBodyImpl() {
+        requestBodyCF.whenComplete((v, t) -> requestSent());
+        if (requestPublisher != null) {
+            final RequestSubscriber subscriber = new RequestSubscriber(requestContentLen);
+            requestPublisher.subscribe(requestSubscriber = subscriber);
+        } else {
+            // there is no request body, therefore the request is complete,
+            // END_STREAM has already sent with outgoing headers
+            requestBodyCF.complete(null);
+        }
+        return requestBodyCF;
+    }
+
+    @Override
+    void cancel() {
+        cancel(new IOException("Stream " + streamid + " cancelled"));
+    }
+
+    @Override
+    void cancel(IOException cause) {
+        cancelImpl(cause);
+    }
+
+    // This method sends a RST_STREAM frame
+    void cancelImpl(Throwable e) {
+        debug.log(Level.DEBUG, "cancelling stream {0}: {1}", streamid, e);
+        if (Log.trace()) {
+            Log.logTrace("cancelling stream {0}: {1}\n", streamid, e);
+        }
+        boolean closing;
+        if (closing = !closed) { // assigning closing to !closed
+            synchronized (this) {
+                failed = e;
+                if (closing = !closed) { // assigning closing to !closed
+                    closed=true;
+                }
+            }
+        }
+        if (closing) { // true if the stream has not been closed yet
+            if (responseSubscriber != null)
+                sched.runOrSchedule();
+        }
+        completeResponseExceptionally(e);
+        if (!requestBodyCF.isDone()) {
+            requestBodyCF.completeExceptionally(e); // we may be sending the body..
+        }
+        if (responseBodyCF != null) {
+            responseBodyCF.completeExceptionally(e);
+        }
+        try {
+            // will send a RST_STREAM frame
+            if (streamid != 0) {
+                connection.resetStream(streamid, ResetFrame.CANCEL);
+            }
+        } catch (IOException ex) {
+            Log.logError(ex);
+        }
+    }
+
+    // This method doesn't send any frame
+    void close() {
+        if (closed) return;
+        synchronized(this) {
+            if (closed) return;
+            closed = true;
+        }
+        Log.logTrace("Closing stream {0}", streamid);
+        connection.closeStream(streamid);
+        Log.logTrace("Stream {0} closed", streamid);
+    }
+
+    static class PushedStream<T> extends Stream<T> {
+        final PushGroup<T> pushGroup;
+        // push streams need the response CF allocated up front as it is
+        // given directly to user via the multi handler callback function.
+        final CompletableFuture<Response> pushCF;
+        CompletableFuture<HttpResponse<T>> responseCF;
+        final HttpRequestImpl pushReq;
+        HttpResponse.BodyHandler<T> pushHandler;
+
+        PushedStream(PushGroup<T> pushGroup,
+                     Http2Connection connection,
+                     Exchange<T> pushReq) {
+            // ## no request body possible, null window controller
+            super(connection, pushReq, null);
+            this.pushGroup = pushGroup;
+            this.pushReq = pushReq.request();
+            this.pushCF = new MinimalFuture<>();
+            this.responseCF = new MinimalFuture<>();
+
+        }
+
+        CompletableFuture<HttpResponse<T>> responseCF() {
+            return responseCF;
+        }
+
+        synchronized void setPushHandler(HttpResponse.BodyHandler<T> pushHandler) {
+            this.pushHandler = pushHandler;
+        }
+
+        synchronized HttpResponse.BodyHandler<T> getPushHandler() {
+            // ignored parameters to function can be used as BodyHandler
+            return this.pushHandler;
+        }
+
+        // Following methods call the super class but in case of
+        // error record it in the PushGroup. The error method is called
+        // with a null value when no error occurred (is a no-op)
+        @Override
+        CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
+            return super.sendBodyAsync()
+                        .whenComplete((ExchangeImpl<T> v, Throwable t)
+                                -> pushGroup.pushError(Utils.getCompletionCause(t)));
+        }
+
+        @Override
+        CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
+            return super.sendHeadersAsync()
+                        .whenComplete((ExchangeImpl<T> ex, Throwable t)
+                                -> pushGroup.pushError(Utils.getCompletionCause(t)));
+        }
+
+        @Override
+        CompletableFuture<Response> getResponseAsync(Executor executor) {
+            CompletableFuture<Response> cf = pushCF.whenComplete(
+                    (v, t) -> pushGroup.pushError(Utils.getCompletionCause(t)));
+            if(executor!=null && !cf.isDone()) {
+                cf  = cf.thenApplyAsync( r -> r, executor);
+            }
+            return cf;
+        }
+
+        @Override
+        CompletableFuture<T> readBodyAsync(
+                HttpResponse.BodyHandler<T> handler,
+                boolean returnConnectionToPool,
+                Executor executor)
+        {
+            return super.readBodyAsync(handler, returnConnectionToPool, executor)
+                        .whenComplete((v, t) -> pushGroup.pushError(t));
+        }
+
+        @Override
+        void completeResponse(Response r) {
+            Log.logResponse(r::toString);
+            pushCF.complete(r); // not strictly required for push API
+            // start reading the body using the obtained BodySubscriber
+            CompletableFuture<Void> start = new MinimalFuture<>();
+            start.thenCompose( v -> readBodyAsync(getPushHandler(), false, getExchange().executor()))
+                .whenComplete((T body, Throwable t) -> {
+                    if (t != null) {
+                        responseCF.completeExceptionally(t);
+                    } else {
+                        HttpResponseImpl<T> resp =
+                                new HttpResponseImpl<>(r.request, r, null, body, getExchange());
+                        responseCF.complete(resp);
+                    }
+                });
+            start.completeAsync(() -> null, getExchange().executor());
+        }
+
+        @Override
+        void completeResponseExceptionally(Throwable t) {
+            pushCF.completeExceptionally(t);
+        }
+
+//        @Override
+//        synchronized void responseReceived() {
+//            super.responseReceived();
+//        }
+
+        // create and return the PushResponseImpl
+        @Override
+        protected void handleResponse() {
+            responseCode = (int)responseHeaders
+                .firstValueAsLong(":status")
+                .orElse(-1);
+
+            if (responseCode == -1) {
+                completeResponseExceptionally(new IOException("No status code"));
+            }
+
+            this.response = new Response(
+                pushReq, exchange, responseHeaders,
+                responseCode, HttpClient.Version.HTTP_2);
+
+            /* TODO: review if needs to be removed
+               the value is not used, but in case `content-length` doesn't parse
+               as long, there will be NumberFormatException. If left as is, make
+               sure code up the stack handles NFE correctly. */
+            responseHeaders.firstValueAsLong("content-length");
+
+            if (Log.headers()) {
+                StringBuilder sb = new StringBuilder("RESPONSE HEADERS");
+                sb.append(" (streamid=").append(streamid).append("): ");
+                Log.dumpHeaders(sb, "    ", responseHeaders);
+                Log.logHeaders(sb.toString());
+            }
+
+            // different implementations for normal streams and pushed streams
+            completeResponse(response);
+        }
+    }
+
+    final class StreamWindowUpdateSender extends WindowUpdateSender {
+
+        StreamWindowUpdateSender(Http2Connection connection) {
+            super(connection);
+        }
+
+        @Override
+        int getStreamId() {
+            return streamid;
+        }
+    }
+
+    /**
+     * Returns true if this exchange was canceled.
+     * @return true if this exchange was canceled.
+     */
+    synchronized boolean isCanceled() {
+        return failed != null;
+    }
+
+    /**
+     * Returns the cause for which this exchange was canceled, if available.
+     * @return the cause for which this exchange was canceled, if available.
+     */
+    synchronized Throwable getCancelCause() {
+        return failed;
+    }
+
+    final String dbgString() {
+        return connection.dbgString() + "/Stream("+streamid+")";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/TimeoutEvent.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Timeout event notified by selector thread. Executes the given handler if
+ * the timer not canceled first.
+ *
+ * Register with {@link HttpClientImpl#registerTimer(TimeoutEvent)}.
+ *
+ * Cancel with {@link HttpClientImpl#cancelTimer(TimeoutEvent)}.
+ */
+abstract class TimeoutEvent implements Comparable<TimeoutEvent> {
+
+    private static final AtomicLong COUNTER = new AtomicLong();
+    // we use id in compareTo to make compareTo consistent with equals
+    // see TimeoutEvent::compareTo below;
+    private final long id = COUNTER.incrementAndGet();
+    private final Instant deadline;
+
+    TimeoutEvent(Duration duration) {
+        deadline = Instant.now().plus(duration);
+    }
+
+    public abstract void handle();
+
+    public Instant deadline() {
+        return deadline;
+    }
+
+    @Override
+    public int compareTo(TimeoutEvent other) {
+        if (other == this) return 0;
+        // if two events have the same deadline, but are not equals, then the
+        // smaller is the one that was created before (has the smaller id).
+        // This is arbitrary and we don't really care which is smaller or
+        // greater, but we need a total order, so two events with the
+        // same deadline cannot compare == 0 if they are not equals.
+        final int compareDeadline = this.deadline.compareTo(other.deadline);
+        if (compareDeadline == 0 && !this.equals(other)) {
+            long diff = this.id - other.id; // should take care of wrap around
+            if (diff < 0) return -1;
+            else if (diff > 0) return 1;
+            else assert false : "Different events with same id and deadline";
+        }
+        return compareDeadline;
+    }
+
+    @Override
+    public String toString() {
+        return "TimeoutEvent[id=" + id + ", deadline=" + deadline + "]";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/UntrustedBodyHandler.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.security.AccessControlContext;
+import jdk.incubator.http.HttpResponse;
+
+/** A body handler that is further restricted by a given ACC. */
+public interface UntrustedBodyHandler<T> extends HttpResponse.BodyHandler<T> {
+    void setAccessControlContext(AccessControlContext acc);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/WindowController.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.lang.System.Logger.Level;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * A Send Window Flow-Controller that is used to control outgoing Connection
+ * and Stream flows, per HTTP/2 connection.
+ *
+ * A Http2Connection has its own unique single instance of a WindowController
+ * that it shares with its Streams. Each stream must acquire the appropriate
+ * amount of Send Window from the controller before sending data.
+ *
+ * WINDOW_UPDATE frames, both connection and stream specific, must notify the
+ * controller of their increments. SETTINGS frame's INITIAL_WINDOW_SIZE must
+ * notify the controller so that it can adjust the active stream's window size.
+ */
+final class WindowController {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary developer's flag
+    static final System.Logger DEBUG_LOGGER =
+            Utils.getDebugLogger("WindowController"::toString, DEBUG);
+
+    /**
+     * Default initial connection Flow-Control Send Window size, as per HTTP/2.
+     */
+    private static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024 - 1;
+
+    /** The connection Send Window size. */
+    private int connectionWindowSize;
+    /** A Map of the active streams, where the key is the stream id, and the
+     *  value is the stream's Send Window size, which may be negative. */
+    private final Map<Integer,Integer> streams = new HashMap<>();
+    /** A Map of streams awaiting Send Window. The key is the stream id. The
+     * value is a pair of the Stream ( representing the key's stream id ) and
+     * the requested amount of send Window. */
+    private final Map<Integer, Map.Entry<Stream<?>, Integer>> pending
+            = new LinkedHashMap<>();
+
+    private final ReentrantLock controllerLock = new ReentrantLock();
+
+    /** A Controller with the default initial window size. */
+    WindowController() {
+        connectionWindowSize = DEFAULT_INITIAL_WINDOW_SIZE;
+    }
+
+//    /** A Controller with the given initial window size. */
+//    WindowController(int initialConnectionWindowSize) {
+//        connectionWindowSize = initialConnectionWindowSize;
+//    }
+
+    /** Registers the given stream with this controller. */
+    void registerStream(int streamid, int initialStreamWindowSize) {
+        controllerLock.lock();
+        try {
+            Integer old = streams.put(streamid, initialStreamWindowSize);
+            if (old != null)
+                throw new InternalError("Unexpected entry ["
+                        + old + "] for streamid: " + streamid);
+        } finally {
+            controllerLock.unlock();
+        }
+    }
+
+    /** Removes/De-registers the given stream with this controller. */
+    void removeStream(int streamid) {
+        controllerLock.lock();
+        try {
+            Integer old = streams.remove(streamid);
+            // Odd stream numbers (client streams) should have been registered.
+            // Even stream numbers (server streams - aka Push Streams) should
+            // not be registered
+            final boolean isClientStream = (streamid % 2) == 1;
+            if (old == null && isClientStream) {
+                throw new InternalError("Expected entry for streamid: " + streamid);
+            } else if (old != null && !isClientStream) {
+                throw new InternalError("Unexpected entry for streamid: " + streamid);
+            }
+        } finally {
+            controllerLock.unlock();
+        }
+    }
+
+    /**
+     * Attempts to acquire the requested amount of Send Window for the given
+     * stream.
+     *
+     * The actual amount of Send Window available may differ from the requested
+     * amount. The actual amount, returned by this method, is the minimum of,
+     * 1) the requested amount, 2) the stream's Send Window, and 3) the
+     * connection's Send Window.
+     *
+     * A negative or zero value is returned if there's no window available.
+     * When the result is negative or zero, this method arranges for the
+     * given stream's {@link Stream#signalWindowUpdate()} method to be invoke at
+     * a later time when the connection and/or stream window's have been
+     * increased. The {@code tryAcquire} method should then be invoked again to
+     * attempt to acquire the available window.
+     */
+    int tryAcquire(int requestAmount, int streamid, Stream<?> stream) {
+        controllerLock.lock();
+        try {
+            Integer streamSize = streams.get(streamid);
+            if (streamSize == null)
+                throw new InternalError("Expected entry for streamid: "
+                                        + streamid);
+            int x = Math.min(requestAmount,
+                             Math.min(streamSize, connectionWindowSize));
+
+            if (x <= 0)  { // stream window size may be negative
+                DEBUG_LOGGER.log(Level.DEBUG,
+                        "Stream %d requesting %d but only %d available (stream: %d, connection: %d)",
+                      streamid, requestAmount, Math.min(streamSize, connectionWindowSize),
+                      streamSize, connectionWindowSize);
+                // If there's not enough window size available, put the
+                // caller in a pending list.
+                pending.put(streamid, Map.entry(stream, requestAmount));
+                return x;
+            }
+
+            // Remove the caller from the pending list ( if was waiting ).
+            pending.remove(streamid);
+
+            // Update window sizes and return the allocated amount to the caller.
+            streamSize -= x;
+            streams.put(streamid, streamSize);
+            connectionWindowSize -= x;
+            DEBUG_LOGGER.log(Level.DEBUG,
+                  "Stream %d amount allocated %d, now %d available (stream: %d, connection: %d)",
+                  streamid, x, Math.min(streamSize, connectionWindowSize),
+                  streamSize, connectionWindowSize);
+            return x;
+        } finally {
+            controllerLock.unlock();
+        }
+    }
+
+    /**
+     * Increases the Send Window size for the connection.
+     *
+     * A number of awaiting requesters, from unfulfilled tryAcquire requests,
+     * may have their stream's {@link Stream#signalWindowUpdate()} method
+     * scheduled to run ( i.e. awake awaiters ).
+     *
+     * @return false if, and only if, the addition of the given amount would
+     *         cause the Send Window to exceed 2^31-1 (overflow), otherwise true
+     */
+    boolean increaseConnectionWindow(int amount) {
+        List<Stream<?>> candidates = null;
+        controllerLock.lock();
+        try {
+            int size = connectionWindowSize;
+            size += amount;
+            if (size < 0)
+                return false;
+            connectionWindowSize = size;
+            DEBUG_LOGGER.log(Level.DEBUG, "Connection window size is now %d", size);
+
+            // Notify waiting streams, until the new increased window size is
+            // effectively exhausted.
+            Iterator<Map.Entry<Integer,Map.Entry<Stream<?>,Integer>>> iter =
+                    pending.entrySet().iterator();
+
+            while (iter.hasNext() && size > 0) {
+                Map.Entry<Integer,Map.Entry<Stream<?>,Integer>> item = iter.next();
+                Integer streamSize = streams.get(item.getKey());
+                if (streamSize == null) {
+                    iter.remove();
+                } else {
+                    Map.Entry<Stream<?>,Integer> e = item.getValue();
+                    int requestedAmount = e.getValue();
+                    // only wakes up the pending streams for which there is
+                    // at least 1 byte of space in both windows
+                    int minAmount = 1;
+                    if (size >= minAmount && streamSize >= minAmount) {
+                        size -= Math.min(streamSize, requestedAmount);
+                        iter.remove();
+                        if (candidates == null)
+                            candidates = new ArrayList<>();
+                        candidates.add(e.getKey());
+                    }
+                }
+            }
+        } finally {
+            controllerLock.unlock();
+        }
+        if (candidates != null) {
+            candidates.forEach(Stream::signalWindowUpdate);
+        }
+        return true;
+    }
+
+    /**
+     * Increases the Send Window size for the given stream.
+     *
+     * If the given stream is awaiting window size, from an unfulfilled
+     * tryAcquire request, it will have its stream's {@link
+     * Stream#signalWindowUpdate()} method scheduled to run ( i.e. awoken ).
+     *
+     * @return false if, and only if, the addition of the given amount would
+     *         cause the Send Window to exceed 2^31-1 (overflow), otherwise true
+     */
+    boolean increaseStreamWindow(int amount, int streamid) {
+        Stream<?> s = null;
+        controllerLock.lock();
+        try {
+            Integer size = streams.get(streamid);
+            if (size == null)
+                throw new InternalError("Expected entry for streamid: " + streamid);
+            size += amount;
+            if (size < 0)
+                return false;
+            streams.put(streamid, size);
+            DEBUG_LOGGER.log(Level.DEBUG,
+                             "Stream %s window size is now %s", streamid, size);
+
+            Map.Entry<Stream<?>,Integer> p = pending.get(streamid);
+            if (p != null) {
+                int minAmount = 1;
+                // only wakes up the pending stream if there is at least
+                // 1 byte of space in both windows
+                if (size >= minAmount
+                        && connectionWindowSize >= minAmount) {
+                     pending.remove(streamid);
+                     s = p.getKey();
+                }
+            }
+        } finally {
+            controllerLock.unlock();
+        }
+
+        if (s != null)
+            s.signalWindowUpdate();
+
+        return true;
+    }
+
+    /**
+     * Adjusts, either increases or decreases, the active streams registered
+     * with this controller.  May result in a stream's Send Window size becoming
+     * negative.
+     */
+    void adjustActiveStreams(int adjustAmount) {
+        assert adjustAmount != 0;
+
+        controllerLock.lock();
+        try {
+            for (Map.Entry<Integer,Integer> entry : streams.entrySet()) {
+                int streamid = entry.getKey();
+                // the API only supports sending on Streams initialed by
+                // the client, i.e. odd stream numbers
+                if (streamid != 0 && (streamid % 2) != 0) {
+                    Integer size = entry.getValue();
+                    size += adjustAmount;
+                    streams.put(streamid, size);
+                    DEBUG_LOGGER.log(Level.DEBUG,
+                        "Stream %s window size is now %s", streamid, size);
+                }
+            }
+        } finally {
+            controllerLock.unlock();
+        }
+    }
+
+    /** Returns the Send Window size for the connection. */
+    int connectionWindowSize() {
+        controllerLock.lock();
+        try {
+            return connectionWindowSize;
+        } finally {
+            controllerLock.unlock();
+        }
+    }
+
+//    /** Returns the Send Window size for the given stream. */
+//    int streamWindowSize(int streamid) {
+//        controllerLock.lock();
+//        try {
+//            Integer size = streams.get(streamid);
+//            if (size == null)
+//                throw new InternalError("Expected entry for streamid: " + streamid);
+//            return size;
+//        } finally {
+//            controllerLock.unlock();
+//        }
+//    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/WindowUpdateSender.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.lang.System.Logger.Level;
+import jdk.incubator.http.internal.frame.SettingsFrame;
+import jdk.incubator.http.internal.frame.WindowUpdateFrame;
+import jdk.incubator.http.internal.common.Utils;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+abstract class WindowUpdateSender {
+
+    final static boolean DEBUG = Utils.DEBUG;
+    final System.Logger debug =
+            Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    final int limit;
+    final Http2Connection connection;
+    final AtomicInteger received = new AtomicInteger(0);
+
+    WindowUpdateSender(Http2Connection connection) {
+        this(connection, connection.clientSettings.getParameter(SettingsFrame.INITIAL_WINDOW_SIZE));
+    }
+
+    WindowUpdateSender(Http2Connection connection, int initWindowSize) {
+        this(connection, connection.getMaxReceiveFrameSize(), initWindowSize);
+    }
+
+    WindowUpdateSender(Http2Connection connection, int maxFrameSize, int initWindowSize) {
+        this.connection = connection;
+        int v0 = Math.max(0, initWindowSize - maxFrameSize);
+        int v1 = (initWindowSize + (maxFrameSize - 1)) / maxFrameSize;
+        v1 = v1 * maxFrameSize / 2;
+        // send WindowUpdate heuristic:
+        // - we got data near half of window size
+        //   or
+        // - remaining window size reached max frame size.
+        limit = Math.min(v0, v1);
+        debug.log(Level.DEBUG, "maxFrameSize=%d, initWindowSize=%d, limit=%d",
+                maxFrameSize, initWindowSize, limit);
+    }
+
+    abstract int getStreamId();
+
+    void update(int delta) {
+        debug.log(Level.DEBUG, "update: %d", delta);
+        if (received.addAndGet(delta) > limit) {
+            synchronized (this) {
+                int tosend = received.get();
+                if( tosend > limit) {
+                    received.getAndAdd(-tosend);
+                    sendWindowUpdate(tosend);
+                }
+            }
+        }
+    }
+
+    void sendWindowUpdate(int delta) {
+        debug.log(Level.DEBUG, "sending window update: %d", delta);
+        connection.sendUnorderedFrame(new WindowUpdateFrame(getStreamId(), delta));
+    }
+
+    String dbgString() {
+        return "WindowUpdateSender(stream: " + getStreamId() + ")";
+    }
+
+}
--- a/test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java	Tue Feb 06 11:39:55 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java	Tue Feb 06 14:10:28 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,9 @@
  * @summary Verifies that the ConnectionPool correctly handle
  *          connection deadlines and purges the right connections
  *          from the cache.
- * @modules jdk.incubator.httpclient java.management
- * @run main/othervm --add-reads jdk.incubator.httpclient=java.management jdk.incubator.httpclient/jdk.incubator.http.ConnectionPoolTest
+ * @modules jdk.incubator.httpclient/jdk.incubator.http.internal
+  *         java.management
+ * @run main/othervm
+ *       --add-reads jdk.incubator.httpclient=java.management
+ *       jdk.incubator.httpclient/jdk.incubator.http.internal.ConnectionPoolTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/Driver.java	Tue Feb 06 11:39:55 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/Driver.java	Tue Feb 06 14:10:28 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8151299 8164704
- * @modules jdk.incubator.httpclient
- * @run testng jdk.incubator.httpclient/jdk.incubator.http.SelectorTest
- * @run testng jdk.incubator.httpclient/jdk.incubator.http.RawChannelTest
+ * @modules jdk.incubator.httpclient/jdk.incubator.http.internal
+ * @run testng jdk.incubator.httpclient/jdk.incubator.http.internal.SelectorTest
+ * @run testng jdk.incubator.httpclient/jdk.incubator.http.internal.RawChannelTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/FramesDecoderTestDriver.java	Tue Feb 06 11:39:55 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/FramesDecoderTestDriver.java	Tue Feb 06 14:10:28 2018 +0000
@@ -25,6 +25,8 @@
  * @test
  * @bug 8195823
  * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- * @run testng/othervm -Djdk.internal.httpclient.debug=true jdk.incubator.httpclient/jdk.incubator.http.internal.frame.FramesDecoderTest
+ * @run testng/othervm
+ *       -Djdk.internal.httpclient.debug=true
+ *       jdk.incubator.httpclient/jdk.incubator.http.internal.frame.FramesDecoderTest
  */
 
--- a/test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java	Tue Feb 06 11:39:55 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java	Tue Feb 06 14:10:28 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,6 @@
 /*
  * @test
  * @bug 8195138
- * @modules jdk.incubator.httpclient
- * @run testng jdk.incubator.httpclient/jdk.incubator.http.Http1HeaderParserTest
+ * @modules jdk.incubator.httpclient/jdk.incubator.http.internal
+ * @run testng jdk.incubator.httpclient/jdk.incubator.http.internal.Http1HeaderParserTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/ConnectionPoolTest.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.InetSocketAddress;
-import java.net.ProxySelector;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-import java.util.Optional;
-import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.stream.IntStream;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.internal.common.FlowTube;
-
-/**
- * @summary Verifies that the ConnectionPool correctly handle
- *          connection deadlines and purges the right connections
- *          from the cache.
- * @bug 8187044 8187111
- * @author danielfuchs
- */
-public class ConnectionPoolTest {
-
-    static long getActiveCleaners() throws ClassNotFoundException {
-        // ConnectionPool.ACTIVE_CLEANER_COUNTER.get()
-        // ConnectionPoolTest.class.getModule().addReads(
-        //      Class.forName("java.lang.management.ManagementFactory").getModule());
-        return java.util.stream.Stream.of(ManagementFactory.getThreadMXBean()
-                .dumpAllThreads(false, false))
-              .filter(t -> t.getThreadName().startsWith("HTTP-Cache-cleaner"))
-              .count();
-    }
-
-    public static void main(String[] args) throws Exception {
-        testCacheCleaners();
-    }
-
-    public static void testCacheCleaners() throws Exception {
-        ConnectionPool pool = new ConnectionPool(666);
-        HttpClient client = new HttpClientStub(pool);
-        InetSocketAddress proxy = InetSocketAddress.createUnresolved("bar", 80);
-        System.out.println("Adding 10 connections to pool");
-        Random random = new Random();
-
-        final int count = 20;
-        Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
-        int[] keepAlives = new int[count];
-        HttpConnectionStub[] connections = new HttpConnectionStub[count];
-        long purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
-        long expected = 0;
-        if (purge != expected) {
-            throw new RuntimeException("Bad purge delay: " + purge
-                                        + ", expected " + expected);
-        }
-        expected = Long.MAX_VALUE;
-        for (int i=0; i<count; i++) {
-            InetSocketAddress addr = InetSocketAddress.createUnresolved("foo"+i, 80);
-            keepAlives[i] = random.nextInt(10) * 10  + 10;
-            connections[i] = new HttpConnectionStub(client, addr, proxy, true);
-            System.out.println("Adding connection: " + now
-                                + " keepAlive: " + keepAlives[i]
-                                + " /" + connections[i]);
-            pool.returnToPool(connections[i], now, keepAlives[i]);
-            expected = Math.min(expected, keepAlives[i] * 1000);
-            purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
-            if (purge != expected) {
-                throw new RuntimeException("Bad purge delay: " + purge
-                                        + ", expected " + expected);
-            }
-        }
-        int min = IntStream.of(keepAlives).min().getAsInt();
-        int max = IntStream.of(keepAlives).max().getAsInt();
-        int mean = (min + max)/2;
-        System.out.println("min=" + min + ", max=" + max + ", mean=" + mean);
-        purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
-        System.out.println("first purge would be in " + purge + " ms");
-        if (Math.abs(purge/1000 - min) > 0) {
-            throw new RuntimeException("expected " + min + " got " + purge/1000);
-        }
-        long opened = java.util.stream.Stream.of(connections)
-                     .filter(HttpConnectionStub::connected).count();
-        if (opened != count) {
-            throw new RuntimeException("Opened: expected "
-                                       + count + " got " + opened);
-        }
-        purge = mean * 1000;
-        System.out.println("start purging at " + purge + " ms");
-        Instant next = now;
-        do {
-           System.out.println("next purge is in " + purge + " ms");
-           next = next.plus(purge, ChronoUnit.MILLIS);
-           purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(next);
-           long k = now.until(next, ChronoUnit.SECONDS);
-           System.out.println("now is " + k + "s from start");
-           for (int i=0; i<count; i++) {
-               if (connections[i].connected() != (k < keepAlives[i])) {
-                   throw new RuntimeException("Bad connection state for "
-                             + i
-                             + "\n\t connected=" + connections[i].connected()
-                             + "\n\t keepAlive=" + keepAlives[i]
-                             + "\n\t elapsed=" + k);
-               }
-           }
-        } while (purge > 0);
-        opened = java.util.stream.Stream.of(connections)
-                     .filter(HttpConnectionStub::connected).count();
-        if (opened != 0) {
-           throw new RuntimeException("Closed: expected "
-                                       + count + " got "
-                                       + (count-opened));
-        }
-    }
-
-    static <T> T error() {
-        throw new InternalError("Should not reach here: wrong test assumptions!");
-    }
-
-    static class FlowTubeStub implements FlowTube {
-        final HttpConnectionStub conn;
-        FlowTubeStub(HttpConnectionStub conn) { this.conn = conn; }
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) { }
-        @Override public void onError(Throwable error) { error(); }
-        @Override public void onComplete() { error(); }
-        @Override public void onNext(List<ByteBuffer> item) { error();}
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-        }
-        @Override public boolean isFinished() { return conn.closed; }
-    }
-
-    // Emulates an HttpConnection that has a strong reference to its HttpClient.
-    static class HttpConnectionStub extends HttpConnection {
-
-        public HttpConnectionStub(HttpClient client,
-                InetSocketAddress address,
-                InetSocketAddress proxy,
-                boolean secured) {
-            super(address, null);
-            this.key = ConnectionPool.cacheKey(address, proxy);
-            this.address = address;
-            this.proxy = proxy;
-            this.secured = secured;
-            this.client = client;
-            this.flow = new FlowTubeStub(this);
-        }
-
-        final InetSocketAddress proxy;
-        final InetSocketAddress address;
-        final boolean secured;
-        final ConnectionPool.CacheKey key;
-        final HttpClient client;
-        final FlowTubeStub flow;
-        volatile boolean closed;
-
-        // All these return something
-        @Override boolean connected() {return !closed;}
-        @Override boolean isSecure() {return secured;}
-        @Override boolean isProxied() {return proxy!=null;}
-        @Override ConnectionPool.CacheKey cacheKey() {return key;}
-        @Override void shutdownInput() throws IOException {}
-        @Override void shutdownOutput() throws IOException {}
-        @Override
-        public void close() {
-            closed=true;
-            System.out.println("closed: " + this);
-        }
-        @Override
-        public String toString() {
-            return "HttpConnectionStub: " + address + " proxy: " + proxy;
-        }
-
-        // All these throw errors
-        @Override public HttpPublisher publisher() {return error();}
-        @Override public CompletableFuture<Void> connectAsync() {return error();}
-        @Override SocketChannel channel() {return error();}
-        @Override
-        HttpConnection.DetachedConnectionChannel detachChannel() {
-            return error();
-        }
-        @Override
-        FlowTube getConnectionFlow() {return flow;}
-    }
-    // Emulates an HttpClient that has a strong reference to its connection pool.
-    static class HttpClientStub extends HttpClient {
-        public HttpClientStub(ConnectionPool pool) {
-            this.pool = pool;
-        }
-        final ConnectionPool pool;
-        @Override public Optional<CookieHandler> cookieHandler() {return error();}
-        @Override public HttpClient.Redirect followRedirects() {return error();}
-        @Override public Optional<ProxySelector> proxy() {return error();}
-        @Override public SSLContext sslContext() {return error();}
-        @Override public SSLParameters sslParameters() {return error();}
-        @Override public Optional<Authenticator> authenticator() {return error();}
-        @Override public HttpClient.Version version() {return HttpClient.Version.HTTP_1_1;}
-        @Override public Optional<Executor> executor() {return error();}
-        @Override
-        public <T> HttpResponse<T> send(HttpRequest req,
-                HttpResponse.BodyHandler<T> responseBodyHandler)
-                throws IOException, InterruptedException {
-            return error();
-        }
-        @Override
-        public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req,
-                HttpResponse.BodyHandler<T> responseBodyHandler) {
-            return error();
-        }
-        @Override
-        public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req,
-                HttpResponse.BodyHandler<T> bodyHandler,
-                HttpResponse.PushPromiseHandler<T> multiHandler) {
-            return error();
-        }
-    }
-
-}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/Http1HeaderParserTest.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,379 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.ByteArrayInputStream;
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.IntStream;
-import sun.net.www.MessageHeader;
-import org.testng.annotations.Test;
-import org.testng.annotations.DataProvider;
-import static java.lang.System.out;
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.util.stream.Collectors.toList;
-import static org.testng.Assert.*;
-
-// Mostly verifies the "new" Http1HeaderParser returns the same results as the
-// tried and tested sun.net.www.MessageHeader.
-
-public class Http1HeaderParserTest {
-
-    @DataProvider(name = "responses")
-    public Object[][] responses() {
-        List<String> responses = new ArrayList<>();
-
-        String[] basic =
-            { "HTTP/1.1 200 OK\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
-              "Server: Apache/1.3.14 (Unix)\r\n" +
-              "Connection: close\r\n" +
-              "Content-Type: text/html; charset=iso-8859-1\r\n" +
-              "Content-Length: 10\r\n\r\n" +
-              "123456789",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 9\r\n" +
-              "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
-              "XXXXX",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length:   9\r\n" +
-              "Content-Type:   text/html; charset=UTF-8\r\n\r\n" +   // more than one SP after ':'
-              "XXXXX",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length:\t10\r\n" +
-              "Content-Type:\ttext/html; charset=UTF-8\r\n\r\n" +   // HT separator
-              "XXXXX",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length:\t\t10\r\n" +
-              "Content-Type:\t\ttext/html; charset=UTF-8\r\n\r\n" +   // more than one HT after ':'
-              "XXXXX",
-
-              "HTTP/1.1 407 Proxy Authorization Required\r\n" +
-              "Proxy-Authenticate: Basic realm=\"a fake realm\"\r\n\r\n",
-
-              "HTTP/1.1 401 Unauthorized\r\n" +
-              "WWW-Authenticate: Digest realm=\"wally land\" domain=/ " +
-              "nonce=\"2B7F3A2B\" qop=\"auth\"\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n\r\n",      // no value
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n\r\n" +     // no value, with response body
-              "Some Response Body",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n" +    // no value, followed by another header
-              "Content-Length: 10\r\n\r\n" +
-              "Some Response Body",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n" +    // no value, followed by another header, with response body
-              "Content-Length: 10\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo: chegar\r\n" +
-              "X-Foo: dfuchs\r\n" +  // same header appears multiple times
-              "Content-Length: 0\r\n" +
-              "X-Foo: michaelm\r\n" +
-              "X-Foo: prappo\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n" +    // no value, same header appears multiple times
-              "X-Foo: dfuchs\r\n" +
-              "Content-Length: 0\r\n" +
-              "X-Foo: michaelm\r\n" +
-              "X-Foo: prappo\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Accept-Ranges: bytes\r\n" +
-              "Cache-control: max-age=0, no-cache=\"set-cookie\"\r\n" +
-              "Content-Length: 132868\r\n" +
-              "Content-Type: text/html; charset=UTF-8\r\n" +
-              "Date: Sun, 05 Nov 2017 22:24:03 GMT\r\n" +
-              "Server: Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips Communique/4.2.2\r\n" +
-              "Set-Cookie: AWSELB=AF7927F5100F4202119876ED2436B5005EE;PATH=/;MAX-AGE=900\r\n" +
-              "Vary: Host,Accept-Encoding,User-Agent\r\n" +
-              "X-Mod-Pagespeed: 1.12.34.2-0\r\n" +
-              "Connection: keep-alive\r\n\r\n"
-            };
-        Arrays.stream(basic).forEach(responses::add);
-
-        String[] foldingTemplate =
-           {  "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 9\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r'
-              " charset=UTF-8\r\n" +                // one preceding SP
-              "Connection: close\r\n\r\n" +
-              "XXYYZZAABBCCDDEE",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 19\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "   charset=UTF-8\r\n" +              // more than one preceding SP
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGG",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 999\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "\tcharset=UTF-8\r\n" +               // one preceding HT
-              "Connection: close\r\n\r\n" +
-              "XXYYZZAABBCCDDEE",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 54\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "\t\t\tcharset=UTF-8\r\n" +           // more than one preceding HT
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGG",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: -1\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "\t \t \tcharset=UTF-8\r\n" +         // mix of preceding HT and SP
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGGHH",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 65\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              " \t \t charset=UTF-8\r\n" +          // mix of preceding SP and HT
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGGHHII",
-
-              "HTTP/1.1 401 Unauthorized\r\n" +
-              "WWW-Authenticate: Digest realm=\"wally land\","
-                      +"$NEWLINE    domain=/,"
-                      +"$NEWLINE nonce=\"2B7F3A2B\","
-                      +"$NEWLINE\tqop=\"auth\"\r\n\r\n",
-
-           };
-        for (String newLineChar : new String[] { "\n", "\r", "\r\n" }) {
-            for (String template : foldingTemplate)
-                responses.add(template.replace("$NEWLINE", newLineChar));
-        }
-
-        String[] bad = // much of this is to retain parity with legacy MessageHeaders
-           { "HTTP/1.1 200 OK\r\n" +
-             "Connection:\r\n\r\n",   // empty value, no body
-
-             "HTTP/1.1 200 OK\r\n" +
-             "Connection:\r\n\r\n" +  // empty value, with body
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no header\r\n\r\n",  // no/empty header-name, no body, no following header
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no; header\r\n" +  // no/empty header-name, no body, following header
-             "Content-Length: 65\r\n\r\n",
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no header\r\n" +  // no/empty header-name
-             "Content-Length: 65\r\n\r\n" +
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no header\r\n\r\n" +  // no/empty header-name, followed by header
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             "Conte\r" +
-             " nt-Length: 9\r\n" +    // fold/bad header name ???
-             "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             "Conte\r" +
-             "nt-Length: 9\r\n" +    // fold/bad header name ??? without preceding space
-             "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
-             "XXXXXYYZZ",
-
-             "HTTP/1.0 404 Not Found\r\n" +
-             "header-without-colon\r\n\r\n",
-
-             "HTTP/1.0 404 Not Found\r\n" +
-             "header-without-colon\r\n\r\n" +
-             "SOMEBODY",
-
-           };
-        Arrays.stream(bad).forEach(responses::add);
-
-        return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
-    }
-
-    @Test(dataProvider = "responses")
-    public void verifyHeaders(String respString) throws Exception {
-        byte[] bytes = respString.getBytes(US_ASCII);
-        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
-        MessageHeader m = new MessageHeader(bais);
-        Map<String,List<String>> messageHeaderMap = m.getHeaders();
-        int available = bais.available();
-
-        Http1HeaderParser decoder = new Http1HeaderParser();
-        ByteBuffer b = ByteBuffer.wrap(bytes);
-        decoder.parse(b);
-        Map<String,List<String>> decoderMap1 = decoder.headers().map();
-        assertEquals(available, b.remaining(),
-                     "stream available not equal to remaining");
-
-        // assert status-line
-        String statusLine1 = messageHeaderMap.get(null).get(0);
-        String statusLine2 = decoder.statusLine();
-        if (statusLine1.startsWith("HTTP")) {// skip the case where MH's messes up the status-line
-            assertEquals(statusLine1, statusLine2, "Status-line not equal");
-        } else {
-            assertTrue(statusLine2.startsWith("HTTP/1."), "Status-line not HTTP/1.");
-        }
-
-        // remove the null'th entry with is the status-line
-        Map<String,List<String>> map = new HashMap<>();
-        for (Map.Entry<String,List<String>> e : messageHeaderMap.entrySet()) {
-            if (e.getKey() != null) {
-                map.put(e.getKey(), e.getValue());
-            }
-        }
-        messageHeaderMap = map;
-
-        assertHeadersEqual(messageHeaderMap, decoderMap1,
-                          "messageHeaderMap not equal to decoderMap1");
-
-        // byte at a time
-        decoder = new Http1HeaderParser();
-        List<ByteBuffer> buffers = IntStream.range(0, bytes.length)
-                .mapToObj(i -> ByteBuffer.wrap(bytes, i, 1))
-                .collect(toList());
-        while (decoder.parse(buffers.remove(0)) != true);
-        Map<String,List<String>> decoderMap2 = decoder.headers().map();
-        assertEquals(available, buffers.size(),
-                     "stream available not equals to remaining buffers");
-        assertEquals(decoderMap1, decoderMap2, "decoder maps not equal");
-    }
-
-    @DataProvider(name = "errors")
-    public Object[][] errors() {
-        List<String> responses = new ArrayList<>();
-
-        // These responses are parsed, somewhat, by MessageHeaders but give
-        // nonsensible results. They, correctly, fail with the Http1HeaderParser.
-        String[] bad =
-           {// "HTTP/1.1 402 Payment Required\r\n" +
-            // "Content-Length: 65\r\n\r",   // missing trailing LF   //TODO: incomplete
-
-             "HTTP/1.1 402 Payment Required\r\n" +
-             "Content-Length: 65\r\n\rT\r\n\r\nGGGGGG",
-
-             "HTTP/1.1 200OK\r\n\rT",
-
-             "HTTP/1.1 200OK\rT",
-           };
-        Arrays.stream(bad).forEach(responses::add);
-
-        return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
-    }
-
-    @Test(dataProvider = "errors", expectedExceptions = ProtocolException.class)
-    public void errors(String respString) throws ProtocolException {
-        byte[] bytes = respString.getBytes(US_ASCII);
-        Http1HeaderParser decoder = new Http1HeaderParser();
-        ByteBuffer b = ByteBuffer.wrap(bytes);
-        decoder.parse(b);
-    }
-
-    void assertHeadersEqual(Map<String,List<String>> expected,
-                            Map<String,List<String>> actual,
-                            String msg) {
-
-        if (expected.equals(actual))
-            return;
-
-        assertEquals(expected.size(), actual.size(),
-                     format("%s. Expected size %d, actual size %s. %nexpected= %s,%n actual=%s.",
-                            msg, expected.size(), actual.size(), mapToString(expected), mapToString(actual)));
-
-        for (Map.Entry<String,List<String>> e : expected.entrySet()) {
-            String key = e.getKey();
-            List<String> values = e.getValue();
-
-            boolean found = false;
-            for (Map.Entry<String,List<String>> other: actual.entrySet()) {
-                if (key.equalsIgnoreCase(other.getKey())) {
-                    found = true;
-                    List<String> otherValues = other.getValue();
-                    assertEquals(values.size(), otherValues.size(),
-                                 format("%s. Expected list size %d, actual size %s",
-                                        msg, values.size(), otherValues.size()));
-                    if (!(values.containsAll(otherValues) && otherValues.containsAll(values)))
-                        assertTrue(false, format("Lists are unequal [%s] [%s]", values, otherValues));
-                    break;
-                }
-            }
-            assertTrue(found, format("header name, %s, not found in %s", key, actual));
-        }
-    }
-
-    static String mapToString(Map<String,List<String>> map) {
-        StringBuilder sb = new StringBuilder();
-        List<String> sortedKeys = new ArrayList(map.keySet());
-        Collections.sort(sortedKeys);
-        for (String key : sortedKeys) {
-            List<String> values = map.get(key);
-            sb.append("\n\t" + key + " | " + values);
-        }
-        return sb.toString();
-    }
-
-    // ---
-
-    /* Main entry point for standalone testing of the main functional test. */
-    public static void main(String... args) throws Exception  {
-        Http1HeaderParserTest test = new Http1HeaderParserTest();
-        int count = 0;
-        for (Object[] objs : test.responses()) {
-            out.println("Testing " + count++ + ", " + objs[0]);
-            test.verifyHeaders((String) objs[0]);
-        }
-        for (Object[] objs : test.errors()) {
-            out.println("Testing " + count++ + ", " + objs[0]);
-            try {
-                test.errors((String) objs[0]);
-                throw new RuntimeException("Expected ProtocolException for " + objs[0]);
-            } catch (ProtocolException expected) { /* Ok */ }
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/RawChannelTest.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,332 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.websocket.RawChannel;
-import jdk.incubator.http.internal.websocket.WebSocketRequest;
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UncheckedIOException;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-import static org.testng.Assert.assertEquals;
-
-/*
- * This test exercises mechanics of _independent_ reads and writes on the
- * RawChannel. It verifies that the underlying implementation can manage more
- * than a single type of notifications at the same time.
- */
-public class RawChannelTest {
-
-    private final AtomicLong clientWritten = new AtomicLong();
-    private final AtomicLong serverWritten = new AtomicLong();
-    private final AtomicLong clientRead = new AtomicLong();
-    private final AtomicLong serverRead = new AtomicLong();
-
-    /*
-     * Since at this level we don't have any control over the low level socket
-     * parameters, this latch ensures a write to the channel will stall at least
-     * once (socket's send buffer filled up).
-     */
-    private final CountDownLatch writeStall = new CountDownLatch(1);
-    private final CountDownLatch initialWriteStall = new CountDownLatch(1);
-
-    /*
-     * This one works similarly by providing means to ensure a read from the
-     * channel will stall at least once (no more data available on the socket).
-     */
-    private final CountDownLatch readStall = new CountDownLatch(1);
-    private final CountDownLatch initialReadStall = new CountDownLatch(1);
-
-    private final AtomicInteger writeHandles = new AtomicInteger();
-    private final AtomicInteger readHandles = new AtomicInteger();
-
-    private final CountDownLatch exit = new CountDownLatch(1);
-
-    @Test
-    public void test() throws Exception {
-        try (ServerSocket server = new ServerSocket(0)) {
-            int port = server.getLocalPort();
-            new TestServer(server).start();
-
-            final RawChannel chan = channelOf(port);
-            print("RawChannel is %s", String.valueOf(chan));
-            initialWriteStall.await();
-
-            // It's very important not to forget the initial bytes, possibly
-            // left from the HTTP thingy
-            int initialBytes = chan.initialByteBuffer().remaining();
-            print("RawChannel has %s initial bytes", initialBytes);
-            clientRead.addAndGet(initialBytes);
-
-            // tell the server we have read the initial bytes, so
-            // that it makes sure there is something for us to
-            // read next in case the initialBytes have already drained the
-            // channel dry.
-            initialReadStall.countDown();
-
-            chan.registerEvent(new RawChannel.RawEvent() {
-
-                private final ByteBuffer reusableBuffer = ByteBuffer.allocate(32768);
-
-                @Override
-                public int interestOps() {
-                    return SelectionKey.OP_WRITE;
-                }
-
-                @Override
-                public void handle() {
-                    int i = writeHandles.incrementAndGet();
-                    print("OP_WRITE #%s", i);
-                    if (i > 3) { // Fill up the send buffer not more than 3 times
-                        try {
-                            chan.shutdownOutput();
-                        } catch (IOException e) {
-                            e.printStackTrace();
-                        }
-                        return;
-                    }
-                    long total = 0;
-                    try {
-                        long n;
-                        do {
-                            ByteBuffer[] array = {reusableBuffer.slice()};
-                            n = chan.write(array, 0, 1);
-                            total += n;
-                        } while (n > 0);
-                        print("OP_WRITE clogged SNDBUF with %s bytes", total);
-                        clientWritten.addAndGet(total);
-                        chan.registerEvent(this);
-                        writeStall.countDown(); // signal send buffer is full
-                    } catch (IOException e) {
-                        throw new UncheckedIOException(e);
-                    }
-                }
-            });
-
-            chan.registerEvent(new RawChannel.RawEvent() {
-
-                @Override
-                public int interestOps() {
-                    return SelectionKey.OP_READ;
-                }
-
-                @Override
-                public void handle() {
-                    int i = readHandles.incrementAndGet();
-                    print("OP_READ #%s", i);
-                    ByteBuffer read = null;
-                    long total = 0;
-                    while (true) {
-                        try {
-                            read = chan.read();
-                        } catch (IOException e) {
-                            e.printStackTrace();
-                        }
-                        if (read == null) {
-                            print("OP_READ EOF");
-                            break;
-                        } else if (!read.hasRemaining()) {
-                            print("OP_READ stall");
-                            try {
-                                chan.registerEvent(this);
-                            } catch (IOException e) {
-                                e.printStackTrace();
-                            }
-                            readStall.countDown();
-                            break;
-                        }
-                        int r = read.remaining();
-                        total += r;
-                        clientRead.addAndGet(r);
-                    }
-                    print("OP_READ read %s bytes (%s total)", total, clientRead.get());
-                }
-            });
-            exit.await(); // All done, we need to compare results:
-            assertEquals(clientRead.get(), serverWritten.get());
-            assertEquals(serverRead.get(), clientWritten.get());
-        }
-    }
-
-    private static RawChannel channelOf(int port) throws Exception {
-        URI uri = URI.create("http://127.0.0.1:" + port + "/");
-        print("raw channel to %s", uri.toString());
-        HttpRequest req = HttpRequest.newBuilder(uri).build();
-        // Switch on isWebSocket flag to prevent the connection from
-        // being returned to the pool.
-        ((WebSocketRequest)req).isWebSocket(true);
-        HttpClient client = HttpClient.newHttpClient();
-        try {
-            HttpResponse<?> r = client.send(req, discard(null));
-            r.body();
-            return ((HttpResponseImpl) r).rawChannel();
-        } finally {
-           // Need to hold onto the client until the RawChannel is
-           // created. This would not be needed if we had created
-           // a WebSocket, but here we are fiddling directly
-           // with the internals of HttpResponseImpl!
-           java.lang.ref.Reference.reachabilityFence(client);
-        }
-    }
-
-    private class TestServer extends Thread { // Powered by Slowpokes
-
-        private final ServerSocket server;
-
-        TestServer(ServerSocket server) throws IOException {
-            this.server = server;
-        }
-
-        @Override
-        public void run() {
-            try (Socket s = server.accept()) {
-                InputStream is = s.getInputStream();
-                OutputStream os = s.getOutputStream();
-
-                processHttp(is, os);
-
-                Thread reader = new Thread(() -> {
-                    try {
-                        long n = readSlowly(is);
-                        print("Server read %s bytes", n);
-                        serverRead.addAndGet(n);
-                        s.shutdownInput();
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-                });
-
-                Thread writer = new Thread(() -> {
-                    try {
-                        long n = writeSlowly(os);
-                        print("Server written %s bytes", n);
-                        serverWritten.addAndGet(n);
-                        s.shutdownOutput();
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-                });
-
-                reader.start();
-                writer.start();
-
-                reader.join();
-                writer.join();
-            } catch (Exception e) {
-                e.printStackTrace();
-            } finally {
-                exit.countDown();
-            }
-        }
-
-        private void processHttp(InputStream is, OutputStream os)
-                throws IOException
-        {
-            os.write("HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n".getBytes());
-
-            // write some initial bytes
-            byte[] initial = byteArrayOfSize(1024);
-            os.write(initial);
-            os.flush();
-            serverWritten.addAndGet(initial.length);
-            initialWriteStall.countDown();
-
-            byte[] buf = new byte[1024];
-            String s = "";
-            while (true) {
-                int n = is.read(buf);
-                if (n <= 0) {
-                    throw new RuntimeException("Unexpected end of request");
-                }
-                s = s + new String(buf, 0, n);
-                if (s.contains("\r\n\r\n")) {
-                    break;
-                }
-            }
-        }
-
-        private long writeSlowly(OutputStream os) throws Exception {
-            byte[] first = byteArrayOfSize(1024);
-            long total = first.length;
-            os.write(first);
-            os.flush();
-
-            // wait until initial bytes were read
-            initialReadStall.await();
-
-            // make sure there is something to read, otherwise readStall
-            // will never be counted down.
-            first = byteArrayOfSize(1024);
-            os.write(first);
-            os.flush();
-            total += first.length;
-
-            // Let's wait for the signal from the raw channel that its read has
-            // stalled, and then continue sending a bit more stuff
-            readStall.await();
-            for (int i = 0; i < 32; i++) {
-                byte[] b = byteArrayOfSize(1024);
-                os.write(b);
-                os.flush();
-                total += b.length;
-                TimeUnit.MILLISECONDS.sleep(1);
-            }
-            return total;
-        }
-
-        private long readSlowly(InputStream is) throws Exception {
-            // Wait for the raw channel to fill up its send buffer
-            writeStall.await();
-            long overall = 0;
-            byte[] array = new byte[1024];
-            for (int n = 0; n != -1; n = is.read(array)) {
-                TimeUnit.MILLISECONDS.sleep(1);
-                overall += n;
-            }
-            return overall;
-        }
-    }
-
-    private static void print(String format, Object... args) {
-        System.out.println(Thread.currentThread() + ": " + String.format(format, args));
-    }
-
-    private static byte[] byteArrayOfSize(int bound) {
-        return new byte[new Random().nextInt(1 + bound)];
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/SelectorTest.java	Tue Feb 06 11:39:55 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.*;
-import java.io.*;
-import java.nio.channels.*;
-import java.nio.ByteBuffer;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-import static java.lang.System.out;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-
-import org.testng.annotations.Test;
-import jdk.incubator.http.internal.websocket.RawChannel;
-
-/**
- * Whitebox test of selector mechanics. Currently only a simple test
- * setting one read and one write event is done. It checks that the
- * write event occurs first, followed by the read event and then no
- * further events occur despite the conditions actually still existing.
- */
-@Test
-public class SelectorTest {
-
-    AtomicInteger counter = new AtomicInteger();
-    volatile boolean error;
-    static final CountDownLatch finishingGate = new CountDownLatch(1);
-    static volatile HttpClient staticDefaultClient;
-
-    static HttpClient defaultClient() {
-        if (staticDefaultClient == null) {
-            synchronized (SelectorTest.class) {
-                staticDefaultClient = HttpClient.newHttpClient();
-            }
-        }
-        return staticDefaultClient;
-    }
-
-    String readSomeBytes(RawChannel chan) {
-        try {
-            ByteBuffer buf = chan.read();
-            if (buf == null) {
-                out.println("chan read returned null");
-                return null;
-            }
-            buf.flip();
-            byte[] bb = new byte[buf.remaining()];
-            buf.get(bb);
-            return new String(bb, US_ASCII);
-        } catch (IOException ioe) {
-            throw new UncheckedIOException(ioe);
-        }
-    }
-
-    @Test
-    public void test() throws Exception {
-
-        try (ServerSocket server = new ServerSocket(0)) {
-            int port = server.getLocalPort();
-
-            out.println("Listening on port " + server.getLocalPort());
-
-            TestServer t = new TestServer(server);
-            t.start();
-            out.println("Started server thread");
-
-            try (RawChannel chan = getARawChannel(port)) {
-
-                chan.registerEvent(new RawChannel.RawEvent() {
-                    @Override
-                    public int interestOps() {
-                        return SelectionKey.OP_READ;
-                    }
-
-                    @Override
-                    public void handle() {
-                        readSomeBytes(chan);
-                        out.printf("OP_READ\n");
-                        final int count = counter.get();
-                        if (count != 1) {
-                            out.printf("OP_READ error counter = %d\n", count);
-                            error = true;
-                        }
-                    }
-                });
-
-                chan.registerEvent(new RawChannel.RawEvent() {
-                    @Override
-                    public int interestOps() {
-                        return SelectionKey.OP_WRITE;
-                    }
-
-                    @Override
-                    public void handle() {
-                        out.printf("OP_WRITE\n");
-                        final int count = counter.get();
-                        if (count != 0) {
-                            out.printf("OP_WRITE error counter = %d\n", count);
-                            error = true;
-                        } else {
-                            ByteBuffer bb = ByteBuffer.wrap(TestServer.INPUT);
-                            counter.incrementAndGet();
-                            try {
-                                chan.write(new ByteBuffer[]{bb}, 0, 1);
-                            } catch (IOException e) {
-                                throw new UncheckedIOException(e);
-                            }
-                        }
-                    }
-
-                });
-                out.println("Events registered. Waiting");
-                finishingGate.await(30, SECONDS);
-                if (error)
-                    throw new RuntimeException("Error");
-                else
-                    out.println("No error");
-            }
-        }
-    }
-
-    static RawChannel getARawChannel(int port) throws Exception {
-        URI uri = URI.create("http://127.0.0.1:" + port + "/");
-        out.println("client connecting to " + uri.toString());
-        HttpRequest req = HttpRequest.newBuilder(uri).build();
-        // Otherwise HttpClient will think this is an ordinary connection and
-        // thus all ordinary procedures apply to it, e.g. it must be put into
-        // the cache
-        ((HttpRequestImpl) req).isWebSocket(true);
-        HttpResponse<?> r = defaultClient().send(req, discard(null));
-        r.body();
-        return ((HttpResponseImpl) r).rawChannel();
-    }
-
-    static class TestServer extends Thread {
-        static final byte[] INPUT = "Hello world".getBytes(US_ASCII);
-        static final byte[] OUTPUT = "Goodbye world".getBytes(US_ASCII);
-        static final String FIRST_RESPONSE = "HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n";
-        final ServerSocket server;
-
-        TestServer(ServerSocket server) throws IOException {
-            this.server = server;
-        }
-
-        public void run() {
-            try (Socket s = server.accept();
-                 InputStream is = s.getInputStream();
-                 OutputStream os = s.getOutputStream()) {
-
-                out.println("Got connection");
-                readRequest(is);
-                os.write(FIRST_RESPONSE.getBytes());
-                read(is);
-                write(os);
-                Thread.sleep(1000);
-                // send some more data, and make sure WRITE op does not get called
-                write(os);
-                out.println("TestServer exiting");
-                SelectorTest.finishingGate.countDown();
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-
-        // consumes the HTTP request
-        static void readRequest(InputStream is) throws IOException {
-            out.println("starting readRequest");
-            byte[] buf = new byte[1024];
-            String s = "";
-            while (true) {
-                int n = is.read(buf);
-                if (n <= 0)
-                    throw new IOException("Error");
-                s = s + new String(buf, 0, n);
-                if (s.indexOf("\r\n\r\n") != -1)
-                    break;
-            }
-            out.println("returning from readRequest");
-        }
-
-        static void read(InputStream is) throws IOException {
-            out.println("starting read");
-            for (int i = 0; i < INPUT.length; i++) {
-                int c = is.read();
-                if (c == -1)
-                    throw new IOException("closed");
-                if (INPUT[i] != (byte) c)
-                    throw new IOException("Error. Expected:" + INPUT[i] + ", got:" + c);
-            }
-            out.println("returning from read");
-        }
-
-        static void write(OutputStream os) throws IOException {
-            out.println("doing write");
-            os.write(OUTPUT);
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/ConnectionPoolTest.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import java.util.stream.IntStream;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.internal.common.FlowTube;
+
+/**
+ * @summary Verifies that the ConnectionPool correctly handle
+ *          connection deadlines and purges the right connections
+ *          from the cache.
+ * @bug 8187044 8187111
+ * @author danielfuchs
+ */
+public class ConnectionPoolTest {
+
+    static long getActiveCleaners() throws ClassNotFoundException {
+        // ConnectionPool.ACTIVE_CLEANER_COUNTER.get()
+        // ConnectionPoolTest.class.getModule().addReads(
+        //      Class.forName("java.lang.management.ManagementFactory").getModule());
+        return java.util.stream.Stream.of(ManagementFactory.getThreadMXBean()
+                .dumpAllThreads(false, false))
+              .filter(t -> t.getThreadName().startsWith("HTTP-Cache-cleaner"))
+              .count();
+    }
+
+    public static void main(String[] args) throws Exception {
+        testCacheCleaners();
+    }
+
+    public static void testCacheCleaners() throws Exception {
+        ConnectionPool pool = new ConnectionPool(666);
+        HttpClient client = new HttpClientStub(pool);
+        InetSocketAddress proxy = InetSocketAddress.createUnresolved("bar", 80);
+        System.out.println("Adding 10 connections to pool");
+        Random random = new Random();
+
+        final int count = 20;
+        Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
+        int[] keepAlives = new int[count];
+        HttpConnectionStub[] connections = new HttpConnectionStub[count];
+        long purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
+        long expected = 0;
+        if (purge != expected) {
+            throw new RuntimeException("Bad purge delay: " + purge
+                                        + ", expected " + expected);
+        }
+        expected = Long.MAX_VALUE;
+        for (int i=0; i<count; i++) {
+            InetSocketAddress addr = InetSocketAddress.createUnresolved("foo"+i, 80);
+            keepAlives[i] = random.nextInt(10) * 10  + 10;
+            connections[i] = new HttpConnectionStub(client, addr, proxy, true);
+            System.out.println("Adding connection: " + now
+                                + " keepAlive: " + keepAlives[i]
+                                + " /" + connections[i]);
+            pool.returnToPool(connections[i], now, keepAlives[i]);
+            expected = Math.min(expected, keepAlives[i] * 1000);
+            purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
+            if (purge != expected) {
+                throw new RuntimeException("Bad purge delay: " + purge
+                                        + ", expected " + expected);
+            }
+        }
+        int min = IntStream.of(keepAlives).min().getAsInt();
+        int max = IntStream.of(keepAlives).max().getAsInt();
+        int mean = (min + max)/2;
+        System.out.println("min=" + min + ", max=" + max + ", mean=" + mean);
+        purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
+        System.out.println("first purge would be in " + purge + " ms");
+        if (Math.abs(purge/1000 - min) > 0) {
+            throw new RuntimeException("expected " + min + " got " + purge/1000);
+        }
+        long opened = java.util.stream.Stream.of(connections)
+                     .filter(HttpConnectionStub::connected).count();
+        if (opened != count) {
+            throw new RuntimeException("Opened: expected "
+                                       + count + " got " + opened);
+        }
+        purge = mean * 1000;
+        System.out.println("start purging at " + purge + " ms");
+        Instant next = now;
+        do {
+           System.out.println("next purge is in " + purge + " ms");
+           next = next.plus(purge, ChronoUnit.MILLIS);
+           purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(next);
+           long k = now.until(next, ChronoUnit.SECONDS);
+           System.out.println("now is " + k + "s from start");
+           for (int i=0; i<count; i++) {
+               if (connections[i].connected() != (k < keepAlives[i])) {
+                   throw new RuntimeException("Bad connection state for "
+                             + i
+                             + "\n\t connected=" + connections[i].connected()
+                             + "\n\t keepAlive=" + keepAlives[i]
+                             + "\n\t elapsed=" + k);
+               }
+           }
+        } while (purge > 0);
+        opened = java.util.stream.Stream.of(connections)
+                     .filter(HttpConnectionStub::connected).count();
+        if (opened != 0) {
+           throw new RuntimeException("Closed: expected "
+                                       + count + " got "
+                                       + (count-opened));
+        }
+    }
+
+    static <T> T error() {
+        throw new InternalError("Should not reach here: wrong test assumptions!");
+    }
+
+    static class FlowTubeStub implements FlowTube {
+        final HttpConnectionStub conn;
+        FlowTubeStub(HttpConnectionStub conn) { this.conn = conn; }
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) { }
+        @Override public void onError(Throwable error) { error(); }
+        @Override public void onComplete() { error(); }
+        @Override public void onNext(List<ByteBuffer> item) { error();}
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+        }
+        @Override public boolean isFinished() { return conn.closed; }
+    }
+
+    // Emulates an HttpConnection that has a strong reference to its HttpClient.
+    static class HttpConnectionStub extends HttpConnection {
+
+        public HttpConnectionStub(HttpClient client,
+                InetSocketAddress address,
+                InetSocketAddress proxy,
+                boolean secured) {
+            super(address, null);
+            this.key = ConnectionPool.cacheKey(address, proxy);
+            this.address = address;
+            this.proxy = proxy;
+            this.secured = secured;
+            this.client = client;
+            this.flow = new FlowTubeStub(this);
+        }
+
+        final InetSocketAddress proxy;
+        final InetSocketAddress address;
+        final boolean secured;
+        final ConnectionPool.CacheKey key;
+        final HttpClient client;
+        final FlowTubeStub flow;
+        volatile boolean closed;
+
+        // All these return something
+        @Override boolean connected() {return !closed;}
+        @Override boolean isSecure() {return secured;}
+        @Override boolean isProxied() {return proxy!=null;}
+        @Override ConnectionPool.CacheKey cacheKey() {return key;}
+        @Override void shutdownInput() throws IOException {}
+        @Override void shutdownOutput() throws IOException {}
+        @Override
+        public void close() {
+            closed=true;
+            System.out.println("closed: " + this);
+        }
+        @Override
+        public String toString() {
+            return "HttpConnectionStub: " + address + " proxy: " + proxy;
+        }
+
+        // All these throw errors
+        @Override public HttpPublisher publisher() {return error();}
+        @Override public CompletableFuture<Void> connectAsync() {return error();}
+        @Override SocketChannel channel() {return error();}
+        @Override
+        HttpConnection.DetachedConnectionChannel detachChannel() {
+            return error();
+        }
+        @Override
+        FlowTube getConnectionFlow() {return flow;}
+    }
+    // Emulates an HttpClient that has a strong reference to its connection pool.
+    static class HttpClientStub extends HttpClient {
+        public HttpClientStub(ConnectionPool pool) {
+            this.pool = pool;
+        }
+        final ConnectionPool pool;
+        @Override public Optional<CookieHandler> cookieHandler() {return error();}
+        @Override public HttpClient.Redirect followRedirects() {return error();}
+        @Override public Optional<ProxySelector> proxy() {return error();}
+        @Override public SSLContext sslContext() {return error();}
+        @Override public SSLParameters sslParameters() {return error();}
+        @Override public Optional<Authenticator> authenticator() {return error();}
+        @Override public HttpClient.Version version() {return HttpClient.Version.HTTP_1_1;}
+        @Override public Optional<Executor> executor() {return error();}
+        @Override
+        public <T> HttpResponse<T> send(HttpRequest req,
+                                        HttpResponse.BodyHandler<T> responseBodyHandler)
+                throws IOException, InterruptedException {
+            return error();
+        }
+        @Override
+        public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req,
+                HttpResponse.BodyHandler<T> responseBodyHandler) {
+            return error();
+        }
+        @Override
+        public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req,
+                HttpResponse.BodyHandler<T> bodyHandler,
+                HttpResponse.PushPromiseHandler<T> multiHandler) {
+            return error();
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/Http1HeaderParserTest.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.ByteArrayInputStream;
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
+import sun.net.www.MessageHeader;
+import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
+import static java.lang.System.out;
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.stream.Collectors.toList;
+import static org.testng.Assert.*;
+
+// Mostly verifies the "new" Http1HeaderParser returns the same results as the
+// tried and tested sun.net.www.MessageHeader.
+
+public class Http1HeaderParserTest {
+
+    @DataProvider(name = "responses")
+    public Object[][] responses() {
+        List<String> responses = new ArrayList<>();
+
+        String[] basic =
+            { "HTTP/1.1 200 OK\r\n\r\n",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
+              "Server: Apache/1.3.14 (Unix)\r\n" +
+              "Connection: close\r\n" +
+              "Content-Type: text/html; charset=iso-8859-1\r\n" +
+              "Content-Length: 10\r\n\r\n" +
+              "123456789",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length: 9\r\n" +
+              "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
+              "XXXXX",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length:   9\r\n" +
+              "Content-Type:   text/html; charset=UTF-8\r\n\r\n" +   // more than one SP after ':'
+              "XXXXX",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length:\t10\r\n" +
+              "Content-Type:\ttext/html; charset=UTF-8\r\n\r\n" +   // HT separator
+              "XXXXX",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length:\t\t10\r\n" +
+              "Content-Type:\t\ttext/html; charset=UTF-8\r\n\r\n" +   // more than one HT after ':'
+              "XXXXX",
+
+              "HTTP/1.1 407 Proxy Authorization Required\r\n" +
+              "Proxy-Authenticate: Basic realm=\"a fake realm\"\r\n\r\n",
+
+              "HTTP/1.1 401 Unauthorized\r\n" +
+              "WWW-Authenticate: Digest realm=\"wally land\" domain=/ " +
+              "nonce=\"2B7F3A2B\" qop=\"auth\"\r\n\r\n",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "X-Foo:\r\n\r\n",      // no value
+
+              "HTTP/1.1 200 OK\r\n" +
+              "X-Foo:\r\n\r\n" +     // no value, with response body
+              "Some Response Body",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "X-Foo:\r\n" +    // no value, followed by another header
+              "Content-Length: 10\r\n\r\n" +
+              "Some Response Body",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "X-Foo:\r\n" +    // no value, followed by another header, with response body
+              "Content-Length: 10\r\n\r\n",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "X-Foo: chegar\r\n" +
+              "X-Foo: dfuchs\r\n" +  // same header appears multiple times
+              "Content-Length: 0\r\n" +
+              "X-Foo: michaelm\r\n" +
+              "X-Foo: prappo\r\n\r\n",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "X-Foo:\r\n" +    // no value, same header appears multiple times
+              "X-Foo: dfuchs\r\n" +
+              "Content-Length: 0\r\n" +
+              "X-Foo: michaelm\r\n" +
+              "X-Foo: prappo\r\n\r\n",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Accept-Ranges: bytes\r\n" +
+              "Cache-control: max-age=0, no-cache=\"set-cookie\"\r\n" +
+              "Content-Length: 132868\r\n" +
+              "Content-Type: text/html; charset=UTF-8\r\n" +
+              "Date: Sun, 05 Nov 2017 22:24:03 GMT\r\n" +
+              "Server: Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips Communique/4.2.2\r\n" +
+              "Set-Cookie: AWSELB=AF7927F5100F4202119876ED2436B5005EE;PATH=/;MAX-AGE=900\r\n" +
+              "Vary: Host,Accept-Encoding,User-Agent\r\n" +
+              "X-Mod-Pagespeed: 1.12.34.2-0\r\n" +
+              "Connection: keep-alive\r\n\r\n"
+            };
+        Arrays.stream(basic).forEach(responses::add);
+
+        String[] foldingTemplate =
+           {  "HTTP/1.1 200 OK\r\n" +
+              "Content-Length: 9\r\n" +
+              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r'
+              " charset=UTF-8\r\n" +                // one preceding SP
+              "Connection: close\r\n\r\n" +
+              "XXYYZZAABBCCDDEE",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length: 19\r\n" +
+              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
+              "   charset=UTF-8\r\n" +              // more than one preceding SP
+              "Connection: keep-alive\r\n\r\n" +
+              "XXYYZZAABBCCDDEEFFGG",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length: 999\r\n" +
+              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
+              "\tcharset=UTF-8\r\n" +               // one preceding HT
+              "Connection: close\r\n\r\n" +
+              "XXYYZZAABBCCDDEE",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length: 54\r\n" +
+              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
+              "\t\t\tcharset=UTF-8\r\n" +           // more than one preceding HT
+              "Connection: keep-alive\r\n\r\n" +
+              "XXYYZZAABBCCDDEEFFGG",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length: -1\r\n" +
+              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
+              "\t \t \tcharset=UTF-8\r\n" +         // mix of preceding HT and SP
+              "Connection: keep-alive\r\n\r\n" +
+              "XXYYZZAABBCCDDEEFFGGHH",
+
+              "HTTP/1.1 200 OK\r\n" +
+              "Content-Length: 65\r\n" +
+              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
+              " \t \t charset=UTF-8\r\n" +          // mix of preceding SP and HT
+              "Connection: keep-alive\r\n\r\n" +
+              "XXYYZZAABBCCDDEEFFGGHHII",
+
+              "HTTP/1.1 401 Unauthorized\r\n" +
+              "WWW-Authenticate: Digest realm=\"wally land\","
+                      +"$NEWLINE    domain=/,"
+                      +"$NEWLINE nonce=\"2B7F3A2B\","
+                      +"$NEWLINE\tqop=\"auth\"\r\n\r\n",
+
+           };
+        for (String newLineChar : new String[] { "\n", "\r", "\r\n" }) {
+            for (String template : foldingTemplate)
+                responses.add(template.replace("$NEWLINE", newLineChar));
+        }
+
+        String[] bad = // much of this is to retain parity with legacy MessageHeaders
+           { "HTTP/1.1 200 OK\r\n" +
+             "Connection:\r\n\r\n",   // empty value, no body
+
+             "HTTP/1.1 200 OK\r\n" +
+             "Connection:\r\n\r\n" +  // empty value, with body
+             "XXXXX",
+
+             "HTTP/1.1 200 OK\r\n" +
+             ": no header\r\n\r\n",  // no/empty header-name, no body, no following header
+
+             "HTTP/1.1 200 OK\r\n" +
+             ": no; header\r\n" +  // no/empty header-name, no body, following header
+             "Content-Length: 65\r\n\r\n",
+
+             "HTTP/1.1 200 OK\r\n" +
+             ": no header\r\n" +  // no/empty header-name
+             "Content-Length: 65\r\n\r\n" +
+             "XXXXX",
+
+             "HTTP/1.1 200 OK\r\n" +
+             ": no header\r\n\r\n" +  // no/empty header-name, followed by header
+             "XXXXX",
+
+             "HTTP/1.1 200 OK\r\n" +
+             "Conte\r" +
+             " nt-Length: 9\r\n" +    // fold/bad header name ???
+             "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
+             "XXXXX",
+
+             "HTTP/1.1 200 OK\r\n" +
+             "Conte\r" +
+             "nt-Length: 9\r\n" +    // fold/bad header name ??? without preceding space
+             "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
+             "XXXXXYYZZ",
+
+             "HTTP/1.0 404 Not Found\r\n" +
+             "header-without-colon\r\n\r\n",
+
+             "HTTP/1.0 404 Not Found\r\n" +
+             "header-without-colon\r\n\r\n" +
+             "SOMEBODY",
+
+           };
+        Arrays.stream(bad).forEach(responses::add);
+
+        return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+    }
+
+    @Test(dataProvider = "responses")
+    public void verifyHeaders(String respString) throws Exception {
+        byte[] bytes = respString.getBytes(US_ASCII);
+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+        MessageHeader m = new MessageHeader(bais);
+        Map<String,List<String>> messageHeaderMap = m.getHeaders();
+        int available = bais.available();
+
+        Http1HeaderParser decoder = new Http1HeaderParser();
+        ByteBuffer b = ByteBuffer.wrap(bytes);
+        decoder.parse(b);
+        Map<String,List<String>> decoderMap1 = decoder.headers().map();
+        assertEquals(available, b.remaining(),
+                     "stream available not equal to remaining");
+
+        // assert status-line
+        String statusLine1 = messageHeaderMap.get(null).get(0);
+        String statusLine2 = decoder.statusLine();
+        if (statusLine1.startsWith("HTTP")) {// skip the case where MH's messes up the status-line
+            assertEquals(statusLine1, statusLine2, "Status-line not equal");
+        } else {
+            assertTrue(statusLine2.startsWith("HTTP/1."), "Status-line not HTTP/1.");
+        }
+
+        // remove the null'th entry with is the status-line
+        Map<String,List<String>> map = new HashMap<>();
+        for (Map.Entry<String,List<String>> e : messageHeaderMap.entrySet()) {
+            if (e.getKey() != null) {
+                map.put(e.getKey(), e.getValue());
+            }
+        }
+        messageHeaderMap = map;
+
+        assertHeadersEqual(messageHeaderMap, decoderMap1,
+                          "messageHeaderMap not equal to decoderMap1");
+
+        // byte at a time
+        decoder = new Http1HeaderParser();
+        List<ByteBuffer> buffers = IntStream.range(0, bytes.length)
+                .mapToObj(i -> ByteBuffer.wrap(bytes, i, 1))
+                .collect(toList());
+        while (decoder.parse(buffers.remove(0)) != true);
+        Map<String,List<String>> decoderMap2 = decoder.headers().map();
+        assertEquals(available, buffers.size(),
+                     "stream available not equals to remaining buffers");
+        assertEquals(decoderMap1, decoderMap2, "decoder maps not equal");
+    }
+
+    @DataProvider(name = "errors")
+    public Object[][] errors() {
+        List<String> responses = new ArrayList<>();
+
+        // These responses are parsed, somewhat, by MessageHeaders but give
+        // nonsensible results. They, correctly, fail with the Http1HeaderParser.
+        String[] bad =
+           {// "HTTP/1.1 402 Payment Required\r\n" +
+            // "Content-Length: 65\r\n\r",   // missing trailing LF   //TODO: incomplete
+
+             "HTTP/1.1 402 Payment Required\r\n" +
+             "Content-Length: 65\r\n\rT\r\n\r\nGGGGGG",
+
+             "HTTP/1.1 200OK\r\n\rT",
+
+             "HTTP/1.1 200OK\rT",
+           };
+        Arrays.stream(bad).forEach(responses::add);
+
+        return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
+    }
+
+    @Test(dataProvider = "errors", expectedExceptions = ProtocolException.class)
+    public void errors(String respString) throws ProtocolException {
+        byte[] bytes = respString.getBytes(US_ASCII);
+        Http1HeaderParser decoder = new Http1HeaderParser();
+        ByteBuffer b = ByteBuffer.wrap(bytes);
+        decoder.parse(b);
+    }
+
+    void assertHeadersEqual(Map<String,List<String>> expected,
+                            Map<String,List<String>> actual,
+                            String msg) {
+
+        if (expected.equals(actual))
+            return;
+
+        assertEquals(expected.size(), actual.size(),
+                     format("%s. Expected size %d, actual size %s. %nexpected= %s,%n actual=%s.",
+                            msg, expected.size(), actual.size(), mapToString(expected), mapToString(actual)));
+
+        for (Map.Entry<String,List<String>> e : expected.entrySet()) {
+            String key = e.getKey();
+            List<String> values = e.getValue();
+
+            boolean found = false;
+            for (Map.Entry<String,List<String>> other: actual.entrySet()) {
+                if (key.equalsIgnoreCase(other.getKey())) {
+                    found = true;
+                    List<String> otherValues = other.getValue();
+                    assertEquals(values.size(), otherValues.size(),
+                                 format("%s. Expected list size %d, actual size %s",
+                                        msg, values.size(), otherValues.size()));
+                    if (!(values.containsAll(otherValues) && otherValues.containsAll(values)))
+                        assertTrue(false, format("Lists are unequal [%s] [%s]", values, otherValues));
+                    break;
+                }
+            }
+            assertTrue(found, format("header name, %s, not found in %s", key, actual));
+        }
+    }
+
+    static String mapToString(Map<String,List<String>> map) {
+        StringBuilder sb = new StringBuilder();
+        List<String> sortedKeys = new ArrayList(map.keySet());
+        Collections.sort(sortedKeys);
+        for (String key : sortedKeys) {
+            List<String> values = map.get(key);
+            sb.append("\n\t" + key + " | " + values);
+        }
+        return sb.toString();
+    }
+
+    // ---
+
+    /* Main entry point for standalone testing of the main functional test. */
+    public static void main(String... args) throws Exception  {
+        Http1HeaderParserTest test = new Http1HeaderParserTest();
+        int count = 0;
+        for (Object[] objs : test.responses()) {
+            out.println("Testing " + count++ + ", " + objs[0]);
+            test.verifyHeaders((String) objs[0]);
+        }
+        for (Object[] objs : test.errors()) {
+            out.println("Testing " + count++ + ", " + objs[0]);
+            try {
+                test.errors((String) objs[0]);
+                throw new RuntimeException("Expected ProtocolException for " + objs[0]);
+            } catch (ProtocolException expected) { /* Ok */ }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/RawChannelTest.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.internal.websocket.RawChannel;
+import jdk.incubator.http.internal.websocket.WebSocketRequest;
+import org.testng.annotations.Test;
+import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
+import static org.testng.Assert.assertEquals;
+
+/*
+ * This test exercises mechanics of _independent_ reads and writes on the
+ * RawChannel. It verifies that the underlying implementation can manage more
+ * than a single type of notifications at the same time.
+ */
+public class RawChannelTest {
+
+    private final AtomicLong clientWritten = new AtomicLong();
+    private final AtomicLong serverWritten = new AtomicLong();
+    private final AtomicLong clientRead = new AtomicLong();
+    private final AtomicLong serverRead = new AtomicLong();
+
+    /*
+     * Since at this level we don't have any control over the low level socket
+     * parameters, this latch ensures a write to the channel will stall at least
+     * once (socket's send buffer filled up).
+     */
+    private final CountDownLatch writeStall = new CountDownLatch(1);
+    private final CountDownLatch initialWriteStall = new CountDownLatch(1);
+
+    /*
+     * This one works similarly by providing means to ensure a read from the
+     * channel will stall at least once (no more data available on the socket).
+     */
+    private final CountDownLatch readStall = new CountDownLatch(1);
+    private final CountDownLatch initialReadStall = new CountDownLatch(1);
+
+    private final AtomicInteger writeHandles = new AtomicInteger();
+    private final AtomicInteger readHandles = new AtomicInteger();
+
+    private final CountDownLatch exit = new CountDownLatch(1);
+
+    @Test
+    public void test() throws Exception {
+        try (ServerSocket server = new ServerSocket(0)) {
+            int port = server.getLocalPort();
+            new TestServer(server).start();
+
+            final RawChannel chan = channelOf(port);
+            print("RawChannel is %s", String.valueOf(chan));
+            initialWriteStall.await();
+
+            // It's very important not to forget the initial bytes, possibly
+            // left from the HTTP thingy
+            int initialBytes = chan.initialByteBuffer().remaining();
+            print("RawChannel has %s initial bytes", initialBytes);
+            clientRead.addAndGet(initialBytes);
+
+            // tell the server we have read the initial bytes, so
+            // that it makes sure there is something for us to
+            // read next in case the initialBytes have already drained the
+            // channel dry.
+            initialReadStall.countDown();
+
+            chan.registerEvent(new RawChannel.RawEvent() {
+
+                private final ByteBuffer reusableBuffer = ByteBuffer.allocate(32768);
+
+                @Override
+                public int interestOps() {
+                    return SelectionKey.OP_WRITE;
+                }
+
+                @Override
+                public void handle() {
+                    int i = writeHandles.incrementAndGet();
+                    print("OP_WRITE #%s", i);
+                    if (i > 3) { // Fill up the send buffer not more than 3 times
+                        try {
+                            chan.shutdownOutput();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                        return;
+                    }
+                    long total = 0;
+                    try {
+                        long n;
+                        do {
+                            ByteBuffer[] array = {reusableBuffer.slice()};
+                            n = chan.write(array, 0, 1);
+                            total += n;
+                        } while (n > 0);
+                        print("OP_WRITE clogged SNDBUF with %s bytes", total);
+                        clientWritten.addAndGet(total);
+                        chan.registerEvent(this);
+                        writeStall.countDown(); // signal send buffer is full
+                    } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                    }
+                }
+            });
+
+            chan.registerEvent(new RawChannel.RawEvent() {
+
+                @Override
+                public int interestOps() {
+                    return SelectionKey.OP_READ;
+                }
+
+                @Override
+                public void handle() {
+                    int i = readHandles.incrementAndGet();
+                    print("OP_READ #%s", i);
+                    ByteBuffer read = null;
+                    long total = 0;
+                    while (true) {
+                        try {
+                            read = chan.read();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                        if (read == null) {
+                            print("OP_READ EOF");
+                            break;
+                        } else if (!read.hasRemaining()) {
+                            print("OP_READ stall");
+                            try {
+                                chan.registerEvent(this);
+                            } catch (IOException e) {
+                                e.printStackTrace();
+                            }
+                            readStall.countDown();
+                            break;
+                        }
+                        int r = read.remaining();
+                        total += r;
+                        clientRead.addAndGet(r);
+                    }
+                    print("OP_READ read %s bytes (%s total)", total, clientRead.get());
+                }
+            });
+            exit.await(); // All done, we need to compare results:
+            assertEquals(clientRead.get(), serverWritten.get());
+            assertEquals(serverRead.get(), clientWritten.get());
+        }
+    }
+
+    private static RawChannel channelOf(int port) throws Exception {
+        URI uri = URI.create("http://127.0.0.1:" + port + "/");
+        print("raw channel to %s", uri.toString());
+        HttpRequest req = HttpRequest.newBuilder(uri).build();
+        // Switch on isWebSocket flag to prevent the connection from
+        // being returned to the pool.
+        ((WebSocketRequest)req).isWebSocket(true);
+        HttpClient client = HttpClient.newHttpClient();
+        try {
+            HttpResponse<?> r = client.send(req, discard(null));
+            r.body();
+            return ((HttpResponseImpl) r).rawChannel();
+        } finally {
+           // Need to hold onto the client until the RawChannel is
+           // created. This would not be needed if we had created
+           // a WebSocket, but here we are fiddling directly
+           // with the internals of HttpResponseImpl!
+           java.lang.ref.Reference.reachabilityFence(client);
+        }
+    }
+
+    private class TestServer extends Thread { // Powered by Slowpokes
+
+        private final ServerSocket server;
+
+        TestServer(ServerSocket server) throws IOException {
+            this.server = server;
+        }
+
+        @Override
+        public void run() {
+            try (Socket s = server.accept()) {
+                InputStream is = s.getInputStream();
+                OutputStream os = s.getOutputStream();
+
+                processHttp(is, os);
+
+                Thread reader = new Thread(() -> {
+                    try {
+                        long n = readSlowly(is);
+                        print("Server read %s bytes", n);
+                        serverRead.addAndGet(n);
+                        s.shutdownInput();
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                });
+
+                Thread writer = new Thread(() -> {
+                    try {
+                        long n = writeSlowly(os);
+                        print("Server written %s bytes", n);
+                        serverWritten.addAndGet(n);
+                        s.shutdownOutput();
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                });
+
+                reader.start();
+                writer.start();
+
+                reader.join();
+                writer.join();
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                exit.countDown();
+            }
+        }
+
+        private void processHttp(InputStream is, OutputStream os)
+                throws IOException
+        {
+            os.write("HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n".getBytes());
+
+            // write some initial bytes
+            byte[] initial = byteArrayOfSize(1024);
+            os.write(initial);
+            os.flush();
+            serverWritten.addAndGet(initial.length);
+            initialWriteStall.countDown();
+
+            byte[] buf = new byte[1024];
+            String s = "";
+            while (true) {
+                int n = is.read(buf);
+                if (n <= 0) {
+                    throw new RuntimeException("Unexpected end of request");
+                }
+                s = s + new String(buf, 0, n);
+                if (s.contains("\r\n\r\n")) {
+                    break;
+                }
+            }
+        }
+
+        private long writeSlowly(OutputStream os) throws Exception {
+            byte[] first = byteArrayOfSize(1024);
+            long total = first.length;
+            os.write(first);
+            os.flush();
+
+            // wait until initial bytes were read
+            initialReadStall.await();
+
+            // make sure there is something to read, otherwise readStall
+            // will never be counted down.
+            first = byteArrayOfSize(1024);
+            os.write(first);
+            os.flush();
+            total += first.length;
+
+            // Let's wait for the signal from the raw channel that its read has
+            // stalled, and then continue sending a bit more stuff
+            readStall.await();
+            for (int i = 0; i < 32; i++) {
+                byte[] b = byteArrayOfSize(1024);
+                os.write(b);
+                os.flush();
+                total += b.length;
+                TimeUnit.MILLISECONDS.sleep(1);
+            }
+            return total;
+        }
+
+        private long readSlowly(InputStream is) throws Exception {
+            // Wait for the raw channel to fill up its send buffer
+            writeStall.await();
+            long overall = 0;
+            byte[] array = new byte[1024];
+            for (int n = 0; n != -1; n = is.read(array)) {
+                TimeUnit.MILLISECONDS.sleep(1);
+                overall += n;
+            }
+            return overall;
+        }
+    }
+
+    private static void print(String format, Object... args) {
+        System.out.println(Thread.currentThread() + ": " + String.format(format, args));
+    }
+
+    private static byte[] byteArrayOfSize(int bound) {
+        return new byte[new Random().nextInt(1 + bound)];
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/SelectorTest.java	Tue Feb 06 14:10:28 2018 +0000
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal;
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import org.testng.annotations.Test;
+import jdk.incubator.http.internal.websocket.RawChannel;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
+
+/**
+ * Whitebox test of selector mechanics. Currently only a simple test
+ * setting one read and one write event is done. It checks that the
+ * write event occurs first, followed by the read event and then no
+ * further events occur despite the conditions actually still existing.
+ */
+@Test
+public class SelectorTest {
+
+    AtomicInteger counter = new AtomicInteger();
+    volatile boolean error;
+    static final CountDownLatch finishingGate = new CountDownLatch(1);
+    static volatile HttpClient staticDefaultClient;
+
+    static HttpClient defaultClient() {
+        if (staticDefaultClient == null) {
+            synchronized (SelectorTest.class) {
+                staticDefaultClient = HttpClient.newHttpClient();
+            }
+        }
+        return staticDefaultClient;
+    }
+
+    String readSomeBytes(RawChannel chan) {
+        try {
+            ByteBuffer buf = chan.read();
+            if (buf == null) {
+                out.println("chan read returned null");
+                return null;
+            }
+            buf.flip();
+            byte[] bb = new byte[buf.remaining()];
+            buf.get(bb);
+            return new String(bb, US_ASCII);
+        } catch (IOException ioe) {
+            throw new UncheckedIOException(ioe);
+        }
+    }
+
+    @Test
+    public void test() throws Exception {
+
+        try (ServerSocket server = new ServerSocket(0)) {
+            int port = server.getLocalPort();
+
+            out.println("Listening on port " + server.getLocalPort());
+
+            TestServer t = new TestServer(server);
+            t.start();
+            out.println("Started server thread");
+
+            try (RawChannel chan = getARawChannel(port)) {
+
+                chan.registerEvent(new RawChannel.RawEvent() {
+                    @Override
+                    public int interestOps() {
+                        return SelectionKey.OP_READ;
+                    }
+
+                    @Override
+                    public void handle() {
+                        readSomeBytes(chan);
+                        out.printf("OP_READ\n");
+                        final int count = counter.get();
+                        if (count != 1) {
+                            out.printf("OP_READ error counter = %d\n", count);
+                            error = true;
+                        }
+                    }
+                });
+
+                chan.registerEvent(new RawChannel.RawEvent() {
+                    @Override
+                    public int interestOps() {
+                        return SelectionKey.OP_WRITE;
+                    }
+
+                    @Override
+                    public void handle() {
+                        out.printf("OP_WRITE\n");
+                        final int count = counter.get();
+                        if (count != 0) {
+                            out.printf("OP_WRITE error counter = %d\n", count);
+                            error = true;
+                        } else {
+                            ByteBuffer bb = ByteBuffer.wrap(TestServer.INPUT);
+                            counter.incrementAndGet();
+                            try {
+                                chan.write(new ByteBuffer[]{bb}, 0, 1);
+                            } catch (IOException e) {
+                                throw new UncheckedIOException(e);
+                            }
+                        }
+                    }
+
+                });
+                out.println("Events registered. Waiting");
+                finishingGate.await(30, SECONDS);
+                if (error)
+                    throw new RuntimeException("Error");
+                else
+                    out.println("No error");
+            }
+        }
+    }
+
+    static RawChannel getARawChannel(int port) throws Exception {
+        URI uri = URI.create("http://127.0.0.1:" + port + "/");
+        out.println("client connecting to " + uri.toString());
+        HttpRequest req = HttpRequest.newBuilder(uri).build();
+        // Otherwise HttpClient will think this is an ordinary connection and
+        // thus all ordinary procedures apply to it, e.g. it must be put into
+        // the cache
+        ((HttpRequestImpl) req).isWebSocket(true);
+        HttpResponse<?> r = defaultClient().send(req, discard(null));
+        r.body();
+        return ((HttpResponseImpl) r).rawChannel();
+    }
+
+    static class TestServer extends Thread {
+        static final byte[] INPUT = "Hello world".getBytes(US_ASCII);
+        static final byte[] OUTPUT = "Goodbye world".getBytes(US_ASCII);
+        static final String FIRST_RESPONSE = "HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n";
+        final ServerSocket server;
+
+        TestServer(ServerSocket server) throws IOException {
+            this.server = server;
+        }
+
+        public void run() {
+            try (Socket s = server.accept();
+                 InputStream is = s.getInputStream();
+                 OutputStream os = s.getOutputStream()) {
+
+                out.println("Got connection");
+                readRequest(is);
+                os.write(FIRST_RESPONSE.getBytes());
+                read(is);
+                write(os);
+                Thread.sleep(1000);
+                // send some more data, and make sure WRITE op does not get called
+                write(os);
+                out.println("TestServer exiting");
+                SelectorTest.finishingGate.countDown();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        // consumes the HTTP request
+        static void readRequest(InputStream is) throws IOException {
+            out.println("starting readRequest");
+            byte[] buf = new byte[1024];
+            String s = "";
+            while (true) {
+                int n = is.read(buf);
+                if (n <= 0)
+                    throw new IOException("Error");
+                s = s + new String(buf, 0, n);
+                if (s.indexOf("\r\n\r\n") != -1)
+                    break;
+            }
+            out.println("returning from readRequest");
+        }
+
+        static void read(InputStream is) throws IOException {
+            out.println("starting read");
+            for (int i = 0; i < INPUT.length; i++) {
+                int c = is.read();
+                if (c == -1)
+                    throw new IOException("closed");
+                if (INPUT[i] != (byte) c)
+                    throw new IOException("Error. Expected:" + INPUT[i] + ", got:" + c);
+            }
+            out.println("returning from read");
+        }
+
+        static void write(OutputStream os) throws IOException {
+            out.println("doing write");
+            os.write(OUTPUT);
+        }
+    }
+}