http-client-branch: move implementation to jdk.internal.net.http http-client-branch
authorchegar
Wed, 07 Feb 2018 21:45:37 +0000
branchhttp-client-branch
changeset 56092 fd85b2bf2b0d
parent 56091 aedd6133e7a0
child 56093 22d94c4a3641
http-client-branch: move implementation to jdk.internal.net.http
src/java.net.http/share/classes/java/net/http/HttpClient.java
src/java.net.http/share/classes/java/net/http/HttpRequest.java
src/java.net.http/share/classes/java/net/http/HttpResponse.java
src/java.net.http/share/classes/java/net/http/internal/AbstractAsyncSSLConnection.java
src/java.net.http/share/classes/java/net/http/internal/AbstractSubscription.java
src/java.net.http/share/classes/java/net/http/internal/AsyncEvent.java
src/java.net.http/share/classes/java/net/http/internal/AsyncSSLConnection.java
src/java.net.http/share/classes/java/net/http/internal/AsyncSSLTunnelConnection.java
src/java.net.http/share/classes/java/net/http/internal/AsyncTriggerEvent.java
src/java.net.http/share/classes/java/net/http/internal/AuthenticationFilter.java
src/java.net.http/share/classes/java/net/http/internal/BufferingSubscriber.java
src/java.net.http/share/classes/java/net/http/internal/ConnectionPool.java
src/java.net.http/share/classes/java/net/http/internal/CookieFilter.java
src/java.net.http/share/classes/java/net/http/internal/Exchange.java
src/java.net.http/share/classes/java/net/http/internal/ExchangeImpl.java
src/java.net.http/share/classes/java/net/http/internal/FilterFactory.java
src/java.net.http/share/classes/java/net/http/internal/HeaderFilter.java
src/java.net.http/share/classes/java/net/http/internal/HeaderParser.java
src/java.net.http/share/classes/java/net/http/internal/Http1AsyncReceiver.java
src/java.net.http/share/classes/java/net/http/internal/Http1Exchange.java
src/java.net.http/share/classes/java/net/http/internal/Http1HeaderParser.java
src/java.net.http/share/classes/java/net/http/internal/Http1Request.java
src/java.net.http/share/classes/java/net/http/internal/Http1Response.java
src/java.net.http/share/classes/java/net/http/internal/Http2ClientImpl.java
src/java.net.http/share/classes/java/net/http/internal/Http2Connection.java
src/java.net.http/share/classes/java/net/http/internal/HttpClientBuilderImpl.java
src/java.net.http/share/classes/java/net/http/internal/HttpClientFacade.java
src/java.net.http/share/classes/java/net/http/internal/HttpClientImpl.java
src/java.net.http/share/classes/java/net/http/internal/HttpConnection.java
src/java.net.http/share/classes/java/net/http/internal/HttpRequestBuilderImpl.java
src/java.net.http/share/classes/java/net/http/internal/HttpRequestImpl.java
src/java.net.http/share/classes/java/net/http/internal/HttpResponseImpl.java
src/java.net.http/share/classes/java/net/http/internal/ImmutableHeaders.java
src/java.net.http/share/classes/java/net/http/internal/LineSubscriberAdapter.java
src/java.net.http/share/classes/java/net/http/internal/MultiExchange.java
src/java.net.http/share/classes/java/net/http/internal/PlainHttpConnection.java
src/java.net.http/share/classes/java/net/http/internal/PlainProxyConnection.java
src/java.net.http/share/classes/java/net/http/internal/PlainTunnelingConnection.java
src/java.net.http/share/classes/java/net/http/internal/PrivilegedExecutor.java
src/java.net.http/share/classes/java/net/http/internal/ProxyAuthenticationRequired.java
src/java.net.http/share/classes/java/net/http/internal/PullPublisher.java
src/java.net.http/share/classes/java/net/http/internal/PushGroup.java
src/java.net.http/share/classes/java/net/http/internal/RawChannelImpl.java
src/java.net.http/share/classes/java/net/http/internal/RedirectFilter.java
src/java.net.http/share/classes/java/net/http/internal/RequestPublishers.java
src/java.net.http/share/classes/java/net/http/internal/Response.java
src/java.net.http/share/classes/java/net/http/internal/ResponseBodyHandlers.java
src/java.net.http/share/classes/java/net/http/internal/ResponseContent.java
src/java.net.http/share/classes/java/net/http/internal/ResponseSubscribers.java
src/java.net.http/share/classes/java/net/http/internal/SSLDelegate.java
src/java.net.http/share/classes/java/net/http/internal/SocketTube.java
src/java.net.http/share/classes/java/net/http/internal/Stream.java
src/java.net.http/share/classes/java/net/http/internal/TimeoutEvent.java
src/java.net.http/share/classes/java/net/http/internal/UntrustedBodyHandler.java
src/java.net.http/share/classes/java/net/http/internal/WindowController.java
src/java.net.http/share/classes/java/net/http/internal/WindowUpdateSender.java
src/java.net.http/share/classes/java/net/http/internal/common/ByteBufferPool.java
src/java.net.http/share/classes/java/net/http/internal/common/ByteBufferReference.java
src/java.net.http/share/classes/java/net/http/internal/common/ConnectionExpiredException.java
src/java.net.http/share/classes/java/net/http/internal/common/DebugLogger.java
src/java.net.http/share/classes/java/net/http/internal/common/Demand.java
src/java.net.http/share/classes/java/net/http/internal/common/FlowTube.java
src/java.net.http/share/classes/java/net/http/internal/common/HttpHeadersImpl.java
src/java.net.http/share/classes/java/net/http/internal/common/Log.java
src/java.net.http/share/classes/java/net/http/internal/common/MinimalFuture.java
src/java.net.http/share/classes/java/net/http/internal/common/Pair.java
src/java.net.http/share/classes/java/net/http/internal/common/SSLFlowDelegate.java
src/java.net.http/share/classes/java/net/http/internal/common/SSLTube.java
src/java.net.http/share/classes/java/net/http/internal/common/SequentialScheduler.java
src/java.net.http/share/classes/java/net/http/internal/common/SubscriberWrapper.java
src/java.net.http/share/classes/java/net/http/internal/common/SubscriptionBase.java
src/java.net.http/share/classes/java/net/http/internal/common/Utils.java
src/java.net.http/share/classes/java/net/http/internal/frame/ContinuationFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/DataFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/ErrorFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/FramesDecoder.java
src/java.net.http/share/classes/java/net/http/internal/frame/FramesEncoder.java
src/java.net.http/share/classes/java/net/http/internal/frame/GoAwayFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/HeaderFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/HeadersFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/Http2Frame.java
src/java.net.http/share/classes/java/net/http/internal/frame/MalformedFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/OutgoingHeaders.java
src/java.net.http/share/classes/java/net/http/internal/frame/PingFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/PriorityFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/PushPromiseFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/ResetFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/SettingsFrame.java
src/java.net.http/share/classes/java/net/http/internal/frame/WindowUpdateFrame.java
src/java.net.http/share/classes/java/net/http/internal/hpack/BinaryRepresentationWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/BulkSizeUpdateWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/Decoder.java
src/java.net.http/share/classes/java/net/http/internal/hpack/DecodingCallback.java
src/java.net.http/share/classes/java/net/http/internal/hpack/Encoder.java
src/java.net.http/share/classes/java/net/http/internal/hpack/HPACK.java
src/java.net.http/share/classes/java/net/http/internal/hpack/HeaderTable.java
src/java.net.http/share/classes/java/net/http/internal/hpack/Huffman.java
src/java.net.http/share/classes/java/net/http/internal/hpack/ISO_8859_1.java
src/java.net.http/share/classes/java/net/http/internal/hpack/IndexNameValueWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/IndexedWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/IntegerReader.java
src/java.net.http/share/classes/java/net/http/internal/hpack/IntegerWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/LiteralNeverIndexedWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/LiteralWithIndexingWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/LiteralWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/SizeUpdateWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/StringReader.java
src/java.net.http/share/classes/java/net/http/internal/hpack/StringWriter.java
src/java.net.http/share/classes/java/net/http/internal/hpack/package-info.java
src/java.net.http/share/classes/java/net/http/internal/websocket/BuilderImpl.java
src/java.net.http/share/classes/java/net/http/internal/websocket/CheckFailedException.java
src/java.net.http/share/classes/java/net/http/internal/websocket/FailWebSocketException.java
src/java.net.http/share/classes/java/net/http/internal/websocket/Frame.java
src/java.net.http/share/classes/java/net/http/internal/websocket/FrameConsumer.java
src/java.net.http/share/classes/java/net/http/internal/websocket/MessageStreamConsumer.java
src/java.net.http/share/classes/java/net/http/internal/websocket/OpeningHandshake.java
src/java.net.http/share/classes/java/net/http/internal/websocket/OutgoingMessage.java
src/java.net.http/share/classes/java/net/http/internal/websocket/RawChannel.java
src/java.net.http/share/classes/java/net/http/internal/websocket/StatusCodes.java
src/java.net.http/share/classes/java/net/http/internal/websocket/Transport.java
src/java.net.http/share/classes/java/net/http/internal/websocket/TransportFactory.java
src/java.net.http/share/classes/java/net/http/internal/websocket/TransportFactoryImpl.java
src/java.net.http/share/classes/java/net/http/internal/websocket/TransportImpl.java
src/java.net.http/share/classes/java/net/http/internal/websocket/UTF8AccumulatingDecoder.java
src/java.net.http/share/classes/java/net/http/internal/websocket/WebSocketImpl.java
src/java.net.http/share/classes/java/net/http/internal/websocket/WebSocketRequest.java
src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/AbstractSubscription.java
src/java.net.http/share/classes/jdk/internal/net/http/AsyncEvent.java
src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/AsyncTriggerEvent.java
src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java
src/java.net.http/share/classes/jdk/internal/net/http/BufferingSubscriber.java
src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java
src/java.net.http/share/classes/jdk/internal/net/http/CookieFilter.java
src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/ExchangeImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/FilterFactory.java
src/java.net.http/share/classes/jdk/internal/net/http/HeaderFilter.java
src/java.net.http/share/classes/jdk/internal/net/http/HeaderParser.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java
src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpClientBuilderImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpClientFacade.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpResponseImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/ImmutableHeaders.java
src/java.net.http/share/classes/jdk/internal/net/http/LineSubscriberAdapter.java
src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java
src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/PlainProxyConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/PlainTunnelingConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/PrivilegedExecutor.java
src/java.net.http/share/classes/jdk/internal/net/http/ProxyAuthenticationRequired.java
src/java.net.http/share/classes/jdk/internal/net/http/PullPublisher.java
src/java.net.http/share/classes/jdk/internal/net/http/PushGroup.java
src/java.net.http/share/classes/jdk/internal/net/http/RawChannelImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java
src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java
src/java.net.http/share/classes/jdk/internal/net/http/Response.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java
src/java.net.http/share/classes/jdk/internal/net/http/SSLDelegate.java
src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java
src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
src/java.net.http/share/classes/jdk/internal/net/http/TimeoutEvent.java
src/java.net.http/share/classes/jdk/internal/net/http/UntrustedBodyHandler.java
src/java.net.http/share/classes/jdk/internal/net/http/WindowController.java
src/java.net.http/share/classes/jdk/internal/net/http/WindowUpdateSender.java
src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferPool.java
src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferReference.java
src/java.net.http/share/classes/jdk/internal/net/http/common/ConnectionExpiredException.java
src/java.net.http/share/classes/jdk/internal/net/http/common/DebugLogger.java
src/java.net.http/share/classes/jdk/internal/net/http/common/Demand.java
src/java.net.http/share/classes/jdk/internal/net/http/common/FlowTube.java
src/java.net.http/share/classes/jdk/internal/net/http/common/HttpHeadersImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/common/Log.java
src/java.net.http/share/classes/jdk/internal/net/http/common/MinimalFuture.java
src/java.net.http/share/classes/jdk/internal/net/http/common/Pair.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SequentialScheduler.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriberWrapper.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriptionBase.java
src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/ContinuationFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/DataFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/ErrorFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/FramesDecoder.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/FramesEncoder.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/GoAwayFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/HeaderFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/HeadersFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/Http2Frame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/MalformedFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/OutgoingHeaders.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/PingFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/PriorityFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/PushPromiseFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/ResetFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/SettingsFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/frame/WindowUpdateFrame.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/BinaryRepresentationWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/BulkSizeUpdateWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/Decoder.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/DecodingCallback.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/Encoder.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/HPACK.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/HeaderTable.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/Huffman.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/ISO_8859_1.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/IndexNameValueWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/IndexedWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/IntegerReader.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/IntegerWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/LiteralNeverIndexedWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/LiteralWithIndexingWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/LiteralWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/SizeUpdateWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringReader.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringWriter.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/package-info.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/BuilderImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/CheckFailedException.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/FailWebSocketException.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/Frame.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/FrameConsumer.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageStreamConsumer.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/OutgoingMessage.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/RawChannel.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/StatusCodes.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/Transport.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportFactory.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportFactoryImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/UTF8AccumulatingDecoder.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketRequest.java
test/jdk/java/net/httpclient/ConcurrentResponses.java
test/jdk/java/net/httpclient/CustomRequestPublisher.java
test/jdk/java/net/httpclient/CustomResponseSubscriber.java
test/jdk/java/net/httpclient/DigestEchoClient.java
test/jdk/java/net/httpclient/DigestEchoClientSSL.java
test/jdk/java/net/httpclient/FlowAdapterPublisherTest.java
test/jdk/java/net/httpclient/FlowAdapterSubscriberTest.java
test/jdk/java/net/httpclient/HttpServerAdapters.java
test/jdk/java/net/httpclient/ImmutableFlowItems.java
test/jdk/java/net/httpclient/LineBodyHandlerTest.java
test/jdk/java/net/httpclient/MappedResponseSubscriber.java
test/jdk/java/net/httpclient/NoBodyPartOne.java
test/jdk/java/net/httpclient/NoBodyPartTwo.java
test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java
test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.java
test/jdk/java/net/httpclient/SmallTimeout.java
test/jdk/java/net/httpclient/http2/BasicTest.java
test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java
test/jdk/java/net/httpclient/http2/ErrorTest.java
test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java
test/jdk/java/net/httpclient/http2/HpackBinaryTestDriver.java
test/jdk/java/net/httpclient/http2/HpackCircularBufferDriver.java
test/jdk/java/net/httpclient/http2/HpackDecoderDriver.java
test/jdk/java/net/httpclient/http2/HpackEncoderDriver.java
test/jdk/java/net/httpclient/http2/HpackHeaderTableDriver.java
test/jdk/java/net/httpclient/http2/HpackHuffmanDriver.java
test/jdk/java/net/httpclient/http2/HpackTestHelper.java
test/jdk/java/net/httpclient/http2/ImplicitPushCancel.java
test/jdk/java/net/httpclient/http2/ProxyTest2.java
test/jdk/java/net/httpclient/http2/RedirectTest.java
test/jdk/java/net/httpclient/http2/ServerPush.java
test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java
test/jdk/java/net/httpclient/http2/TLSConnection.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/BinaryPrimitivesTest.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/BuffersTestingKit.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/CircularBufferTest.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/DecoderTest.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/EncoderTest.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/HeaderTableTest.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/HuffmanTest.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/SpecHelper.java
test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/TestHelper.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/BinaryPrimitivesTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/BuffersTestingKit.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/CircularBufferTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/DecoderTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/EncoderTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HeaderTableTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HuffmanTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/SpecHelper.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/TestHelper.java
test/jdk/java/net/httpclient/http2/server/BodyInputStream.java
test/jdk/java/net/httpclient/http2/server/BodyOutputStream.java
test/jdk/java/net/httpclient/http2/server/EchoHandler.java
test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java
test/jdk/java/net/httpclient/http2/server/Http2RedirectHandler.java
test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java
test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java
test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java
test/jdk/java/net/httpclient/http2/server/Http2TestServer.java
test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java
test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java
test/jdk/java/net/httpclient/http2/server/PushHandler.java
test/jdk/java/net/httpclient/websocket/BuildingWebSocketDriver.java
test/jdk/java/net/httpclient/websocket/HeaderWriterDriver.java
test/jdk/java/net/httpclient/websocket/MaskerDriver.java
test/jdk/java/net/httpclient/websocket/ReaderDriver.java
test/jdk/java/net/httpclient/websocket/WebSocketImplDriver.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/BuildingWebSocketTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/HeaderWriterTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/MaskerTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/MockListener.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/MockTransport.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/ReaderTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/TestSupport.java
test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/WebSocketImplTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/BuildingWebSocketTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/HeaderWriterTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/MaskerTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/MockListener.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/MockTransport.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/ReaderTest.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/TestSupport.java
test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/WebSocketImplTest.java
test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java
test/jdk/java/net/httpclient/whitebox/DemandTestDriver.java
test/jdk/java/net/httpclient/whitebox/Driver.java
test/jdk/java/net/httpclient/whitebox/FlowTestDriver.java
test/jdk/java/net/httpclient/whitebox/FramesDecoderTestDriver.java
test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java
test/jdk/java/net/httpclient/whitebox/MinimalFutureTestDriver.java
test/jdk/java/net/httpclient/whitebox/SSLEchoTubeTestDriver.java
test/jdk/java/net/httpclient/whitebox/SSLTubeTestDriver.java
test/jdk/java/net/httpclient/whitebox/WrapperTestDriver.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/AbstractRandomTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/AbstractSSLTubeTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/FlowTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/SSLEchoTubeTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/SSLTubeTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/WrapperTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/ConnectionPoolTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/Http1HeaderParserTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/RawChannelTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/SelectorTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/common/DemandTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/common/MinimalFutureTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/frame/FramesDecoderTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/AbstractRandomTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/AbstractSSLTubeTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/ConnectionPoolTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/FlowTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/Http1HeaderParserTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/RawChannelTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLTubeTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SelectorTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/WrapperTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/DemandTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/MinimalFutureTest.java
test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/frame/FramesDecoderTest.java
--- a/src/java.net.http/share/classes/java/net/http/HttpClient.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpClient.java	Wed Feb 07 21:45:37 2018 +0000
@@ -40,7 +40,7 @@
 import javax.net.ssl.SSLParameters;
 import java.net.http.HttpResponse.BodyHandler;
 import java.net.http.HttpResponse.PushPromiseHandler;
-import java.net.http.internal.HttpClientBuilderImpl;
+import jdk.internal.net.http.HttpClientBuilderImpl;
 
 /**
  * An HTTP Client.
--- a/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -45,8 +45,8 @@
 import java.util.concurrent.Flow;
 import java.util.function.Supplier;
 import java.net.http.HttpResponse.BodyHandler;
-import java.net.http.internal.HttpRequestBuilderImpl;
-import java.net.http.internal.RequestPublishers;
+import jdk.internal.net.http.HttpRequestBuilderImpl;
+import jdk.internal.net.http.RequestPublishers;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
--- a/src/java.net.http/share/classes/java/net/http/HttpResponse.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpResponse.java	Wed Feb 07 21:45:37 2018 +0000
@@ -48,13 +48,13 @@
 import java.util.function.Function;
 import java.util.stream.Stream;
 import javax.net.ssl.SSLParameters;
-import java.net.http.internal.BufferingSubscriber;
-import java.net.http.internal.LineSubscriberAdapter;
-import java.net.http.internal.ResponseBodyHandlers.FileDownloadBodyHandler;
-import java.net.http.internal.ResponseBodyHandlers.PathBodyHandler;
-import java.net.http.internal.ResponseBodyHandlers.PushPromisesHandlerWithMap;
-import java.net.http.internal.ResponseSubscribers;
-import static java.net.http.internal.common.Utils.charsetFrom;
+import jdk.internal.net.http.BufferingSubscriber;
+import jdk.internal.net.http.LineSubscriberAdapter;
+import jdk.internal.net.http.ResponseBodyHandlers.FileDownloadBodyHandler;
+import jdk.internal.net.http.ResponseBodyHandlers.PathBodyHandler;
+import jdk.internal.net.http.ResponseBodyHandlers.PushPromisesHandlerWithMap;
+import jdk.internal.net.http.ResponseSubscribers;
+import static jdk.internal.net.http.common.Utils.charsetFrom;
 
 /**
  * Represents a response to a {@link HttpRequest}.
--- a/src/java.net.http/share/classes/java/net/http/internal/AbstractAsyncSSLConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +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 java.net.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 java.net.http.internal.common.SSLTube;
-import java.net.http.internal.common.Log;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/AbstractSubscription.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +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 java.net.http.internal;
-
-import java.util.concurrent.Flow;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/AsyncEvent.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +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 java.net.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;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/AsyncSSLConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +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 java.net.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 java.net.http.internal.common.SSLTube;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/AsyncSSLTunnelConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +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 java.net.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 java.net.http.HttpHeaders;
-import java.net.http.internal.common.SSLTube;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/AsyncTriggerEvent.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +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 java.net.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; }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/AuthenticationFilter.java	Wed Feb 07 15:46:30 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 java.net.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 java.net.http.HttpHeaders;
-import java.net.http.internal.common.Log;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/BufferingSubscriber.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,315 +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 java.net.http.internal;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Objects;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.net.http.HttpResponse.BodySubscriber;
-import java.net.http.internal.common.Demand;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.Utils;
-
-/**
- * A buffering BodySubscriber. When subscribed, accumulates ( buffers ) a given
- * amount ( in bytes ) of a publisher's data before pushing it to a downstream
- * subscriber.
- */
-public class BufferingSubscriber<T> implements BodySubscriber<T>
-{
-    /** The downstream consumer of the data. */
-    private final BodySubscriber<T> downstreamSubscriber;
-    /** The amount of data to be accumulate before pushing downstream. */
-    private final int bufferSize;
-
-    /** The subscription, created lazily. */
-    private volatile Flow.Subscription subscription;
-    /** The downstream subscription, created lazily. */
-    private volatile DownstreamSubscription downstreamSubscription;
-
-    /** Must be held when accessing the internal buffers. */
-    private final Object buffersLock = new Object();
-    /** The internal buffers holding the buffered data. */
-    private ArrayList<ByteBuffer> internalBuffers;
-    /** The actual accumulated remaining bytes in internalBuffers. */
-    private int accumulatedBytes;
-
-    /** Holds the Throwable from upstream's onError. */
-    private volatile Throwable throwable;
-
-    /** State of the buffering subscriber:
-     *  1) [UNSUBSCRIBED] when initially created
-     *  2) [ACTIVE] when subscribed and can receive data
-     *  3) [ERROR | CANCELLED | COMPLETE] (terminal state)
-     */
-    static final int UNSUBSCRIBED = 0x01;
-    static final int ACTIVE       = 0x02;
-    static final int ERROR        = 0x04;
-    static final int CANCELLED    = 0x08;
-    static final int COMPLETE     = 0x10;
-
-    private volatile int state;
-
-    public BufferingSubscriber(BodySubscriber<T> downstreamSubscriber,
-                               int bufferSize) {
-        this.downstreamSubscriber = Objects.requireNonNull(downstreamSubscriber);
-        this.bufferSize = bufferSize;
-        synchronized (buffersLock) {
-            internalBuffers = new ArrayList<>();
-        }
-        state = UNSUBSCRIBED;
-    }
-
-    /** Returns the number of bytes remaining in the given buffers. */
-    private static final long remaining(List<ByteBuffer> buffers) {
-        return buffers.stream().mapToLong(ByteBuffer::remaining).sum();
-    }
-
-    /**
-     * Tells whether, or not, there is at least a sufficient number of bytes
-     * accumulated in the internal buffers. If the subscriber is COMPLETE, and
-     * has some buffered data, then there is always enough ( to pass downstream ).
-     */
-    private final boolean hasEnoughAccumulatedBytes() {
-        assert Thread.holdsLock(buffersLock);
-        return accumulatedBytes >= bufferSize
-                || (state == COMPLETE && accumulatedBytes > 0);
-    }
-
-    /**
-     * Returns a new, unmodifiable, List<ByteBuffer> containing exactly the
-     * amount of data as required before pushing downstream. The amount of data
-     * may be less than required ( bufferSize ), in the case where the subscriber
-     * is COMPLETE.
-     */
-    private List<ByteBuffer> fromInternalBuffers() {
-        assert Thread.holdsLock(buffersLock);
-        int leftToFill = bufferSize;
-        int state = this.state;
-        assert (state == ACTIVE || state == CANCELLED)
-                ? accumulatedBytes >= leftToFill : true;
-        List<ByteBuffer> dsts = new ArrayList<>();
-
-        ListIterator<ByteBuffer> itr = internalBuffers.listIterator();
-        while (itr.hasNext()) {
-            ByteBuffer b = itr.next();
-            if (b.remaining() <= leftToFill) {
-                itr.remove();
-                if (b.position() != 0)
-                    b = b.slice();  // ensure position = 0 when propagated
-                dsts.add(b);
-                leftToFill -= b.remaining();
-                accumulatedBytes -= b.remaining();
-                if (leftToFill == 0)
-                    break;
-            } else {
-                int prevLimit = b.limit();
-                b.limit(b.position() + leftToFill);
-                ByteBuffer slice = b.slice();
-                dsts.add(slice);
-                b.limit(prevLimit);
-                b.position(b.position() + leftToFill);
-                accumulatedBytes -= leftToFill;
-                leftToFill = 0;
-                break;
-            }
-        }
-        assert (state == ACTIVE || state == CANCELLED)
-                ? leftToFill == 0 : state == COMPLETE;
-        assert (state == ACTIVE || state == CANCELLED)
-                ? remaining(dsts) == bufferSize : state == COMPLETE;
-        assert accumulatedBytes >= 0;
-        assert dsts.stream().noneMatch(b -> b.position() != 0);
-        return Collections.unmodifiableList(dsts);
-    }
-
-    /** Subscription that is passed to the downstream subscriber. */
-    private class DownstreamSubscription implements Flow.Subscription {
-        private final AtomicBoolean cancelled = new AtomicBoolean(); // false
-        private final Demand demand = new Demand();
-        private volatile boolean illegalArg;
-
-        @Override
-        public void request(long n) {
-            if (cancelled.get() || illegalArg) {
-                return;
-            }
-            if (n <= 0L) {
-                // pass the "bad" value upstream so the Publisher can deal with
-                // it appropriately, i.e. invoke onError
-                illegalArg = true;
-                subscription.request(n);
-                return;
-            }
-
-            demand.increase(n);
-
-            pushDemanded();
-        }
-
-        private final SequentialScheduler pushDemandedScheduler =
-                new SequentialScheduler(new PushDemandedTask());
-
-        void pushDemanded() {
-            if (cancelled.get())
-                return;
-            pushDemandedScheduler.runOrSchedule();
-        }
-
-        class PushDemandedTask extends SequentialScheduler.CompleteRestartableTask {
-            @Override
-            public void run() {
-                try {
-                    Throwable t = throwable;
-                    if (t != null) {
-                        pushDemandedScheduler.stop(); // stop the demand scheduler
-                        downstreamSubscriber.onError(t);
-                        return;
-                    }
-
-                    while (true) {
-                        List<ByteBuffer> item;
-                        synchronized (buffersLock) {
-                            if (cancelled.get())
-                                return;
-                            if (!hasEnoughAccumulatedBytes())
-                                break;
-                            if (!demand.tryDecrement())
-                                break;
-                            item = fromInternalBuffers();
-                        }
-                        assert item != null;
-
-                        downstreamSubscriber.onNext(item);
-                    }
-                    if (cancelled.get())
-                        return;
-
-                    // complete only if all data consumed
-                    boolean complete;
-                    synchronized (buffersLock) {
-                        complete = state == COMPLETE && internalBuffers.isEmpty();
-                    }
-                    if (complete) {
-                        assert internalBuffers.isEmpty();
-                        pushDemandedScheduler.stop(); // stop the demand scheduler
-                        downstreamSubscriber.onComplete();
-                        return;
-                    }
-                } catch (Throwable t) {
-                    cancel();  // cancel if there is any error
-                    throw t;
-                }
-
-                boolean requestMore = false;
-                synchronized (buffersLock) {
-                    if (!hasEnoughAccumulatedBytes() && !demand.isFulfilled()) {
-                        // request more upstream data
-                        requestMore = true;
-                    }
-                }
-                if (requestMore)
-                    subscription.request(1);
-            }
-        }
-
-        @Override
-        public void cancel() {
-            if (cancelled.compareAndExchange(false, true))
-                return;  // already cancelled
-
-            state = CANCELLED;  // set CANCELLED state of upstream subscriber
-            subscription.cancel();  // cancel upstream subscription
-            pushDemandedScheduler.stop(); // stop the demand scheduler
-        }
-    }
-
-    @Override
-    public void onSubscribe(Flow.Subscription subscription) {
-        Objects.requireNonNull(subscription);
-        if (this.subscription != null) {
-            subscription.cancel();
-            return;
-        }
-
-        int s = this.state;
-        assert s == UNSUBSCRIBED;
-        state = ACTIVE;
-        this.subscription = subscription;
-        downstreamSubscription = new DownstreamSubscription();
-        downstreamSubscriber.onSubscribe(downstreamSubscription);
-    }
-
-    @Override
-    public void onNext(List<ByteBuffer> item) {
-        Objects.requireNonNull(item);
-
-        int s = state;
-        if (s == CANCELLED)
-            return;
-
-        if (s != ACTIVE)
-            throw new InternalError("onNext on inactive subscriber");
-
-        synchronized (buffersLock) {
-            internalBuffers.addAll(item);
-            accumulatedBytes += remaining(item);
-        }
-
-        downstreamSubscription.pushDemanded();
-    }
-
-    @Override
-    public void onError(Throwable incomingThrowable) {
-        Objects.requireNonNull(incomingThrowable);
-        int s = state;
-        assert s == ACTIVE : "Expected ACTIVE, got:" + s;
-        state = ERROR;
-        Throwable t = this.throwable;
-        assert t == null : "Expected null, got:" + t;
-        this.throwable = incomingThrowable;
-        downstreamSubscription.pushDemanded();
-    }
-
-    @Override
-    public void onComplete() {
-        int s = state;
-        assert s == ACTIVE : "Expected ACTIVE, got:" + s;
-        state = COMPLETE;
-        downstreamSubscription.pushDemanded();
-    }
-
-    @Override
-    public CompletionStage<T> getBody() {
-        return downstreamSubscriber.getBody();
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/ConnectionPool.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,490 +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 java.net.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 java.net.http.internal.common.FlowTube;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/CookieFilter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +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 java.net.http.internal;
-
-import java.io.IOException;
-import java.net.CookieHandler;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.net.http.HttpHeaders;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/Exchange.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,574 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpHeaders;
-import java.net.http.HttpResponse;
-import java.net.http.HttpTimeoutException;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.Utils;
-import java.net.http.internal.common.Log;
-
-import static java.net.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.replace(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/java.net.http/share/classes/java/net/http/internal/ExchangeImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +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 java.net.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 java.net.http.HttpResponse;
-import java.net.http.internal.common.MinimalFuture;
-import static java.net.http.HttpClient.Version.HTTP_1_1;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/FilterFactory.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +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 java.net.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;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/HeaderFilter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +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 java.net.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;
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/HeaderParser.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +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 java.net.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;
-//        }
-//    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/Http1AsyncReceiver.java	Wed Feb 07 15:46:30 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 java.net.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 java.net.http.internal.common.Demand;
-import java.net.http.internal.common.FlowTube.TubeSubscriber;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.ConnectionExpiredException;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/Http1Exchange.java	Wed Feb 07 15:46:30 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 java.net.http.internal;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.http.HttpResponse.BodyHandler;
-import java.net.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 java.net.http.internal.common.Demand;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.Utils;
-import static java.net.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/java.net.http/share/classes/java/net/http/internal/Http1HeaderParser.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,275 +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 java.net.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 java.net.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));
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/Http1Request.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,390 +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 java.net.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 java.net.http.HttpHeaders;
-import java.net.http.HttpRequest;
-import java.net.http.internal.Http1Exchange.Http1BodySubscriber;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.common.Log;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/Http1Response.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,525 +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 java.net.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 java.net.http.HttpHeaders;
-import java.net.http.HttpResponse;
-import java.net.http.internal.ResponseContent.BodyParser;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.Utils;
-import static java.net.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(), 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 CompletableFuture<U> cf = new MinimalFuture<>();
-
-        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);
-                }
-            }
-        });
-        p.getBody().whenComplete((U u, Throwable t) -> {
-            if (t == null)
-                cf.complete(u);
-            else
-                cf.completeExceptionally(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/java.net.http/share/classes/java/net/http/internal/Http2ClientImpl.java	Wed Feb 07 15:46:30 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 java.net.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 java.net.http.internal.common.Log;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.Utils;
-import java.net.http.internal.frame.SettingsFrame;
-import static java.net.http.internal.frame.SettingsFrame.INITIAL_WINDOW_SIZE;
-import static java.net.http.internal.frame.SettingsFrame.ENABLE_PUSH;
-import static java.net.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE;
-import static java.net.http.internal.frame.SettingsFrame.MAX_CONCURRENT_STREAMS;
-import static java.net.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/java.net.http/share/classes/java/net/http/internal/Http2Connection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1290 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpHeaders;
-import java.net.http.internal.HttpConnection.HttpPublisher;
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.FlowTube.TubeSubscriber;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.Utils;
-import java.net.http.internal.frame.ContinuationFrame;
-import java.net.http.internal.frame.DataFrame;
-import java.net.http.internal.frame.ErrorFrame;
-import java.net.http.internal.frame.FramesDecoder;
-import java.net.http.internal.frame.FramesEncoder;
-import java.net.http.internal.frame.GoAwayFrame;
-import java.net.http.internal.frame.HeaderFrame;
-import java.net.http.internal.frame.HeadersFrame;
-import java.net.http.internal.frame.Http2Frame;
-import java.net.http.internal.frame.MalformedFrame;
-import java.net.http.internal.frame.OutgoingHeaders;
-import java.net.http.internal.frame.PingFrame;
-import java.net.http.internal.frame.PushPromiseFrame;
-import java.net.http.internal.frame.ResetFrame;
-import java.net.http.internal.frame.SettingsFrame;
-import java.net.http.internal.frame.WindowUpdateFrame;
-import java.net.http.internal.hpack.Encoder;
-import java.net.http.internal.hpack.Decoder;
-import java.net.http.internal.hpack.DecodingCallback;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.net.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/java.net.http/share/classes/java/net/http/internal/HttpClientBuilderImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +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 java.net.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 java.net.http.HttpClient;
-import java.net.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);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/HttpClientFacade.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodyHandler;
-import java.net.http.HttpResponse.PushPromiseHandler;
-import java.net.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();
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/HttpClientImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1023 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodyHandler;
-import java.net.http.HttpResponse.PushPromiseHandler;
-import java.net.http.WebSocket;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.Pair;
-import java.net.http.internal.common.Utils;
-import java.net.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);
-                if (key != null) {
-                    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
-                try {
-                    chan.register(selector, interestOps, this);
-                } catch (CancelledKeyException x) {
-                    abortPending(x);
-                }
-            }
-        }
-
-        /**
-         * 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/java.net.http/share/classes/java/net/http/internal/HttpConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,493 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpClient.Version;
-import java.net.http.HttpHeaders;
-import java.net.http.internal.common.Demand;
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.SequentialScheduler.DeferredCompleter;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.Utils;
-import static java.net.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/java.net.http/share/classes/java/net/http/internal/HttpRequestBuilderImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,229 +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 java.net.http.internal;
-
-import java.net.URI;
-import java.time.Duration;
-import java.util.Optional;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpRequest.BodyPublisher;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.common.Utils;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static java.net.http.internal.common.Utils.isValidName;
-import static java.net.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; }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/HttpRequestImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,333 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpHeaders;
-import java.net.http.HttpRequest;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.websocket.WebSocketRequest;
-
-import static java.net.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/java.net.http/share/classes/java/net/http/internal/HttpResponseImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,177 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpHeaders;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/ImmutableHeaders.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +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 java.net.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 java.net.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;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/LineSubscriberAdapter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,467 +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 java.net.http.internal;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
-import java.nio.charset.CodingErrorAction;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.concurrent.Flow.Subscription;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Function;
-import java.net.http.internal.common.Demand;
-import java.net.http.HttpResponse.BodySubscriber;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.SequentialScheduler;
-
-/** An adapter between {@code BodySubscriber} and {@code Flow.Subscriber<String>}. */
-public final class LineSubscriberAdapter<S extends Subscriber<? super String>,R>
-        implements BodySubscriber<R> {
-    private final CompletableFuture<R> cf = new MinimalFuture<>();
-    private final S subscriber;
-    private final Function<S, R> finisher;
-    private final Charset charset;
-    private final String eol;
-    private volatile LineSubscription downstream;
-
-    private LineSubscriberAdapter(S subscriber,
-                                  Function<S, R> finisher,
-                                  Charset charset,
-                                  String eol) {
-        if (eol != null && eol.isEmpty())
-            throw new IllegalArgumentException("empty line separator");
-        this.subscriber = Objects.requireNonNull(subscriber);
-        this.finisher = Objects.requireNonNull(finisher);
-        this.charset = Objects.requireNonNull(charset);
-        this.eol = eol;
-    }
-
-    @Override
-    public void onSubscribe(Subscription subscription) {
-        downstream = LineSubscription.create(subscription,
-                                             charset,
-                                             eol,
-                                             subscriber,
-                                             cf);
-        subscriber.onSubscribe(downstream);
-    }
-
-    @Override
-    public void onNext(List<ByteBuffer> item) {
-        try {
-            downstream.submit(item);
-        } catch (Throwable t) {
-            onError(t);
-        }
-    }
-
-    @Override
-    public void onError(Throwable throwable) {
-        try {
-            downstream.signalError(throwable);
-        } finally {
-            cf.completeExceptionally(throwable);
-        }
-    }
-
-    @Override
-    public void onComplete() {
-        try {
-            downstream.signalComplete();
-        } finally {
-            cf.complete(finisher.apply(subscriber));
-        }
-    }
-
-    @Override
-    public CompletionStage<R> getBody() {
-        return cf;
-    }
-
-    public static <S extends Subscriber<? super String>, R> LineSubscriberAdapter<S, R>
-    create(S subscriber, Function<S, R> finisher, Charset charset, String eol)
-    {
-        if (eol != null && eol.isEmpty())
-            throw new IllegalArgumentException("empty line separator");
-        return new LineSubscriberAdapter<>(Objects.requireNonNull(subscriber),
-                Objects.requireNonNull(finisher),
-                Objects.requireNonNull(charset),
-                eol);
-    }
-
-    static final class LineSubscription implements Flow.Subscription {
-        final Flow.Subscription upstreamSubscription;
-        final CharsetDecoder decoder;
-        final String newline;
-        final Demand downstreamDemand;
-        final ConcurrentLinkedDeque<ByteBuffer> queue;
-        final SequentialScheduler scheduler;
-        final Flow.Subscriber<? super String> upstream;
-        final CompletableFuture<?> cf;
-        private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
-        private final AtomicLong demanded = new AtomicLong();
-        private volatile boolean completed;
-        private volatile boolean cancelled;
-
-        private final char[] chars = new char[1024];
-        private final ByteBuffer leftover = ByteBuffer.wrap(new byte[64]);
-        private final CharBuffer buffer = CharBuffer.wrap(chars);
-        private final StringBuilder builder = new StringBuilder();
-        private int lineCount;
-        private String nextLine;
-
-        private LineSubscription(Flow.Subscription s,
-                                 CharsetDecoder dec,
-                                 String separator,
-                                 Flow.Subscriber<? super String> subscriber,
-                                 CompletableFuture<?> completion) {
-            downstreamDemand = new Demand();
-            queue = new ConcurrentLinkedDeque<>();
-            upstreamSubscription = Objects.requireNonNull(s);
-            decoder = Objects.requireNonNull(dec);
-            newline = separator;
-            upstream = Objects.requireNonNull(subscriber);
-            cf = Objects.requireNonNull(completion);
-            scheduler = SequentialScheduler.synchronizedScheduler(this::loop);
-        }
-
-        @Override
-        public void request(long n) {
-            if (cancelled) return;
-            if (downstreamDemand.increase(n)) {
-                scheduler.runOrSchedule();
-            }
-        }
-
-        @Override
-        public void cancel() {
-            cancelled = true;
-            upstreamSubscription.cancel();
-        }
-
-        public void submit(List<ByteBuffer> list) {
-            queue.addAll(list);
-            demanded.decrementAndGet();
-            scheduler.runOrSchedule();
-        }
-
-        public void signalComplete() {
-            completed = true;
-            scheduler.runOrSchedule();
-        }
-
-        public void signalError(Throwable error) {
-            if (errorRef.compareAndSet(null,
-                    Objects.requireNonNull(error))) {
-                scheduler.runOrSchedule();
-            }
-        }
-
-        // This method looks at whether some bytes where left over (in leftover)
-        // from decoding the previous buffer when the previous buffer was in
-        // underflow. If so, it takes bytes one by one from the new buffer 'in'
-        // and combines them with the leftover bytes until 'in' is exhausted or a
-        // character was produced in 'out', resolving the previous underflow.
-        // Returns true if the buffer is still in underflow, false otherwise.
-        // However, in both situation some chars might have been produced in 'out'.
-        private boolean isUnderFlow(ByteBuffer in, CharBuffer out, boolean endOfInput)
-                throws CharacterCodingException {
-            int limit = leftover.position();
-            if (limit == 0) {
-                // no leftover
-                return false;
-            } else {
-                CoderResult res = null;
-                while (in.hasRemaining()) {
-                    leftover.position(limit);
-                    leftover.limit(++limit);
-                    leftover.put(in.get());
-                    leftover.position(0);
-                    res = decoder.decode(leftover, out,
-                            endOfInput && !in.hasRemaining());
-                    int remaining = leftover.remaining();
-                    if (remaining > 0) {
-                        assert leftover.position() == 0;
-                        leftover.position(remaining);
-                    } else {
-                        leftover.position(0);
-                    }
-                    leftover.limit(leftover.capacity());
-                    if (res.isUnderflow() && remaining > 0 && in.hasRemaining()) {
-                        continue;
-                    }
-                    if (res.isError()) {
-                        res.throwException();
-                    }
-                    assert !res.isOverflow();
-                    return false;
-                }
-                return !endOfInput;
-            }
-        }
-
-        // extract characters from start to end and remove them from
-        // the StringBuilder
-        private static String take(StringBuilder b, int start, int end) {
-            assert start == 0;
-            String line;
-            if (end == start) return "";
-            line = b.substring(start, end);
-            b.delete(start, end);
-            return line;
-        }
-
-        // finds end of line, returns -1 if not found, or the position after
-        // the line delimiter if found, removing the delimiter in the process.
-        private static int endOfLine(StringBuilder b, String eol, boolean endOfInput) {
-            int len = b.length();
-            if (eol != null) { // delimiter explicitly specified
-                int i = b.indexOf(eol);
-                if (i >= 0) {
-                    // remove the delimiter and returns the position
-                    // of the char after it.
-                    b.delete(i, i + eol.length());
-                    return i;
-                }
-            } else { // no delimiter specified, behaves as BufferedReader::readLine
-                boolean crfound = false;
-                for (int i = 0; i < len; i++) {
-                    char c = b.charAt(i);
-                    if (c == '\n') {
-                        // '\n' or '\r\n' found.
-                        // remove the delimiter and returns the position
-                        // of the char after it.
-                        b.delete(crfound ? i - 1 : i, i + 1);
-                        return crfound ? i - 1 : i;
-                    } else if (crfound) {
-                        // previous char was '\r', c != '\n'
-                        assert i != 0;
-                        // remove the delimiter and returns the position
-                        // of the char after it.
-                        b.delete(i - 1, i);
-                        return i - 1;
-                    }
-                    crfound = c == '\r';
-                }
-                if (crfound && endOfInput) {
-                    // remove the delimiter and returns the position
-                    // of the char after it.
-                    b.delete(len - 1, len);
-                    return len - 1;
-                }
-            }
-            return endOfInput && len > 0 ? len : -1;
-        }
-
-        // Looks at whether the StringBuilder contains a line.
-        // Returns null if more character are needed.
-        private static String nextLine(StringBuilder b, String eol, boolean endOfInput) {
-            int next = endOfLine(b, eol, endOfInput);
-            return (next > -1) ? take(b, 0, next) : null;
-        }
-
-        // Attempts to read the next line. Returns the next line if
-        // the delimiter was found, null otherwise. The delimiters are
-        // consumed.
-        private String nextLine()
-                throws CharacterCodingException {
-            assert nextLine == null;
-            LINES:
-            while (nextLine == null) {
-                boolean endOfInput = completed && queue.isEmpty();
-                nextLine = nextLine(builder, newline,
-                        endOfInput && leftover.position() == 0);
-                if (nextLine != null) return nextLine;
-                ByteBuffer b;
-                BUFFERS:
-                while ((b = queue.peek()) != null) {
-                    if (!b.hasRemaining()) {
-                        queue.poll();
-                        continue BUFFERS;
-                    }
-                    BYTES:
-                    while (b.hasRemaining()) {
-                        buffer.position(0);
-                        buffer.limit(buffer.capacity());
-                        boolean endofInput = completed && queue.size() <= 1;
-                        if (isUnderFlow(b, buffer, endofInput)) {
-                            assert !b.hasRemaining();
-                            if (buffer.position() > 0) {
-                                buffer.flip();
-                                builder.append(buffer);
-                            }
-                            continue BUFFERS;
-                        }
-                        CoderResult res = decoder.decode(b, buffer, endofInput);
-                        if (res.isError()) res.throwException();
-                        if (buffer.position() > 0) {
-                            buffer.flip();
-                            builder.append(buffer);
-                            continue LINES;
-                        }
-                        if (res.isUnderflow() && b.hasRemaining()) {
-                            //System.out.println("underflow: adding " + b.remaining() + " bytes");
-                            leftover.put(b);
-                            assert !b.hasRemaining();
-                            continue BUFFERS;
-                        }
-                    }
-                }
-
-                assert queue.isEmpty();
-                if (endOfInput) {
-                    // Time to cleanup: there may be some undecoded leftover bytes
-                    // We need to flush them out.
-                    // The decoder has been configured to replace malformed/unmappable
-                    // chars with some replacement, in order to behave like
-                    // InputStreamReader.
-                    leftover.flip();
-                    buffer.position(0);
-                    buffer.limit(buffer.capacity());
-
-                    // decode() must be called just before flush, even if there
-                    // is nothing to decode. We must do this even if leftover
-                    // has no remaining bytes.
-                    CoderResult res = decoder.decode(leftover, buffer, endOfInput);
-                    if (buffer.position() > 0) {
-                        buffer.flip();
-                        builder.append(buffer);
-                    }
-                    if (res.isError()) res.throwException();
-
-                    // Now call decoder.flush()
-                    buffer.position(0);
-                    buffer.limit(buffer.capacity());
-                    res = decoder.flush(buffer);
-                    if (buffer.position() > 0) {
-                        buffer.flip();
-                        builder.append(buffer);
-                    }
-                    if (res.isError()) res.throwException();
-
-                    // It's possible that we reach here twice - just for the
-                    // purpose of checking that no bytes were left over, so
-                    // we reset leftover/decoder to make the function reentrant.
-                    leftover.position(0);
-                    leftover.limit(leftover.capacity());
-                    decoder.reset();
-
-                    // if some chars were produced then this call will
-                    // return them.
-                    return nextLine = nextLine(builder, newline, endOfInput);
-                }
-                return null;
-            }
-            return null;
-        }
-
-        // The main sequential scheduler loop.
-        private void loop() {
-            try {
-                while (!cancelled) {
-                    Throwable error = errorRef.get();
-                    if (error != null) {
-                        cancelled = true;
-                        scheduler.stop();
-                        upstream.onError(error);
-                        cf.completeExceptionally(error);
-                        return;
-                    }
-                    if (nextLine == null) nextLine = nextLine();
-                    if (nextLine == null) {
-                        if (completed) {
-                            scheduler.stop();
-                            if (leftover.position() != 0) {
-                                // Underflow: not all bytes could be
-                                // decoded, but no more bytes will be coming.
-                                // This should not happen as we should already
-                                // have got a MalformedInputException, or
-                                // replaced the unmappable chars.
-                                errorRef.compareAndSet(null,
-                                        new IllegalStateException(
-                                                "premature end of input ("
-                                                        + leftover.position()
-                                                        + " undecoded bytes)"));
-                                continue;
-                            } else {
-                                upstream.onComplete();
-                            }
-                            return;
-                        } else if (demanded.get() == 0
-                                && !downstreamDemand.isFulfilled()) {
-                            long incr = Math.max(1, downstreamDemand.get());
-                            demanded.addAndGet(incr);
-                            upstreamSubscription.request(incr);
-                            continue;
-                        } else return;
-                    }
-                    assert nextLine != null;
-                    assert newline != null && !nextLine.endsWith(newline)
-                            || !nextLine.endsWith("\n") || !nextLine.endsWith("\r");
-                    if (downstreamDemand.tryDecrement()) {
-                        String forward = nextLine;
-                        nextLine = null;
-                        upstream.onNext(forward);
-                    } else return; // no demand: come back later
-                }
-            } catch (Throwable t) {
-                try {
-                    upstreamSubscription.cancel();
-                } finally {
-                    signalError(t);
-                }
-            }
-        }
-
-        static LineSubscription create(Flow.Subscription s,
-                                       Charset charset,
-                                       String lineSeparator,
-                                       Flow.Subscriber<? super String> upstream,
-                                       CompletableFuture<?> cf) {
-            return new LineSubscription(Objects.requireNonNull(s),
-                    Objects.requireNonNull(charset).newDecoder()
-                            // use the same decoder configuration than
-                            // java.io.InputStreamReader
-                            .onMalformedInput(CodingErrorAction.REPLACE)
-                            .onUnmappableCharacter(CodingErrorAction.REPLACE),
-                    lineSeparator,
-                    Objects.requireNonNull(upstream),
-                    Objects.requireNonNull(cf));
-        }
-    }
-}
-
--- a/src/java.net.http/share/classes/java/net/http/internal/MultiExchange.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,326 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.PushPromiseHandler;
-import java.net.http.HttpTimeoutException;
-import java.net.http.internal.UntrustedBodyHandler;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.ConnectionExpiredException;
-import java.net.http.internal.common.Utils;
-import static java.net.http.internal.common.MinimalFuture.completedFuture;
-import static java.net.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/java.net.http/share/classes/java/net/http/internal/PlainHttpConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +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 java.net.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 java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/PlainProxyConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +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 java.net.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);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/PlainTunnelingConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +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 java.net.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 java.net.http.HttpHeaders;
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.MinimalFuture;
-import static java.net.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);
-                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/java.net.http/share/classes/java/net/http/internal/PrivilegedExecutor.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +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 java.net.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));
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/ProxyAuthenticationRequired.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +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 java.net.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;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/PullPublisher.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-/*
- * 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 java.net.http.internal;
-
-import java.util.Iterator;
-import java.util.concurrent.Flow;
-import java.net.http.internal.common.Demand;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/PushGroup.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-/*
- * 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 java.net.http.internal;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodyHandler;
-import java.net.http.HttpResponse.PushPromiseHandler;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/RawChannelImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +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 java.net.http.internal;
-
-import java.net.http.internal.common.Utils;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/RedirectFilter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +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 java.net.http.internal;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpHeaders;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/RequestPublishers.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,377 +0,0 @@
-/*
- * 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 java.net.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 java.net.http.HttpRequest.BodyPublisher;
-import java.net.http.internal.common.Utils;
-
-public final class RequestPublishers {
-
-    private 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);
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/Response.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * 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 java.net.http.internal;
-
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.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();
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/ResponseBodyHandlers.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +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 java.net.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 java.net.http.HttpHeaders;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodyHandler;
-import java.net.http.HttpResponse.BodySubscriber;
-import java.net.http.internal.ResponseSubscribers.PathSubscriber;
-import static java.net.http.internal.common.Utils.unchecked;
-
-public final class ResponseBodyHandlers {
-
-    private 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);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/ResponseContent.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,467 +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 java.net.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 java.net.http.HttpHeaders;
-import java.net.http.HttpResponse;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/ResponseSubscribers.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,650 +0,0 @@
-/*
- * 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 java.net.http.internal;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.charset.Charset;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.concurrent.Flow.Subscription;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Stream;
-import java.net.http.HttpResponse.BodySubscriber;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.Utils;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class ResponseSubscribers {
-
-    public static class ConsumerSubscriber implements BodySubscriber<Void> {
-        private final Consumer<Optional<byte[]>> consumer;
-        private Flow.Subscription subscription;
-        private final CompletableFuture<Void> result = new MinimalFuture<>();
-        private final AtomicBoolean subscribed = new AtomicBoolean();
-
-        public ConsumerSubscriber(Consumer<Optional<byte[]>> consumer) {
-            this.consumer = Objects.requireNonNull(consumer);
-        }
-
-        @Override
-        public CompletionStage<Void> getBody() {
-            return result;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (!subscribed.compareAndSet(false, true)) {
-                subscription.cancel();
-            } else {
-                this.subscription = subscription;
-                subscription.request(1);
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> items) {
-            for (ByteBuffer item : items) {
-                byte[] buf = new byte[item.remaining()];
-                item.get(buf);
-                consumer.accept(Optional.of(buf));
-            }
-            subscription.request(1);
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            result.completeExceptionally(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            consumer.accept(Optional.empty());
-            result.complete(null);
-        }
-
-    }
-
-    public static class PathSubscriber implements BodySubscriber<Path> {
-
-        private final Path file;
-        private final CompletableFuture<Path> result = new MinimalFuture<>();
-
-        private volatile Flow.Subscription subscription;
-        private volatile FileChannel out;
-        private volatile AccessControlContext acc;
-        private final OpenOption[] options;
-
-        public PathSubscriber(Path file, OpenOption... options) {
-            this.file = file;
-            this.options = options;
-        }
-
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (System.getSecurityManager() != null && acc == null)
-                throw new InternalError(
-                        "Unexpected null acc when security manager has been installed");
-
-            this.subscription = subscription;
-            try {
-                PrivilegedExceptionAction<FileChannel> pa =
-                        () -> FileChannel.open(file, options);
-                out = AccessController.doPrivileged(pa, acc);
-            } catch (PrivilegedActionException pae) {
-                Throwable t = pae.getCause() != null ? pae.getCause() : pae;
-                result.completeExceptionally(t);
-                subscription.cancel();
-                return;
-            }
-            subscription.request(1);
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> items) {
-            try {
-                out.write(items.toArray(Utils.EMPTY_BB_ARRAY));
-            } catch (IOException ex) {
-                Utils.close(out);
-                subscription.cancel();
-                result.completeExceptionally(ex);
-            }
-            subscription.request(1);
-        }
-
-        @Override
-        public void onError(Throwable e) {
-            result.completeExceptionally(e);
-            Utils.close(out);
-        }
-
-        @Override
-        public void onComplete() {
-            Utils.close(out);
-            result.complete(file);
-        }
-
-        @Override
-        public CompletionStage<Path> getBody() {
-            return result;
-        }
-    }
-
-    public static class ByteArraySubscriber<T> implements BodySubscriber<T> {
-        private final Function<byte[], T> finisher;
-        private final CompletableFuture<T> result = new MinimalFuture<>();
-        private final List<ByteBuffer> received = new ArrayList<>();
-
-        private volatile Flow.Subscription subscription;
-
-        public ByteArraySubscriber(Function<byte[],T> finisher) {
-            this.finisher = finisher;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (this.subscription != null) {
-                subscription.cancel();
-                return;
-            }
-            this.subscription = subscription;
-            // We can handle whatever you've got
-            subscription.request(Long.MAX_VALUE);
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> items) {
-            // incoming buffers are allocated by http client internally,
-            // and won't be used anywhere except this place.
-            // So it's free simply to store them for further processing.
-            assert Utils.hasRemaining(items);
-            received.addAll(items);
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            received.clear();
-            result.completeExceptionally(throwable);
-        }
-
-        static private byte[] join(List<ByteBuffer> bytes) {
-            int size = Utils.remaining(bytes, Integer.MAX_VALUE);
-            byte[] res = new byte[size];
-            int from = 0;
-            for (ByteBuffer b : bytes) {
-                int l = b.remaining();
-                b.get(res, from, l);
-                from += l;
-            }
-            return res;
-        }
-
-        @Override
-        public void onComplete() {
-            try {
-                result.complete(finisher.apply(join(received)));
-                received.clear();
-            } catch (IllegalArgumentException e) {
-                result.completeExceptionally(e);
-            }
-        }
-
-        @Override
-        public CompletionStage<T> getBody() {
-            return result;
-        }
-    }
-
-    /**
-     * An InputStream built on top of the Flow API.
-     */
-    public static class HttpResponseInputStream extends InputStream
-        implements BodySubscriber<InputStream>
-    {
-        final static boolean DEBUG = Utils.DEBUG;
-        final static int MAX_BUFFERS_IN_QUEUE = 1;  // lock-step with the producer
-
-        // An immutable ByteBuffer sentinel to mark that the last byte was received.
-        private static final ByteBuffer LAST_BUFFER = ByteBuffer.wrap(new byte[0]);
-        private static final List<ByteBuffer> LAST_LIST = List.of(LAST_BUFFER);
-        private static final System.Logger DEBUG_LOGGER =
-                Utils.getDebugLogger("HttpResponseInputStream"::toString, DEBUG);
-
-        // A queue of yet unprocessed ByteBuffers received from the flow API.
-        private final BlockingQueue<List<ByteBuffer>> buffers;
-        private volatile Flow.Subscription subscription;
-        private volatile boolean closed;
-        private volatile Throwable failed;
-        private volatile Iterator<ByteBuffer> currentListItr;
-        private volatile ByteBuffer currentBuffer;
-        private final AtomicBoolean subscribed = new AtomicBoolean();
-
-        public HttpResponseInputStream() {
-            this(MAX_BUFFERS_IN_QUEUE);
-        }
-
-        HttpResponseInputStream(int maxBuffers) {
-            int capacity = (maxBuffers <= 0 ? MAX_BUFFERS_IN_QUEUE : maxBuffers);
-            // 1 additional slot needed for LAST_LIST added by onComplete
-            this.buffers = new ArrayBlockingQueue<>(capacity + 1);
-        }
-
-        @Override
-        public CompletionStage<InputStream> getBody() {
-            // Returns the stream immediately, before the
-            // response body is received.
-            // This makes it possible for sendAsync().get().body()
-            // to complete before the response body is received.
-            return CompletableFuture.completedStage(this);
-        }
-
-        // Returns the current byte buffer to read from.
-        // If the current buffer has no remaining data, this method will take the
-        // next buffer from the buffers queue, possibly blocking until
-        // a new buffer is made available through the Flow API, or the
-        // end of the flow has been reached.
-        private ByteBuffer current() throws IOException {
-            while (currentBuffer == null || !currentBuffer.hasRemaining()) {
-                // Check whether the stream is closed or exhausted
-                if (closed || failed != null) {
-                    throw new IOException("closed", failed);
-                }
-                if (currentBuffer == LAST_BUFFER) break;
-
-                try {
-                    if (currentListItr == null || !currentListItr.hasNext()) {
-                        // Take a new list of buffers from the queue, blocking
-                        // if none is available yet...
-
-                        DEBUG_LOGGER.log(Level.DEBUG, "Taking list of Buffers");
-                        List<ByteBuffer> lb = buffers.take();
-                        currentListItr = lb.iterator();
-                        DEBUG_LOGGER.log(Level.DEBUG, "List of Buffers Taken");
-
-                        // Check whether an exception was encountered upstream
-                        if (closed || failed != null)
-                            throw new IOException("closed", failed);
-
-                        // Check whether we're done.
-                        if (lb == LAST_LIST) {
-                            currentListItr = null;
-                            currentBuffer = LAST_BUFFER;
-                            break;
-                        }
-
-                        // Request another upstream item ( list of buffers )
-                        Flow.Subscription s = subscription;
-                        if (s != null) {
-                            DEBUG_LOGGER.log(Level.DEBUG, "Increased demand by 1");
-                            s.request(1);
-                        }
-                        assert currentListItr != null;
-                        if (lb.isEmpty()) continue;
-                    }
-                    assert currentListItr != null;
-                    assert currentListItr.hasNext();
-                    DEBUG_LOGGER.log(Level.DEBUG, "Next Buffer");
-                    currentBuffer = currentListItr.next();
-                } catch (InterruptedException ex) {
-                    // continue
-                }
-            }
-            assert currentBuffer == LAST_BUFFER || currentBuffer.hasRemaining();
-            return currentBuffer;
-        }
-
-        @Override
-        public int read(byte[] bytes, int off, int len) throws IOException {
-            // get the buffer to read from, possibly blocking if
-            // none is available
-            ByteBuffer buffer;
-            if ((buffer = current()) == LAST_BUFFER) return -1;
-
-            // don't attempt to read more than what is available
-            // in the current buffer.
-            int read = Math.min(buffer.remaining(), len);
-            assert read > 0 && read <= buffer.remaining();
-
-            // buffer.get() will do the boundary check for us.
-            buffer.get(bytes, off, read);
-            return read;
-        }
-
-        @Override
-        public int read() throws IOException {
-            ByteBuffer buffer;
-            if ((buffer = current()) == LAST_BUFFER) return -1;
-            return buffer.get() & 0xFF;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription s) {
-            try {
-                if (!subscribed.compareAndSet(false, true)) {
-                    s.cancel();
-                } else {
-                    // check whether the stream is already closed.
-                    // if so, we should cancel the subscription
-                    // immediately.
-                    boolean closed;
-                    synchronized (this) {
-                        closed = this.closed;
-                        if (!closed) {
-                            this.subscription = s;
-                        }
-                    }
-                    if (closed) {
-                        s.cancel();
-                        return;
-                    }
-                    assert buffers.remainingCapacity() > 1; // should contain at least 2
-                    DEBUG_LOGGER.log(Level.DEBUG, () -> "onSubscribe: requesting "
-                            + Math.max(1, buffers.remainingCapacity() - 1));
-                    s.request(Math.max(1, buffers.remainingCapacity() - 1));
-                }
-            } catch (Throwable t) {
-                failed = t;
-                try {
-                    close();
-                } catch (IOException x) {
-                    // OK
-                } finally {
-                    onError(t);
-                }
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> t) {
-            Objects.requireNonNull(t);
-            try {
-                DEBUG_LOGGER.log(Level.DEBUG, "next item received");
-                if (!buffers.offer(t)) {
-                    throw new IllegalStateException("queue is full");
-                }
-                DEBUG_LOGGER.log(Level.DEBUG, "item offered");
-            } catch (Throwable ex) {
-                failed = ex;
-                try {
-                    close();
-                } catch (IOException ex1) {
-                    // OK
-                } finally {
-                    onError(ex);
-                }
-            }
-        }
-
-        @Override
-        public void onError(Throwable thrwbl) {
-            subscription = null;
-            failed = Objects.requireNonNull(thrwbl);
-            // The client process that reads the input stream might
-            // be blocked in queue.take().
-            // Tries to offer LAST_LIST to the queue. If the queue is
-            // full we don't care if we can't insert this buffer, as
-            // the client can't be blocked in queue.take() in that case.
-            // Adding LAST_LIST to the queue is harmless, as the client
-            // should find failed != null before handling LAST_LIST.
-            buffers.offer(LAST_LIST);
-        }
-
-        @Override
-        public void onComplete() {
-            subscription = null;
-            onNext(LAST_LIST);
-        }
-
-        @Override
-        public void close() throws IOException {
-            Flow.Subscription s;
-            synchronized (this) {
-                if (closed) return;
-                closed = true;
-                s = subscription;
-                subscription = null;
-            }
-            // s will be null if already completed
-            try {
-                if (s != null) {
-                    s.cancel();
-                }
-            } finally {
-                buffers.offer(LAST_LIST);
-                super.close();
-            }
-        }
-
-    }
-
-    public static BodySubscriber<Stream<String>> createLineStream() {
-        return createLineStream(UTF_8);
-    }
-
-    public static BodySubscriber<Stream<String>> createLineStream(Charset charset) {
-        Objects.requireNonNull(charset);
-        BodySubscriber<InputStream> s = new HttpResponseInputStream();
-        return new MappedSubscriber<InputStream,Stream<String>>(s,
-            (InputStream stream) -> {
-                return new BufferedReader(new InputStreamReader(stream, charset))
-                            .lines().onClose(() -> Utils.close(stream));
-            });
-    }
-
-    /**
-     * Currently this consumes all of the data and ignores it
-     */
-    public static class NullSubscriber<T> implements BodySubscriber<T> {
-
-        private final CompletableFuture<T> cf = new MinimalFuture<>();
-        private final Optional<T> result;
-        private final AtomicBoolean subscribed = new AtomicBoolean();
-
-        public NullSubscriber(Optional<T> result) {
-            this.result = result;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (!subscribed.compareAndSet(false, true)) {
-                subscription.cancel();
-            } else {
-                subscription.request(Long.MAX_VALUE);
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> items) {
-            Objects.requireNonNull(items);
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            cf.completeExceptionally(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            if (result.isPresent()) {
-                cf.complete(result.get());
-            } else {
-                cf.complete(null);
-            }
-        }
-
-        @Override
-        public CompletionStage<T> getBody() {
-            return cf;
-        }
-    }
-
-    /** An adapter between {@code BodySubscriber} and {@code Flow.Subscriber}. */
-    public static final class SubscriberAdapter<S extends Subscriber<? super List<ByteBuffer>>,R>
-        implements BodySubscriber<R>
-    {
-        private final CompletableFuture<R> cf = new MinimalFuture<>();
-        private final S subscriber;
-        private final Function<S,R> finisher;
-        private volatile Subscription subscription;
-
-        public SubscriberAdapter(S subscriber, Function<S,R> finisher) {
-            this.subscriber = Objects.requireNonNull(subscriber);
-            this.finisher = Objects.requireNonNull(finisher);
-        }
-
-        @Override
-        public void onSubscribe(Subscription subscription) {
-            Objects.requireNonNull(subscription);
-            if (this.subscription != null) {
-                subscription.cancel();
-            } else {
-                this.subscription = subscription;
-                subscriber.onSubscribe(subscription);
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            Objects.requireNonNull(item);
-            try {
-                subscriber.onNext(item);
-            } catch (Throwable throwable) {
-                subscription.cancel();
-                onError(throwable);
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            Objects.requireNonNull(throwable);
-            try {
-                subscriber.onError(throwable);
-            } finally {
-                cf.completeExceptionally(throwable);
-            }
-        }
-
-        @Override
-        public void onComplete() {
-            try {
-                subscriber.onComplete();
-            } finally {
-                try {
-                    cf.complete(finisher.apply(subscriber));
-                } catch (Throwable throwable) {
-                    cf.completeExceptionally(throwable);
-                }
-            }
-        }
-
-        @Override
-        public CompletionStage<R> getBody() {
-            return cf;
-        }
-    }
-
-    /**
-     * A body subscriber which receives input from an upstream subscriber
-     * and maps that subscriber's body type to a new type. The upstream subscriber
-     * delegates all flow operations directly to this object. The
-     * {@link CompletionStage} returned by {@link #getBody()}} takes the output
-     * of the upstream {@code getBody()} and applies the mapper function to
-     * obtain the new {@code CompletionStage} type.
-     *
-     * Uses an Executor that must be set externally.
-     *
-     * @param <T> the upstream body type
-     * @param <U> this subscriber's body type
-     */
-    public static class MappedSubscriber<T,U> implements BodySubscriber<U> {
-        final BodySubscriber<T> upstream;
-        final Function<T,U> mapper;
-
-        /**
-         *
-         * @param upstream
-         * @param mapper
-         */
-        public MappedSubscriber(BodySubscriber<T> upstream, Function<T,U> mapper) {
-            this.upstream = upstream;
-            this.mapper = mapper;
-        }
-
-        @Override
-        public CompletionStage<U> getBody() {
-            return upstream.getBody()
-                    .thenApply(mapper);
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            upstream.onSubscribe(subscription);
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            upstream.onNext(item);
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            upstream.onError(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            upstream.onComplete();
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/SSLDelegate.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,489 +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 java.net.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 java.net.http.internal.common.Log;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/SocketTube.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,956 +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 java.net.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 java.net.http.internal.common.Demand;
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.SequentialScheduler.DeferredCompleter;
-import java.net.http.internal.common.SequentialScheduler.RestartableTask;
-import java.net.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/java.net.http/share/classes/java/net/http/internal/Stream.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1180 +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 java.net.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 java.net.http.HttpClient;
-import java.net.http.HttpHeaders;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodySubscriber;
-import java.net.http.internal.common.*;
-import java.net.http.internal.frame.*;
-import java.net.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, executor);
-
-        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, Executor executor) {
-        responseBodyCF = new MinimalFuture<>();
-        // We want to allow the subscriber's getBody() method to block so it
-        // can work with InputStreams. So, we offload execution.
-        executor.execute(() -> {
-            bodySubscriber.getBody().whenComplete((T body, Throwable t) -> {
-                if (t == null)
-                    responseBodyCF.complete(body);
-                else
-                    responseBodyCF.completeExceptionally(t);
-            });
-        });
-
-        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/java.net.http/share/classes/java/net/http/internal/TimeoutEvent.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +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 java.net.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 + "]";
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/UntrustedBodyHandler.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +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 java.net.http.internal;
-
-import java.security.AccessControlContext;
-import java.net.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);
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/WindowController.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,320 +0,0 @@
-/*
- * 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 java.net.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 java.net.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/java.net.http/share/classes/java/net/http/internal/WindowUpdateSender.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +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 java.net.http.internal;
-
-import java.lang.System.Logger.Level;
-import java.net.http.internal.frame.SettingsFrame;
-import java.net.http.internal.frame.WindowUpdateFrame;
-import java.net.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/src/java.net.http/share/classes/java/net/http/internal/common/ByteBufferPool.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +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 java.net.http.internal.common;
-
-import java.nio.ByteBuffer;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * The class provides reuse of ByteBuffers.
- * It is supposed that all requested buffers have the same size for a long period of time.
- * That is why there is no any logic splitting buffers into different buckets (by size). It's unnecessary.
- *
- * At the same moment it is allowed to change requested buffers size (all smaller buffers will be discarded).
- * It may be needed for example, if after rehandshaking netPacketBufferSize was changed.
- */
-public class ByteBufferPool {
-
-    private final java.util.Queue<ByteBuffer> pool = new ConcurrentLinkedQueue<>();
-
-    public ByteBufferPool() {
-    }
-
-    public ByteBufferReference get(int size) {
-        ByteBuffer buffer;
-        while ((buffer = pool.poll()) != null) {
-            if (buffer.capacity() >= size) {
-                return ByteBufferReference.of(buffer, this);
-            }
-        }
-        return ByteBufferReference.of(ByteBuffer.allocate(size), this);
-    }
-
-    public void release(ByteBuffer buffer) {
-        buffer.clear();
-        pool.offer(buffer);
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/ByteBufferReference.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +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 java.net.http.internal.common;
-
-import java.nio.ByteBuffer;
-import java.util.Objects;
-import java.util.function.Supplier;
-
-public class ByteBufferReference  implements Supplier<ByteBuffer> {
-
-    private ByteBuffer buffer;
-    private final ByteBufferPool pool;
-
-    public static ByteBufferReference of(ByteBuffer buffer) {
-        return of(buffer, null);
-    }
-
-    public static ByteBufferReference of(ByteBuffer buffer, ByteBufferPool pool) {
-        Objects.requireNonNull(buffer);
-        return new ByteBufferReference(buffer, pool);
-    }
-
-    public static ByteBuffer[] toBuffers(ByteBufferReference... refs) {
-        ByteBuffer[] bufs = new ByteBuffer[refs.length];
-        for (int i = 0; i < refs.length; i++) {
-            bufs[i] = refs[i].get();
-        }
-        return bufs;
-    }
-
-    public static ByteBufferReference[] toReferences(ByteBuffer... buffers) {
-        ByteBufferReference[] refs = new ByteBufferReference[buffers.length];
-        for (int i = 0; i < buffers.length; i++) {
-            refs[i] = of(buffers[i]);
-        }
-        return refs;
-    }
-
-
-    public static void clear(ByteBufferReference[] refs) {
-        for(ByteBufferReference ref : refs) {
-            ref.clear();
-        }
-    }
-
-    private ByteBufferReference(ByteBuffer buffer, ByteBufferPool pool) {
-        this.buffer = buffer;
-        this.pool = pool;
-    }
-
-    @Override
-    public ByteBuffer get() {
-        ByteBuffer buf = this.buffer;
-        assert buf!=null : "getting ByteBuffer after clearance";
-        return buf;
-    }
-
-    public void clear() {
-        ByteBuffer buf = this.buffer;
-        assert buf!=null : "double ByteBuffer clearance";
-        this.buffer = null;
-        if (pool != null) {
-            pool.release(buf);
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/ConnectionExpiredException.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +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 java.net.http.internal.common;
-
-import java.io.IOException;
-
-/**
- * Signals that an end of file or end of stream has been reached
- * unexpectedly before any protocol specific data has been received.
- */
-public final class ConnectionExpiredException extends IOException {
-    private static final long serialVersionUID = 0;
-
-    /**
-     * Constructs a {@code ConnectionExpiredException} with the specified detail
-     * message and cause.
-     *
-     * @param   s     the detail message
-     * @param   cause the throwable cause
-     */
-    public ConnectionExpiredException(String s, Throwable cause) {
-        super(s, cause);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/DebugLogger.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,251 +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 java.net.http.internal.common;
-
-import java.io.PrintStream;
-import java.util.Objects;
-import java.util.ResourceBundle;
-import java.util.function.Supplier;
-import java.lang.System.Logger;
-
-/**
- * A {@code System.Logger} that forwards all messages to an underlying
- * {@code System.Logger}, after adding some decoration.
- * The logger also has the ability to additionally send the logged messages
- * to System.err or System.out, whether the underlying logger is activated or not.
- * In addition instance of {@code DebugLogger} support both
- * {@link String#format(String, Object...)} and
- * {@link java.text.MessageFormat#format(String, Object...)} formatting.
- * String-like formatting is enabled by the presence of "%s" or "%d" in the format
- * string. MessageFormat-like formatting is enabled by the presence of "{0" or "{1".
- * <p>
- * See {@link Utils#getDebugLogger(Supplier, boolean)} and
- * {@link Utils#getHpackLogger(Supplier, boolean)}.
- */
-class DebugLogger implements Logger {
-    // deliberately not in the same subtree than standard loggers.
-    final static String HTTP_NAME  = "jdk.internal.httpclient.debug";
-    final static String HPACK_NAME = "jdk.internal.httpclient.hpack.debug";
-    final static Logger HTTP = System.getLogger(HTTP_NAME);
-    final static Logger HPACK = System.getLogger(HPACK_NAME);
-    final static long START_NANOS = System.nanoTime();
-
-    private final Supplier<String> dbgTag;
-    private final Level errLevel;
-    private final Level outLevel;
-    private final Logger logger;
-    private final boolean debugOn;
-    private final boolean traceOn;
-
-    /**
-     * Create a logger for debug traces.The logger should only be used
-     * with levels whose severity is {@code <= DEBUG}.
-     *
-     * By default, this logger will forward all messages logged to the supplied
-     * {@code logger}.
-     * But in addition, if the message severity level is {@code >=} to
-     * the provided {@code errLevel} it will print the messages on System.err,
-     * and if the message severity level is {@code >=} to
-     * the provided {@code outLevel} it will also print the messages on System.out.
-     * <p>
-     * The logger will add some decoration to the printed message, in the form of
-     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
-     *
-     * @apiNote To obtain a logger that will always print things on stderr in
-     *          addition to forwarding to the internal logger, use
-     *          {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.ALL);}.
-     *          To obtain a logger that will only forward to the internal logger,
-     *          use {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.OFF);}.
-     *
-     * @param logger The internal logger to which messages will be forwarded.
-     *               This should be either {@link #HPACK} or {@link #HTTP};
-     *
-     * @param dbgTag A lambda that returns a string that identifies the caller
-     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
-     * @param outLevel The level above which messages will be also printed on
-     *               System.out (in addition to being forwarded to the internal logger).
-     * @param errLevel The level above which messages will be also printed on
-     *               System.err (in addition to being forwarded to the internal logger).
-     *
-     * @return A logger for HTTP internal debug traces
-     */
-    private DebugLogger(Logger logger,
-                Supplier<String> dbgTag,
-                Level outLevel,
-                Level errLevel) {
-        this.dbgTag = dbgTag;
-        this.errLevel = errLevel;
-        this.outLevel = outLevel;
-        this.logger = Objects.requireNonNull(logger);
-        // support only static configuration.
-        this.debugOn = isEnabled(Level.DEBUG);
-        this.traceOn = isEnabled(Level.TRACE);
-    }
-
-    @Override
-    public String getName() {
-        return logger.getName();
-    }
-
-    private boolean isEnabled(Level level) {
-        if (level == Level.OFF) return false;
-        int severity = level.getSeverity();
-        return severity >= errLevel.getSeverity()
-                || severity >= outLevel.getSeverity()
-                || logger.isLoggable(level);
-    }
-
-    @Override
-    public boolean isLoggable(Level level) {
-        // fast path, we assume these guys never change.
-        // support only static configuration.
-        if (level == Level.DEBUG) return debugOn;
-        if (level == Level.TRACE) return traceOn;
-        return isEnabled(level);
-    }
-
-    @Override
-    public void log(Level level, ResourceBundle unused,
-                    String format, Object... params) {
-        // fast path, we assume these guys never change.
-        // support only static configuration.
-        if (level == Level.DEBUG && !debugOn) return;
-        if (level == Level.TRACE && !traceOn) return;
-
-        int severity = level.getSeverity();
-        if (errLevel != Level.OFF
-                && errLevel.getSeverity() <= severity) {
-            print(System.err, level, format, params, null);
-        }
-        if (outLevel != Level.OFF
-                && outLevel.getSeverity() <= severity) {
-            print(System.out, level, format, params, null);
-        }
-        if (logger.isLoggable(level)) {
-            logger.log(level, unused,
-                    getFormat(new StringBuilder(), format, params).toString(),
-                    params);
-        }
-    }
-
-    @Override
-    public void log(Level level, ResourceBundle unused, String msg,
-                    Throwable thrown) {
-        // fast path, we assume these guys never change.
-        if (level == Level.DEBUG && !debugOn) return;
-        if (level == Level.TRACE && !traceOn) return;
-
-        if (errLevel != Level.OFF
-                && errLevel.getSeverity() <= level.getSeverity()) {
-            print(System.err, level, msg, null, thrown);
-        }
-        if (outLevel != Level.OFF
-                && outLevel.getSeverity() <= level.getSeverity()) {
-            print(System.out, level, msg, null, thrown);
-        }
-        if (logger.isLoggable(level)) {
-            logger.log(level, unused,
-                    getFormat(new StringBuilder(), msg, null).toString(),
-                    thrown);
-        }
-    }
-
-    private void print(PrintStream out, Level level, String msg,
-                       Object[] params, Throwable t) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(level.name()).append(':').append(' ');
-        sb = format(sb, msg, params);
-        if (t != null) sb.append(' ').append(t.toString());
-        out.println(sb.toString());
-        if (t != null) {
-            t.printStackTrace(out);
-        }
-    }
-
-    private StringBuilder decorate(StringBuilder sb, String msg) {
-        String tag = dbgTag == null ? null : dbgTag.get();
-        String res = msg == null ? "" : msg;
-        long elapsed = System.nanoTime() - START_NANOS;
-        long millis = elapsed / 1000_000;
-        long secs   = millis / 1000;
-        sb.append('[').append(Thread.currentThread().getName()).append(']')
-                .append(' ').append('[');
-        if (secs > 0) {
-            sb.append(secs).append('s');
-        }
-        millis = millis % 1000;
-        if (millis > 0) {
-            if (secs > 0) sb.append(' ');
-            sb.append(millis).append("ms");
-        }
-        sb.append(']').append(' ');
-        if (tag != null) {
-            sb.append(tag).append(' ');
-        }
-        sb.append(res);
-        return sb;
-    }
-
-
-    private StringBuilder getFormat(StringBuilder sb, String format, Object[] params) {
-        if (format == null || params == null || params.length == 0) {
-            return decorate(sb, format);
-        } else if (format.contains("{0}") || format.contains("{1}")) {
-            return decorate(sb, format);
-        } else if (format.contains("%s") || format.contains("%d")) {
-            try {
-                return decorate(sb, String.format(format, params));
-            } catch (Throwable t) {
-                return decorate(sb, format);
-            }
-        } else {
-            return decorate(sb, format);
-        }
-    }
-
-    private StringBuilder format(StringBuilder sb, String format, Object[] params) {
-        if (format == null || params == null || params.length == 0) {
-            return decorate(sb, format);
-        } else if (format.contains("{0}") || format.contains("{1}")) {
-            return decorate(sb, java.text.MessageFormat.format(format, params));
-        } else if (format.contains("%s") || format.contains("%d")) {
-            try {
-                return decorate(sb, String.format(format, params));
-            } catch (Throwable t) {
-                return decorate(sb, format);
-            }
-        } else {
-            return decorate(sb, format);
-        }
-    }
-
-    public static DebugLogger createHttpLogger(Supplier<String> dbgTag, Level outLevel, Level errLevel) {
-        return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
-    }
-
-    public static DebugLogger createHpackLogger(Supplier<String> dbgTag, Level outLevel, Level errLevel) {
-        return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/Demand.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +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 java.net.http.internal.common;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Encapsulates operations with demand (Reactive Streams).
- *
- * <p> Demand is the aggregated number of elements requested by a Subscriber
- * which is yet to be delivered (fulfilled) by the Publisher.
- */
-public final class Demand {
-
-    private final AtomicLong val = new AtomicLong();
-
-    /**
-     * Increases this demand by the specified positive value.
-     *
-     * @param n
-     *         increment {@code > 0}
-     *
-     * @return {@code true} iff prior to this operation this demand was fulfilled
-     */
-    public boolean increase(long n) {
-        if (n <= 0) {
-            throw new IllegalArgumentException(String.valueOf(n));
-        }
-        long prev = val.getAndAccumulate(n, (p, i) -> p + i < 0 ? Long.MAX_VALUE : p + i);
-        return prev == 0;
-    }
-
-    /**
-     * Tries to decrease this demand by the specified positive value.
-     *
-     * <p> The actual value this demand has been decreased by might be less than
-     * {@code n}, including {@code 0} (no decrease at all).
-     *
-     * @param n
-     *         decrement {@code > 0}
-     *
-     * @return a value {@code m} ({@code 0 <= m <= n}) this demand has been
-     *         actually decreased by
-     */
-    public long decreaseAndGet(long n) {
-        if (n <= 0) {
-            throw new IllegalArgumentException(String.valueOf(n));
-        }
-        long p, d;
-        do {
-            d = val.get();
-            p = Math.min(d, n);
-        } while (!val.compareAndSet(d, d - p));
-        return p;
-    }
-
-    /**
-     * Tries to decrease this demand by {@code 1}.
-     *
-     * @return {@code true} iff this demand has been decreased by {@code 1}
-     */
-    public boolean tryDecrement() {
-        return decreaseAndGet(1) == 1;
-    }
-
-    /**
-     * @return {@code true} iff there is no unfulfilled demand
-     */
-    public boolean isFulfilled() {
-        return val.get() == 0;
-    }
-
-    /**
-     * Resets this object to its initial state.
-     */
-    public void reset() {
-        val.set(0);
-    }
-
-    /**
-     * Returns the current value of this demand.
-     *
-     * @return the current value of this demand
-     */
-    public long get() {
-        return val.get();
-    }
-
-    @Override
-    public String toString() {
-        return String.valueOf(val.get());
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/FlowTube.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +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 java.net.http.internal.common;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.concurrent.Flow;
-
-/**
- * FlowTube is an I/O abstraction that allows reading from and writing to a
- * destination asynchronously.
- * This is not a {@link Flow.Processor
- * Flow.Processor&lt;List&lt;ByteBuffer&gt;, List&lt;ByteBuffer&gt;&gt;},
- * but rather models a publisher source and a subscriber sink in a bidirectional flow.
- * <p>
- * The {@code connectFlows} method should be called to connect the bidirectional
- * flow. A FlowTube supports handing over the same read subscription to different
- * sequential read subscribers over time. When {@code connectFlows(writePublisher,
- * readSubscriber} is called, the FlowTube will call {@code dropSubscription} on
- * its former readSubscriber, and {@code onSubscribe} on its new readSubscriber.
- */
-public interface FlowTube extends
-       Flow.Publisher<List<ByteBuffer>>,
-       Flow.Subscriber<List<ByteBuffer>> {
-
-    /**
-     * A subscriber for reading from the bidirectional flow.
-     * A TubeSubscriber is a {@code Flow.Subscriber} that can be canceled
-     * by calling {@code dropSubscription()}.
-     * Once {@code dropSubscription()} is called, the {@code TubeSubscriber}
-     * should stop calling any method on its subscription.
-     */
-    static interface TubeSubscriber extends Flow.Subscriber<List<ByteBuffer>> {
-
-        /**
-         * Called when the flow is connected again, and the subscription
-         * is handed over to a new subscriber.
-         * Once {@code dropSubscription()} is called, the {@code TubeSubscriber}
-         * should stop calling any method on its subscription.
-         */
-        default void dropSubscription() { }
-
-    }
-
-    /**
-     * A publisher for writing to the bidirectional flow.
-     */
-    static interface TubePublisher extends Flow.Publisher<List<ByteBuffer>> {
-
-    }
-
-    /**
-     * Connects the bidirectional flows to a write {@code Publisher} and a
-     * read {@code Subscriber}. This method can be called sequentially
-     * several times to switch existing publishers and subscribers to a new
-     * pair of write subscriber and read publisher.
-     * @param writePublisher A new publisher for writing to the bidirectional flow.
-     * @param readSubscriber A new subscriber for reading from the bidirectional
-     *                       flow.
-     */
-    default void connectFlows(TubePublisher writePublisher,
-                              TubeSubscriber readSubscriber) {
-
-        this.subscribe(readSubscriber);
-        writePublisher.subscribe(this);
-    }
-
-    /**
-     * Returns true if this flow was completed, either exceptionally
-     * or normally (EOF reached).
-     * @return true if the flow is finished
-     */
-    boolean isFinished();
-
-
-    /**
-     * Returns {@code s} if {@code s} is a {@code TubeSubscriber}, otherwise
-     * wraps it in a {@code TubeSubscriber}.
-     * Using the wrapper is only appropriate in the case where
-     * {@code dropSubscription} doesn't need to be implemented, and the
-     * {@code TubeSubscriber} is subscribed only once.
-     *
-     * @param s a subscriber for reading.
-     * @return a {@code TubeSubscriber}: either {@code s} if {@code s} is a
-     *           {@code TubeSubscriber}, otherwise a {@code TubeSubscriber}
-     *           wrapper that delegates to {@code s}
-     */
-    static TubeSubscriber asTubeSubscriber(Flow.Subscriber<? super List<ByteBuffer>> s) {
-        if (s instanceof TubeSubscriber) {
-            return (TubeSubscriber) s;
-        }
-        return new AbstractTubeSubscriber.TubeSubscriberWrapper(s);
-    }
-
-    /**
-     * Returns {@code s} if {@code s} is a {@code  TubePublisher}, otherwise
-     * wraps it in a {@code  TubePublisher}.
-     *
-     * @param p a publisher for writing.
-     * @return a {@code TubePublisher}: either {@code s} if {@code s} is a
-     *           {@code  TubePublisher}, otherwise a {@code TubePublisher}
-     *           wrapper that delegates to {@code s}
-     */
-    static TubePublisher asTubePublisher(Flow.Publisher<List<ByteBuffer>> p) {
-        if (p instanceof TubePublisher) {
-            return (TubePublisher) p;
-        }
-        return new AbstractTubePublisher.TubePublisherWrapper(p);
-    }
-
-    /**
-     * Convenience abstract class for {@code TubePublisher} implementations.
-     * It is not required that a {@code TubePublisher} implementation extends
-     * this class.
-     */
-    static abstract class AbstractTubePublisher implements TubePublisher {
-        static final class TubePublisherWrapper extends AbstractTubePublisher {
-            final Flow.Publisher<List<ByteBuffer>> delegate;
-            public TubePublisherWrapper(Flow.Publisher<List<ByteBuffer>> delegate) {
-                this.delegate = delegate;
-            }
-            @Override
-            public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-                delegate.subscribe(subscriber);
-            }
-        }
-    }
-
-    /**
-     * Convenience abstract class for {@code TubeSubscriber} implementations.
-     * It is not required that a {@code TubeSubscriber} implementation extends
-     * this class.
-     */
-    static abstract class AbstractTubeSubscriber implements TubeSubscriber {
-        static final class TubeSubscriberWrapper extends  AbstractTubeSubscriber {
-            final Flow.Subscriber<? super List<ByteBuffer>> delegate;
-            TubeSubscriberWrapper(Flow.Subscriber<? super List<ByteBuffer>> delegate) {
-                this.delegate = delegate;
-            }
-            @Override
-            public void dropSubscription() {}
-            @Override
-            public void onSubscribe(Flow.Subscription subscription) {
-                delegate.onSubscribe(subscription);
-            }
-            @Override
-            public void onNext(List<ByteBuffer> item) {
-                delegate.onNext(item);
-            }
-            @Override
-            public void onError(Throwable throwable) {
-                delegate.onError(throwable);
-            }
-            @Override
-            public void onComplete() {
-                delegate.onComplete();
-            }
-        }
-
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/HttpHeadersImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +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 java.net.http.internal.common;
-
-import java.net.http.HttpHeaders;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-/**
- * Implementation of HttpHeaders.
- */
-public class HttpHeadersImpl extends HttpHeaders {
-
-    private final TreeMap<String,List<String>> headers;
-
-    public HttpHeadersImpl() {
-        headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-    }
-
-    @Override
-    public Map<String, List<String>> map() {
-        return Collections.unmodifiableMap(headers);
-    }
-
-    // package private mutators
-
-    public HttpHeadersImpl deepCopy() {
-        HttpHeadersImpl h1 = new HttpHeadersImpl();
-        for (Map.Entry<String,List<String>> entry : headers.entrySet()) {
-            List<String> valuesCopy = new ArrayList<>(entry.getValue());
-            h1.headers.put(entry.getKey(), valuesCopy);
-        }
-        return h1;
-    }
-
-    public void addHeader(String name, String value) {
-        headers.computeIfAbsent(name, k -> new ArrayList<>(1))
-               .add(value);
-    }
-
-    public void setHeader(String name, String value) {
-        List<String> values = new ArrayList<>(1); // most headers has one value
-        values.add(value);
-        headers.put(name, values);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/Log.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,302 +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 java.net.http.internal.common;
-
-import java.net.http.HttpHeaders;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-import java.net.http.internal.frame.DataFrame;
-import java.net.http.internal.frame.Http2Frame;
-import java.net.http.internal.frame.WindowUpdateFrame;
-
-import javax.net.ssl.SNIServerName;
-import javax.net.ssl.SSLParameters;
-
-/**
- * -Djava.net.HttpClient.log=
- *          errors,requests,headers,
- *          frames[:control:data:window:all..],content,ssl,trace
- *
- * Any of errors, requests, headers or content are optional.
- *
- * Other handlers may be added. All logging is at level INFO
- *
- * Logger name is "jdk.httpclient.HttpClient"
- */
-// implements System.Logger in order to be skipped when printing the caller's
-// information
-public abstract class Log implements System.Logger {
-
-    static final String logProp = "jdk.httpclient.HttpClient.log";
-
-    public static final int OFF = 0;
-    public static final int ERRORS = 0x1;
-    public static final int REQUESTS = 0x2;
-    public static final int HEADERS = 0x4;
-    public static final int CONTENT = 0x8;
-    public static final int FRAMES = 0x10;
-    public static final int SSL = 0x20;
-    public static final int TRACE = 0x40;
-    static int logging;
-
-    // Frame types: "control", "data", "window", "all"
-    public static final int CONTROL = 1; // all except DATA and WINDOW_UPDATES
-    public static final int DATA = 2;
-    public static final int WINDOW_UPDATES = 4;
-    public static final int ALL = CONTROL| DATA | WINDOW_UPDATES;
-    static int frametypes;
-
-    static final System.Logger logger;
-
-    static {
-        String s = Utils.getNetProperty(logProp);
-        if (s == null) {
-            logging = OFF;
-        } else {
-            String[] vals = s.split(",");
-            for (String val : vals) {
-                switch (val.toLowerCase(Locale.US)) {
-                    case "errors":
-                        logging |= ERRORS;
-                        break;
-                    case "requests":
-                        logging |= REQUESTS;
-                        break;
-                    case "headers":
-                        logging |= HEADERS;
-                        break;
-                    case "content":
-                        logging |= CONTENT;
-                        break;
-                    case "ssl":
-                        logging |= SSL;
-                        break;
-                    case "trace":
-                        logging |= TRACE;
-                        break;
-                    case "all":
-                        logging |= CONTENT|HEADERS|REQUESTS|FRAMES|ERRORS|TRACE|SSL;
-                        break;
-                }
-                if (val.startsWith("frames")) {
-                    logging |= FRAMES;
-                    String[] types = val.split(":");
-                    if (types.length == 1) {
-                        frametypes = CONTROL | DATA | WINDOW_UPDATES;
-                    } else {
-                        for (String type : types) {
-                            switch (type.toLowerCase()) {
-                                case "control":
-                                    frametypes |= CONTROL;
-                                    break;
-                                case "data":
-                                    frametypes |= DATA;
-                                    break;
-                                case "window":
-                                    frametypes |= WINDOW_UPDATES;
-                                    break;
-                                case "all":
-                                    frametypes = ALL;
-                                    break;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        if (logging != OFF) {
-            logger = System.getLogger("jdk.httpclient.HttpClient");
-        } else {
-            logger = null;
-        }
-    }
-    public static boolean errors() {
-        return (logging & ERRORS) != 0;
-    }
-
-    public static boolean requests() {
-        return (logging & REQUESTS) != 0;
-    }
-
-    public static boolean headers() {
-        return (logging & HEADERS) != 0;
-    }
-
-    public static boolean trace() {
-        return (logging & TRACE) != 0;
-    }
-
-    public static boolean ssl() {
-        return (logging & SSL) != 0;
-    }
-
-    public static boolean frames() {
-        return (logging & FRAMES) != 0;
-    }
-
-    public static void logError(String s, Object... s1) {
-        if (errors()) {
-            logger.log(Level.INFO, "ERROR: " + s, s1);
-        }
-    }
-
-    public static void logError(Throwable t) {
-        if (errors()) {
-            String s = Utils.stackTrace(t);
-            logger.log(Level.INFO, "ERROR: " + s);
-        }
-    }
-
-    public static void logSSL(String s, Object... s1) {
-        if (ssl()) {
-            logger.log(Level.INFO, "SSL: " + s, s1);
-        }
-    }
-
-    public static void logSSL(Supplier<String> msgSupplier) {
-        if (ssl()) {
-            logger.log(Level.INFO, "SSL: " + msgSupplier.get());
-        }
-    }
-
-    public static void logTrace(String s, Object... s1) {
-        if (trace()) {
-            String format = "TRACE: " + s;
-            logger.log(Level.INFO, format, s1);
-        }
-    }
-
-    public static void logRequest(String s, Object... s1) {
-        if (requests()) {
-            logger.log(Level.INFO, "REQUEST: " + s, s1);
-        }
-    }
-
-    public static void logResponse(Supplier<String> supplier) {
-        if (requests()) {
-            logger.log(Level.INFO, "RESPONSE: " + supplier.get());
-        }
-    }
-
-    public static void logHeaders(String s, Object... s1) {
-        if (headers()) {
-            logger.log(Level.INFO, "HEADERS: " + s, s1);
-        }
-    }
-
-    public static boolean loggingFrame(Class<? extends Http2Frame> clazz) {
-        if (frametypes == ALL) {
-            return true;
-        }
-        if (clazz == DataFrame.class) {
-            return (frametypes & DATA) != 0;
-        } else if (clazz == WindowUpdateFrame.class) {
-            return (frametypes & WINDOW_UPDATES) != 0;
-        } else {
-            return (frametypes & CONTROL) != 0;
-        }
-    }
-
-    public static void logFrames(Http2Frame f, String direction) {
-        if (frames() && loggingFrame(f.getClass())) {
-            logger.log(Level.INFO, "FRAME: " + direction + ": " + f.toString());
-        }
-    }
-
-    public static void logParams(SSLParameters p) {
-        if (!Log.ssl()) {
-            return;
-        }
-
-        if (p == null) {
-            Log.logSSL("SSLParameters: Null params");
-            return;
-        }
-
-        final StringBuilder sb = new StringBuilder("SSLParameters:");
-        final List<Object> params = new ArrayList<>();
-        if (p.getCipherSuites() != null) {
-            for (String cipher : p.getCipherSuites()) {
-                sb.append("\n    cipher: {")
-                        .append(params.size()).append("}");
-                params.add(cipher);
-            }
-        }
-
-        // SSLParameters.getApplicationProtocols() can't return null
-        // JDK 8 EXCL START
-        for (String approto : p.getApplicationProtocols()) {
-            sb.append("\n    application protocol: {")
-                    .append(params.size()).append("}");
-            params.add(approto);
-        }
-        // JDK 8 EXCL END
-
-        if (p.getProtocols() != null) {
-            for (String protocol : p.getProtocols()) {
-                sb.append("\n    protocol: {")
-                        .append(params.size()).append("}");
-                params.add(protocol);
-            }
-        }
-
-        if (p.getServerNames() != null) {
-            for (SNIServerName sname : p.getServerNames()) {
-                sb.append("\n    server name: {")
-                        .append(params.size()).append("}");
-                params.add(sname.toString());
-            }
-        }
-        sb.append('\n');
-
-        Log.logSSL(sb.toString(), params.toArray());
-    }
-
-    public static void dumpHeaders(StringBuilder sb, String prefix, HttpHeaders headers) {
-        if (headers != null) {
-            Map<String,List<String>> h = headers.map();
-            Set<Map.Entry<String,List<String>>> entries = h.entrySet();
-            for (Map.Entry<String,List<String>> entry : entries) {
-                String key = entry.getKey();
-                List<String> values = entry.getValue();
-                sb.append(prefix).append(key).append(":");
-                for (String value : values) {
-                    sb.append(' ').append(value);
-                }
-                sb.append('\n');
-            }
-        }
-    }
-
-
-    // not instantiable
-    private Log() {}
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/MinimalFuture.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-/*
- * 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 java.net.http.internal.common;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static java.util.Objects.requireNonNull;
-
-/*
- * A CompletableFuture which does not allow any obtrusion logic.
- * All methods of CompletionStage return instances of this class.
- */
-public final class MinimalFuture<T> extends CompletableFuture<T> {
-
-    @FunctionalInterface
-    public interface ExceptionalSupplier<U> {
-        U get() throws Throwable;
-    }
-
-    private final static AtomicLong TOKENS = new AtomicLong();
-    private final long id;
-
-    public static <U> MinimalFuture<U> completedFuture(U value) {
-        MinimalFuture<U> f = new MinimalFuture<>();
-        f.complete(value);
-        return f;
-    }
-
-    public static <U> CompletableFuture<U> failedFuture(Throwable ex) {
-        requireNonNull(ex);
-        MinimalFuture<U> f = new MinimalFuture<>();
-        f.completeExceptionally(ex);
-        return f;
-    }
-
-    public static <U> CompletableFuture<U> supply(ExceptionalSupplier<U> supplier) {
-        CompletableFuture<U> cf = new MinimalFuture<>();
-        try {
-            U value = supplier.get();
-            cf.complete(value);
-        } catch (Throwable t) {
-            cf.completeExceptionally(t);
-        }
-        return cf;
-    }
-
-    public MinimalFuture() {
-        super();
-        this.id = TOKENS.incrementAndGet();
-    }
-
-    @Override
-    public <U> MinimalFuture<U> newIncompleteFuture() {
-        return new MinimalFuture<>();
-    }
-
-    @Override
-    public void obtrudeValue(T value) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void obtrudeException(Throwable ex) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + " (id=" + id +")";
-    }
-
-    public static <U> MinimalFuture<U> of(CompletionStage<U> stage) {
-        MinimalFuture<U> cf = new MinimalFuture<>();
-        stage.whenComplete((r,t) -> complete(cf, r, t));
-        return cf;
-    }
-
-    private static <U> void complete(CompletableFuture<U> cf, U result, Throwable t) {
-        if (t == null) {
-            cf.complete(result);
-        } else {
-            cf.completeExceptionally(t);
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/Pair.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +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 java.net.http.internal.common;
-
-/**
- * A simple paired value class
- */
-public final class Pair<T, U> {
-
-    public Pair(T first, U second) {
-        this.second = second;
-        this.first = first;
-    }
-
-    public final T first;
-    public final U second;
-
-    // Because 'pair()' is shorter than 'new Pair<>()'.
-    // Sometimes this difference might be very significant (especially in a
-    // 80-ish characters boundary). Sorry diamond operator.
-    public static <T, U> Pair<T, U> pair(T first, U second) {
-        return new Pair<>(first, second);
-    }
-
-    @Override
-    public String toString() {
-        return "(" + first + ", " + second + ")";
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/SSLFlowDelegate.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,906 +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 java.net.http.internal.common;
-
-import java.net.http.internal.common.SubscriberWrapper.SchedulingAction;
-
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLEngineResult.Status;
-import javax.net.ssl.SSLException;
-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.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Implements SSL using two SubscriberWrappers.
- *
- * <p> Constructor takes two Flow.Subscribers: one that receives the network
- * data (after it has been encrypted by SSLFlowDelegate) data, and one that
- * receives the application data (before it has been encrypted by SSLFlowDelegate).
- *
- * <p> Methods upstreamReader() and upstreamWriter() return the corresponding
- * Flow.Subscribers containing Flows for the encrypted/decrypted upstream data.
- * See diagram below.
- *
- * <p> How Flow.Subscribers are used in this class, and where they come from:
- * <pre>
- * {@code
- *
- *
- *
- * --------->  data flow direction
- *
- *
- *                         +------------------+
- *        upstreamWriter   |                  | downWriter
- *        ---------------> |                  | ------------>
- *  obtained from this     |                  | supplied to constructor
- *                         | SSLFlowDelegate  |
- *        downReader       |                  | upstreamReader
- *        <--------------- |                  | <--------------
- * supplied to constructor |                  | obtained from this
- *                         +------------------+
- * }
- * </pre>
- */
-public class SSLFlowDelegate {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger debug =
-            Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    final Executor exec;
-    final Reader reader;
-    final Writer writer;
-    final SSLEngine engine;
-    final String tubeName; // hack
-    private final CompletableFuture<Void> cf;
-    final CompletableFuture<String> alpnCF; // completes on initial handshake
-    final static ByteBuffer SENTINEL = Utils.EMPTY_BYTEBUFFER;
-    volatile boolean close_notify_received;
-
-    /**
-     * Creates an SSLFlowDelegate fed from two Flow.Subscribers. Each
-     * Flow.Subscriber requires an associated {@link CompletableFuture}
-     * for errors that need to be signaled from downstream to upstream.
-     */
-    public SSLFlowDelegate(SSLEngine engine,
-                           Executor exec,
-                           Subscriber<? super List<ByteBuffer>> downReader,
-                           Subscriber<? super List<ByteBuffer>> downWriter)
-    {
-        this.tubeName = String.valueOf(downWriter);
-        this.reader = new Reader();
-        this.writer = new Writer();
-        this.engine = engine;
-        this.exec = exec;
-        this.handshakeState = new AtomicInteger(NOT_HANDSHAKING);
-        CompletableFuture<Void> cs = CompletableFuture.allOf(
-                reader.completion(), writer.completion()).thenRun(this::normalStop);
-        this.cf = MinimalFuture.of(cs);
-        this.alpnCF = new MinimalFuture<>();
-
-        // connect the Reader to the downReader and the
-        // Writer to the downWriter.
-        connect(downReader, downWriter);
-
-        //Monitor.add(this::monitor);
-    }
-
-    /**
-     * Returns true if the SSLFlowDelegate has detected a TLS
-     * close_notify from the server.
-     * @return true, if a close_notify was detected.
-     */
-    public boolean closeNotifyReceived() {
-        return close_notify_received;
-    }
-
-    /**
-     * Connects the read sink (downReader) to the SSLFlowDelegate Reader,
-     * and the write sink (downWriter) to the SSLFlowDelegate Writer.
-     * Called from within the constructor. Overwritten by SSLTube.
-     *
-     * @param downReader  The left hand side read sink (typically, the
-     *                    HttpConnection read subscriber).
-     * @param downWriter  The right hand side write sink (typically
-     *                    the SocketTube write subscriber).
-     */
-    void connect(Subscriber<? super List<ByteBuffer>> downReader,
-                 Subscriber<? super List<ByteBuffer>> downWriter) {
-        this.reader.subscribe(downReader);
-        this.writer.subscribe(downWriter);
-    }
-
-   /**
-    * Returns a CompletableFuture<String> which completes after
-    * the initial handshake completes, and which contains the negotiated
-    * alpn.
-    */
-    public CompletableFuture<String> alpn() {
-        return alpnCF;
-    }
-
-    private void setALPN() {
-        // Handshake is finished. So, can retrieve the ALPN now
-        if (alpnCF.isDone())
-            return;
-        String alpn = engine.getApplicationProtocol();
-        debug.log(Level.DEBUG, "setALPN = %s", alpn);
-        alpnCF.complete(alpn);
-    }
-
-    public String monitor() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("SSL: HS state: " + states(handshakeState));
-        sb.append(" Engine state: " + engine.getHandshakeStatus().toString());
-        sb.append(" LL : ");
-        synchronized(stateList) {
-            for (String s: stateList) {
-                sb.append(s).append(" ");
-            }
-        }
-        sb.append("\r\n");
-        sb.append("Reader:: ").append(reader.toString());
-        sb.append("\r\n");
-        sb.append("Writer:: ").append(writer.toString());
-        sb.append("\r\n===================================");
-        return sb.toString();
-    }
-
-    protected SchedulingAction enterReadScheduling() {
-        return SchedulingAction.CONTINUE;
-    }
-
-
-    /**
-     * Processing function for incoming data. Pass it thru SSLEngine.unwrap().
-     * Any decrypted buffers returned to be passed downstream.
-     * Status codes:
-     *     NEED_UNWRAP: do nothing. Following incoming data will contain
-     *                  any required handshake data
-     *     NEED_WRAP: call writer.addData() with empty buffer
-     *     NEED_TASK: delegate task to executor
-     *     BUFFER_OVERFLOW: allocate larger output buffer. Repeat unwrap
-     *     BUFFER_UNDERFLOW: keep buffer and wait for more data
-     *     OK: return generated buffers.
-     *
-     * Upstream subscription strategy is to try and keep no more than
-     * TARGET_BUFSIZE bytes in readBuf
-     */
-    class Reader extends SubscriberWrapper {
-        final SequentialScheduler scheduler;
-        static final int TARGET_BUFSIZE = 16 * 1024;
-        volatile ByteBuffer readBuf;
-        volatile boolean completing = false;
-        final Object readBufferLock = new Object();
-        final System.Logger debugr =
-            Utils.getDebugLogger(this::dbgString, DEBUG);
-
-        class ReaderDownstreamPusher implements Runnable {
-            @Override public void run() { processData(); }
-        }
-
-        Reader() {
-            super();
-            scheduler = SequentialScheduler.synchronizedScheduler(
-                                                new ReaderDownstreamPusher());
-            this.readBuf = ByteBuffer.allocate(1024);
-            readBuf.limit(0); // keep in read mode
-        }
-
-        protected SchedulingAction enterScheduling() {
-            return enterReadScheduling();
-        }
-
-        public final String dbgString() {
-            return "SSL Reader(" + tubeName + ")";
-        }
-
-        /**
-         * entry point for buffers delivered from upstream Subscriber
-         */
-        @Override
-        public void incoming(List<ByteBuffer> buffers, boolean complete) {
-            debugr.log(Level.DEBUG, () -> "Adding " + Utils.remaining(buffers)
-                        + " bytes to read buffer");
-            addToReadBuf(buffers, complete);
-            scheduler.runOrSchedule();
-        }
-
-        @Override
-        public String toString() {
-            return "READER: " + super.toString() + " readBuf: " + readBuf.toString()
-                    + " count: " + count.toString();
-        }
-
-        private void reallocReadBuf() {
-            int sz = readBuf.capacity();
-            ByteBuffer newb = ByteBuffer.allocate(sz*2);
-            readBuf.flip();
-            Utils.copy(readBuf, newb);
-            readBuf = newb;
-        }
-
-        @Override
-        protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
-            if (readBuf.remaining() > TARGET_BUFSIZE) {
-                return 0;
-            } else {
-                return super.upstreamWindowUpdate(currentWindow, downstreamQsize);
-            }
-        }
-
-        // readBuf is kept ready for reading outside of this method
-        private void addToReadBuf(List<ByteBuffer> buffers, boolean complete) {
-            synchronized (readBufferLock) {
-                for (ByteBuffer buf : buffers) {
-                    readBuf.compact();
-                    while (readBuf.remaining() < buf.remaining())
-                        reallocReadBuf();
-                    readBuf.put(buf);
-                    readBuf.flip();
-                }
-                if (complete) {
-                    this.completing = complete;
-                }
-            }
-        }
-
-        void schedule() {
-            scheduler.runOrSchedule();
-        }
-
-        void stop() {
-            debugr.log(Level.DEBUG, "stop");
-            scheduler.stop();
-        }
-
-        AtomicInteger count = new AtomicInteger(0);
-
-        // work function where it all happens
-        void processData() {
-            try {
-                debugr.log(Level.DEBUG, () -> "processData: " + readBuf.remaining()
-                           + " bytes to unwrap "
-                           + states(handshakeState)
-                           + ", " + engine.getHandshakeStatus());
-                int len;
-                boolean complete = false;
-                while ((len = readBuf.remaining()) > 0) {
-                    boolean handshaking = false;
-                    try {
-                        EngineResult result;
-                        synchronized (readBufferLock) {
-                            complete = this.completing;
-                            result = unwrapBuffer(readBuf);
-                            debugr.log(Level.DEBUG, "Unwrapped: %s", result.result);
-                        }
-                        if (result.bytesProduced() > 0) {
-                            debugr.log(Level.DEBUG, "sending %d", result.bytesProduced());
-                            count.addAndGet(result.bytesProduced());
-                            outgoing(result.destBuffer, false);
-                        }
-                        if (result.status() == Status.BUFFER_UNDERFLOW) {
-                            debugr.log(Level.DEBUG, "BUFFER_UNDERFLOW");
-                            // not enough data in the read buffer...
-                            requestMore();
-                            synchronized (readBufferLock) {
-                                // check if we have received some data
-                                if (readBuf.remaining() > len) continue;
-                                return;
-                            }
-                        }
-                        if (complete && result.status() == Status.CLOSED) {
-                            debugr.log(Level.DEBUG, "Closed: completing");
-                            outgoing(Utils.EMPTY_BB_LIST, true);
-                            return;
-                        }
-                        if (result.handshaking() && !complete) {
-                            debugr.log(Level.DEBUG, "handshaking");
-                            doHandshake(result, READER);
-                            resumeActivity();
-                            handshaking = true;
-                        } else {
-                            if ((handshakeState.getAndSet(NOT_HANDSHAKING) & ~DOING_TASKS) == HANDSHAKING) {
-                                setALPN();
-                                handshaking = false;
-                                resumeActivity();
-                            }
-                        }
-                    } catch (IOException ex) {
-                        errorCommon(ex);
-                        handleError(ex);
-                    }
-                    if (handshaking && !complete)
-                        return;
-                }
-                if (!complete) {
-                    synchronized (readBufferLock) {
-                        complete = this.completing && !readBuf.hasRemaining();
-                    }
-                }
-                if (complete) {
-                    debugr.log(Level.DEBUG, "completing");
-                    // Complete the alpnCF, if not already complete, regardless of
-                    // whether or not the ALPN is available, there will be no more
-                    // activity.
-                    setALPN();
-                    outgoing(Utils.EMPTY_BB_LIST, true);
-                }
-            } catch (Throwable ex) {
-                errorCommon(ex);
-                handleError(ex);
-            }
-        }
-    }
-
-    /**
-     * Returns a CompletableFuture which completes after all activity
-     * in the delegate is terminated (whether normally or exceptionally).
-     *
-     * @return
-     */
-    public CompletableFuture<Void> completion() {
-        return cf;
-    }
-
-    public interface Monitorable {
-        public String getInfo();
-    }
-
-    public static class Monitor extends Thread {
-        final List<Monitorable> list;
-        static Monitor themon;
-
-        static {
-            themon = new Monitor();
-            themon.start(); // uncomment to enable Monitor
-        }
-
-        Monitor() {
-            super("Monitor");
-            setDaemon(true);
-            list = Collections.synchronizedList(new LinkedList<>());
-        }
-
-        void addTarget(Monitorable o) {
-            list.add(o);
-        }
-
-        public static void add(Monitorable o) {
-            themon.addTarget(o);
-        }
-
-        @Override
-        public void run() {
-            System.out.println("Monitor starting");
-            while (true) {
-                try {Thread.sleep(20*1000); } catch (Exception e) {}
-                synchronized (list) {
-                    for (Monitorable o : list) {
-                        System.out.println(o.getInfo());
-                        System.out.println("-------------------------");
-                    }
-                }
-                System.out.println("--o-o-o-o-o-o-o-o-o-o-o-o-o-o-");
-
-            }
-        }
-    }
-
-    /**
-     * Processing function for outgoing data. Pass it thru SSLEngine.wrap()
-     * Any encrypted buffers generated are passed downstream to be written.
-     * Status codes:
-     *     NEED_UNWRAP: call reader.addData() with empty buffer
-     *     NEED_WRAP: call addData() with empty buffer
-     *     NEED_TASK: delegate task to executor
-     *     BUFFER_OVERFLOW: allocate larger output buffer. Repeat wrap
-     *     BUFFER_UNDERFLOW: shouldn't happen on writing side
-     *     OK: return generated buffers
-     */
-    class Writer extends SubscriberWrapper {
-        final SequentialScheduler scheduler;
-        // queues of buffers received from upstream waiting
-        // to be processed by the SSLEngine
-        final List<ByteBuffer> writeList;
-        final System.Logger debugw =
-            Utils.getDebugLogger(this::dbgString, DEBUG);
-        volatile boolean completing;
-        boolean completed; // only accessed in processData
-
-        class WriterDownstreamPusher extends SequentialScheduler.CompleteRestartableTask {
-            @Override public void run() { processData(); }
-        }
-
-        Writer() {
-            super();
-            writeList = Collections.synchronizedList(new LinkedList<>());
-            scheduler = new SequentialScheduler(new WriterDownstreamPusher());
-        }
-
-        @Override
-        protected void incoming(List<ByteBuffer> buffers, boolean complete) {
-            assert complete ? buffers ==  Utils.EMPTY_BB_LIST : true;
-            assert buffers != Utils.EMPTY_BB_LIST ? complete == false : true;
-            if (complete) {
-                debugw.log(Level.DEBUG, "adding SENTINEL");
-                completing = true;
-                writeList.add(SENTINEL);
-            } else {
-                writeList.addAll(buffers);
-            }
-            debugw.log(Level.DEBUG, () -> "added " + buffers.size()
-                        + " (" + Utils.remaining(buffers)
-                        + " bytes) to the writeList");
-            scheduler.runOrSchedule();
-        }
-
-        public final String dbgString() {
-            return "SSL Writer(" + tubeName + ")";
-        }
-
-        protected void onSubscribe() {
-            doHandshake(EngineResult.INIT, INIT);
-            resumeActivity();
-        }
-
-        void schedule() {
-            scheduler.runOrSchedule();
-        }
-
-        void stop() {
-            debugw.log(Level.DEBUG, "stop");
-            scheduler.stop();
-        }
-
-        @Override
-        public boolean closing() {
-            return closeNotifyReceived();
-        }
-
-        private boolean isCompleting() {
-            return completing;
-        }
-
-        @Override
-        protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
-            if (writeList.size() > 10)
-                return 0;
-            else
-                return super.upstreamWindowUpdate(currentWindow, downstreamQsize);
-        }
-
-        private boolean hsTriggered() {
-            synchronized(writeList) {
-                for (ByteBuffer b : writeList)
-                    if (b == HS_TRIGGER)
-                        return true;
-                return false;
-            }
-        }
-
-        private void processData() {
-            boolean completing = isCompleting();
-
-            try {
-                debugw.log(Level.DEBUG, () -> "processData(" + Utils.remaining(writeList) + ")");
-                while (Utils.remaining(writeList) > 0 || hsTriggered()
-                        || needWrap()) {
-                    ByteBuffer[] outbufs = writeList.toArray(Utils.EMPTY_BB_ARRAY);
-                    EngineResult result = wrapBuffers(outbufs);
-                    debugw.log(Level.DEBUG, "wrapBuffer returned %s", result.result);
-
-                    if (result.status() == Status.CLOSED) {
-                        if (result.bytesProduced() <= 0)
-                            return;
-
-                        if (!completing && !completed) {
-                            completing = this.completing = true;
-                            // There could still be some outgoing data in outbufs.
-                            writeList.add(SENTINEL);
-                        }
-                    }
-
-                    boolean handshaking = false;
-                    if (result.handshaking()) {
-                        debugw.log(Level.DEBUG, "handshaking");
-                        doHandshake(result, WRITER);
-                        handshaking = true;
-                    } else {
-                        if ((handshakeState.getAndSet(NOT_HANDSHAKING) & ~DOING_TASKS) == HANDSHAKING) {
-                            setALPN();
-                            resumeActivity();
-                        }
-                    }
-                    cleanList(writeList); // tidy up the source list
-                    sendResultBytes(result);
-                    if (handshaking && !completing) {
-                        if (writeList.isEmpty() && !result.needUnwrap()) {
-                            writer.addData(HS_TRIGGER);
-                        }
-                        if (needWrap()) continue;
-                        return;
-                    }
-                }
-                if (completing && Utils.remaining(writeList) == 0) {
-                    /*
-                    System.out.println("WRITER DOO 3");
-                    engine.closeOutbound();
-                    EngineResult result = wrapBuffers(Utils.EMPTY_BB_ARRAY);
-                    sendResultBytes(result);
-                    */
-                    if (!completed) {
-                        completed = true;
-                        writeList.clear();
-                        outgoing(Utils.EMPTY_BB_LIST, true);
-                    }
-                    return;
-                }
-                if (writeList.isEmpty() && needWrap()) {
-                    writer.addData(HS_TRIGGER);
-                }
-            } catch (Throwable ex) {
-                errorCommon(ex);
-                handleError(ex);
-            }
-        }
-
-        private boolean needWrap() {
-            return engine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP;
-        }
-
-        private void sendResultBytes(EngineResult result) {
-            if (result.bytesProduced() > 0) {
-                debugw.log(Level.DEBUG, "Sending %d bytes downstream",
-                           result.bytesProduced());
-                outgoing(result.destBuffer, false);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "WRITER: " + super.toString() +
-                    " writeList size " + Integer.toString(writeList.size());
-                    //" writeList: " + writeList.toString();
-        }
-    }
-
-    private void handleError(Throwable t) {
-        debug.log(Level.DEBUG, "handleError", t);
-        cf.completeExceptionally(t);
-        // no-op if already completed
-        alpnCF.completeExceptionally(t);
-        reader.stop();
-        writer.stop();
-    }
-
-    private void normalStop() {
-        reader.stop();
-        writer.stop();
-    }
-
-    private void cleanList(List<ByteBuffer> l) {
-        synchronized (l) {
-            Iterator<ByteBuffer> iter = l.iterator();
-            while (iter.hasNext()) {
-                ByteBuffer b = iter.next();
-                if (!b.hasRemaining() && b != SENTINEL) {
-                    iter.remove();
-                }
-            }
-        }
-    }
-
-    /**
-     * States for handshake. We avoid races when accessing/updating the AtomicInt
-     * because updates always schedule an additional call to both the read()
-     * and write() functions.
-     */
-    private static final int NOT_HANDSHAKING = 0;
-    private static final int HANDSHAKING = 1;
-    private static final int INIT = 2;
-    private static final int DOING_TASKS = 4; // bit added to above state
-    private static final ByteBuffer HS_TRIGGER = ByteBuffer.allocate(0);
-
-    private static final int READER = 1;
-    private static final int WRITER = 2;
-
-    private static String states(AtomicInteger state) {
-        int s = state.get();
-        StringBuilder sb = new StringBuilder();
-        int x = s & ~DOING_TASKS;
-        switch (x) {
-            case NOT_HANDSHAKING:
-                sb.append(" NOT_HANDSHAKING ");
-                break;
-            case HANDSHAKING:
-                sb.append(" HANDSHAKING ");
-                break;
-            case INIT:
-                sb.append(" INIT ");
-                break;
-            default:
-                throw new InternalError();
-        }
-        if ((s & DOING_TASKS) > 0)
-            sb.append("|DOING_TASKS");
-        return sb.toString();
-    }
-
-    private void resumeActivity() {
-        reader.schedule();
-        writer.schedule();
-    }
-
-    final AtomicInteger handshakeState;
-    final ConcurrentLinkedQueue<String> stateList = new ConcurrentLinkedQueue<>();
-
-    private void doHandshake(EngineResult r, int caller) {
-        int s = handshakeState.getAndAccumulate(HANDSHAKING, (current, update) -> update | (current & DOING_TASKS));
-        stateList.add(r.handshakeStatus().toString());
-        stateList.add(Integer.toString(caller));
-        switch (r.handshakeStatus()) {
-            case NEED_TASK:
-                if ((s & DOING_TASKS) > 0) // someone else was doing tasks
-                    return;
-                List<Runnable> tasks = obtainTasks();
-                executeTasks(tasks);
-                break;
-            case NEED_WRAP:
-                writer.addData(HS_TRIGGER);
-                break;
-            case NEED_UNWRAP:
-            case NEED_UNWRAP_AGAIN:
-                // do nothing else
-                break;
-            default:
-                throw new InternalError("Unexpected handshake status:"
-                                        + r.handshakeStatus());
-        }
-    }
-
-    private List<Runnable> obtainTasks() {
-        List<Runnable> l = new ArrayList<>();
-        Runnable r;
-        while ((r = engine.getDelegatedTask()) != null) {
-            l.add(r);
-        }
-        return l;
-    }
-
-    private void executeTasks(List<Runnable> tasks) {
-        exec.execute(() -> {
-            try {
-                handshakeState.getAndUpdate((current) -> current | DOING_TASKS);
-                List<Runnable> nextTasks = tasks;
-                do {
-                    nextTasks.forEach(Runnable::run);
-                    if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
-                        nextTasks = obtainTasks();
-                    } else {
-                        break;
-                    }
-                } while (true);
-                handshakeState.getAndUpdate((current) -> current & ~DOING_TASKS);
-                writer.addData(HS_TRIGGER);
-                resumeActivity();
-            } catch (Throwable t) {
-                handleError(t);
-            }
-        });
-    }
-
-
-    EngineResult unwrapBuffer(ByteBuffer src) throws IOException {
-        ByteBuffer dst = getAppBuffer();
-        while (true) {
-            SSLEngineResult sslResult = engine.unwrap(src, dst);
-            switch (sslResult.getStatus()) {
-                case BUFFER_OVERFLOW:
-                    // may happen only if app size buffer was changed.
-                    // get it again if app buffer size changed
-                    int appSize = engine.getSession().getApplicationBufferSize();
-                    ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
-                    dst.flip();
-                    b.put(dst);
-                    dst = b;
-                    break;
-                case CLOSED:
-                    return doClosure(new EngineResult(sslResult));
-                case BUFFER_UNDERFLOW:
-                    // handled implicitly by compaction/reallocation of readBuf
-                    return new EngineResult(sslResult);
-                case OK:
-                     dst.flip();
-                     return new EngineResult(sslResult, dst);
-            }
-        }
-    }
-
-    // FIXME: acknowledge a received CLOSE request from peer
-    EngineResult doClosure(EngineResult r) throws IOException {
-        debug.log(Level.DEBUG,
-                "doClosure(%s): %s [isOutboundDone: %s, isInboundDone: %s]",
-                r.result, engine.getHandshakeStatus(),
-                engine.isOutboundDone(), engine.isInboundDone());
-        if (engine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
-            // we have received TLS close_notify and need to send
-            // an acknowledgement back. We're calling doHandshake
-            // to finish the close handshake.
-            if (engine.isInboundDone() && !engine.isOutboundDone()) {
-                debug.log(Level.DEBUG, "doClosure: close_notify received");
-                close_notify_received = true;
-                doHandshake(r, READER);
-            }
-        }
-        return r;
-    }
-
-    /**
-     * Returns the upstream Flow.Subscriber of the reading (incoming) side.
-     * This flow must be given the encrypted data read from upstream (eg socket)
-     * before it is decrypted.
-     */
-    public Flow.Subscriber<List<ByteBuffer>> upstreamReader() {
-        return reader;
-    }
-
-    /**
-     * Returns the upstream Flow.Subscriber of the writing (outgoing) side.
-     * This flow contains the plaintext data before it is encrypted.
-     */
-    public Flow.Subscriber<List<ByteBuffer>> upstreamWriter() {
-        return writer;
-    }
-
-    public boolean resumeReader() {
-        return reader.signalScheduling();
-    }
-
-    public void resetReaderDemand() {
-        reader.resetDownstreamDemand();
-    }
-
-    static class EngineResult {
-        final SSLEngineResult result;
-        final ByteBuffer destBuffer;
-
-        // normal result
-        EngineResult(SSLEngineResult result) {
-            this(result, null);
-        }
-
-        EngineResult(SSLEngineResult result, ByteBuffer destBuffer) {
-            this.result = result;
-            this.destBuffer = destBuffer;
-        }
-
-        // Special result used to trigger handshaking in constructor
-        static EngineResult INIT =
-            new EngineResult(
-                new SSLEngineResult(SSLEngineResult.Status.OK, HandshakeStatus.NEED_WRAP, 0, 0));
-
-        boolean handshaking() {
-            HandshakeStatus s = result.getHandshakeStatus();
-            return s != HandshakeStatus.FINISHED
-                   && s != HandshakeStatus.NOT_HANDSHAKING
-                   && result.getStatus() != Status.CLOSED;
-        }
-
-        boolean needUnwrap() {
-            HandshakeStatus s = result.getHandshakeStatus();
-            return s == HandshakeStatus.NEED_UNWRAP;
-        }
-
-
-        int bytesConsumed() {
-            return result.bytesConsumed();
-        }
-
-        int bytesProduced() {
-            return result.bytesProduced();
-        }
-
-        SSLEngineResult.HandshakeStatus handshakeStatus() {
-            return result.getHandshakeStatus();
-        }
-
-        SSLEngineResult.Status status() {
-            return result.getStatus();
-        }
-    }
-
-    public ByteBuffer getNetBuffer() {
-        return ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
-    }
-
-    private ByteBuffer getAppBuffer() {
-        return ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
-    }
-
-    final String dbgString() {
-        return "SSLFlowDelegate(" + tubeName + ")";
-    }
-
-    @SuppressWarnings("fallthrough")
-    EngineResult wrapBuffers(ByteBuffer[] src) throws SSLException {
-        debug.log(Level.DEBUG, () -> "wrapping "
-                    + Utils.remaining(src) + " bytes");
-        ByteBuffer dst = getNetBuffer();
-        while (true) {
-            SSLEngineResult sslResult = engine.wrap(src, dst);
-            debug.log(Level.DEBUG, () -> "SSLResult: " + sslResult);
-            switch (sslResult.getStatus()) {
-                case BUFFER_OVERFLOW:
-                    // Shouldn't happen. We allocated buffer with packet size
-                    // get it again if net buffer size was changed
-                    debug.log(Level.DEBUG, "BUFFER_OVERFLOW");
-                    int appSize = engine.getSession().getApplicationBufferSize();
-                    ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
-                    dst.flip();
-                    b.put(dst);
-                    dst = b;
-                    break; // try again
-                case CLOSED:
-                    debug.log(Level.DEBUG, "CLOSED");
-                    // fallthrough. There could be some remaining data in dst.
-                    // CLOSED will be handled by the caller.
-                case OK:
-                    dst.flip();
-                    final ByteBuffer dest = dst;
-                    debug.log(Level.DEBUG, () -> "OK => produced: "
-                                           + dest.remaining()
-                                           + " not wrapped: "
-                                           + Utils.remaining(src));
-                    return new EngineResult(sslResult, dest);
-                case BUFFER_UNDERFLOW:
-                    // Shouldn't happen.  Doesn't returns when wrap()
-                    // underflow handled externally
-                    // assert false : "Buffer Underflow";
-                    debug.log(Level.DEBUG, "BUFFER_UNDERFLOW");
-                    return new EngineResult(sslResult);
-                default:
-                    debug.log(Level.DEBUG, "ASSERT");
-                    assert false;
-            }
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/SSLTube.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,586 +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 java.net.http.internal.common;
-
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import java.net.http.internal.common.SubscriberWrapper.SchedulingAction;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED;
-
-/**
- * An implementation of FlowTube that wraps another FlowTube in an
- * SSL flow.
- * <p>
- * The following diagram shows a typical usage of the SSLTube, where
- * the SSLTube wraps a SocketTube on the right hand side, and is connected
- * to an HttpConnection on the left hand side.
- *
- * <preformatted>{@code
- *                  +----------  SSLTube -------------------------+
- *                  |                                             |
- *                  |                    +---SSLFlowDelegate---+  |
- *  HttpConnection  |                    |                     |  |   SocketTube
- *    read sink  <- SSLSubscriberW.   <- Reader <- upstreamR.() <---- read source
- *  (a subscriber)  |                    |    \         /      |  |  (a publisher)
- *                  |                    |     SSLEngine       |  |
- *  HttpConnection  |                    |    /         \      |  |   SocketTube
- *  write source -> SSLSubscriptionW. -> upstreamW.() -> Writer ----> write sink
- *  (a publisher)   |                    |                     |  |  (a subscriber)
- *                  |                    +---------------------+  |
- *                  |                                             |
- *                  +---------------------------------------------+
- * }</preformatted>
- */
-public class SSLTube implements FlowTube {
-
-    static final boolean DEBUG = Utils.DEBUG; // revisit: temporary developer's flag.
-    final System.Logger debug =
-            Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    private final FlowTube tube;
-    private final SSLSubscriberWrapper readSubscriber;
-    private final SSLSubscriptionWrapper writeSubscription;
-    private final SSLFlowDelegate sslDelegate;
-    private final SSLEngine engine;
-    private volatile boolean finished;
-
-    public SSLTube(SSLEngine engine, Executor executor, FlowTube tube) {
-        Objects.requireNonNull(engine);
-        Objects.requireNonNull(executor);
-        this.tube = Objects.requireNonNull(tube);
-        writeSubscription = new SSLSubscriptionWrapper();
-        readSubscriber = new SSLSubscriberWrapper();
-        this.engine = engine;
-        sslDelegate = new SSLTubeFlowDelegate(engine,
-                                              executor,
-                                              readSubscriber,
-                                              tube);
-    }
-
-    final class SSLTubeFlowDelegate extends SSLFlowDelegate {
-        SSLTubeFlowDelegate(SSLEngine engine, Executor executor,
-                            SSLSubscriberWrapper readSubscriber,
-                            FlowTube tube) {
-            super(engine, executor, readSubscriber, tube);
-        }
-        protected SchedulingAction enterReadScheduling() {
-            readSubscriber.processPendingSubscriber();
-            return SchedulingAction.CONTINUE;
-        }
-        void connect(Flow.Subscriber<? super List<ByteBuffer>> downReader,
-                     Flow.Subscriber<? super List<ByteBuffer>> downWriter) {
-            assert downWriter == tube;
-            assert downReader == readSubscriber;
-
-            // Connect the read sink first. That's the left-hand side
-            // downstream subscriber from the HttpConnection (or more
-            // accurately, the SSLSubscriberWrapper that will wrap it
-            // when SSLTube::connectFlows is called.
-            reader.subscribe(downReader);
-
-            // Connect the right hand side tube (the socket tube).
-            //
-            // The SSLFlowDelegate.writer publishes ByteBuffer to
-            // the SocketTube for writing on the socket, and the
-            // SSLFlowDelegate::upstreamReader subscribes to the
-            // SocketTube to receive ByteBuffers read from the socket.
-            //
-            // Basically this method is equivalent to:
-            //     // connect the read source:
-            //     //   subscribe the SSLFlowDelegate upstream reader
-            //     //   to the socket tube publisher.
-            //     tube.subscribe(upstreamReader());
-            //     // connect the write sink:
-            //     //   subscribe the socket tube write subscriber
-            //     //   with the SSLFlowDelegate downstream writer.
-            //     writer.subscribe(tube);
-            tube.connectFlows(FlowTube.asTubePublisher(writer),
-                              FlowTube.asTubeSubscriber(upstreamReader()));
-
-            // Finally connect the write source. That's the left
-            // hand side publisher which will push ByteBuffer for
-            // writing and encryption to the SSLFlowDelegate.
-            // The writeSubscription is in fact the SSLSubscriptionWrapper
-            // that will wrap the subscription provided by the
-            // HttpConnection publisher when SSLTube::connectFlows
-            // is called.
-            upstreamWriter().onSubscribe(writeSubscription);
-        }
-    }
-
-    public CompletableFuture<String> getALPN() {
-        return sslDelegate.alpn();
-    }
-
-    @Override
-    public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
-        readSubscriber.dropSubscription();
-        readSubscriber.setDelegate(s);
-        s.onSubscribe(readSubscription);
-    }
-
-    /**
-     * Tells whether, or not, this FlowTube has finished receiving data.
-     *
-     * @return true when one of this FlowTube Subscriber's OnError or onComplete
-     * methods have been invoked
-     */
-    @Override
-    public boolean isFinished() {
-        return finished;
-    }
-
-    private volatile Flow.Subscription readSubscription;
-
-    // The DelegateWrapper wraps a subscribed {@code Flow.Subscriber} and
-    // tracks the subscriber's state. In particular it makes sure that
-    // onComplete/onError are not called before onSubscribed.
-    final static class DelegateWrapper implements FlowTube.TubeSubscriber {
-        private final FlowTube.TubeSubscriber delegate;
-        private final System.Logger debug;
-        volatile boolean subscribedCalled;
-        volatile boolean subscribedDone;
-        volatile boolean completed;
-        volatile Throwable error;
-        DelegateWrapper(Flow.Subscriber<? super List<ByteBuffer>> delegate,
-                        System.Logger debug) {
-            this.delegate = FlowTube.asTubeSubscriber(delegate);
-            this.debug = debug;
-        }
-
-        @Override
-        public void dropSubscription() {
-            if (subscribedCalled && !completed) {
-                delegate.dropSubscription();
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            assert subscribedCalled;
-            delegate.onNext(item);
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            onSubscribe(delegate::onSubscribe, subscription);
-        }
-
-        private void onSubscribe(Consumer<Flow.Subscription> method,
-                                 Flow.Subscription subscription) {
-            subscribedCalled = true;
-            method.accept(subscription);
-            Throwable x;
-            boolean finished;
-            synchronized (this) {
-                subscribedDone = true;
-                x = error;
-                finished = completed;
-            }
-            if (x != null) {
-                debug.log(Level.DEBUG,
-                          "Subscriber completed before subscribe: forwarding %s",
-                          (Object)x);
-                delegate.onError(x);
-            } else if (finished) {
-                debug.log(Level.DEBUG,
-                          "Subscriber completed before subscribe: calling onComplete()");
-                delegate.onComplete();
-            }
-        }
-
-        @Override
-        public void onError(Throwable t) {
-            if (completed) {
-                debug.log(Level.DEBUG,
-                          "Subscriber already completed: ignoring %s",
-                          (Object)t);
-                return;
-            }
-            boolean subscribed;
-            synchronized (this) {
-                if (completed) return;
-                error = t;
-                completed = true;
-                subscribed = subscribedDone;
-            }
-            if (subscribed) {
-                delegate.onError(t);
-            } else {
-                debug.log(Level.DEBUG,
-                          "Subscriber not yet subscribed: stored %s",
-                          (Object)t);
-            }
-        }
-
-        @Override
-        public void onComplete() {
-            if (completed) return;
-            boolean subscribed;
-            synchronized (this) {
-                if (completed) return;
-                completed = true;
-                subscribed = subscribedDone;
-            }
-            if (subscribed) {
-                debug.log(Level.DEBUG, "DelegateWrapper: completing subscriber");
-                delegate.onComplete();
-            } else {
-                debug.log(Level.DEBUG,
-                          "Subscriber not yet subscribed: stored completed=true");
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "DelegateWrapper:" + delegate.toString();
-        }
-
-    }
-
-    // Used to read data from the SSLTube.
-    final class SSLSubscriberWrapper implements FlowTube.TubeSubscriber {
-        private AtomicReference<DelegateWrapper> pendingDelegate =
-                new AtomicReference<>();
-        private volatile DelegateWrapper subscribed;
-        private volatile boolean onCompleteReceived;
-        private final AtomicReference<Throwable> errorRef
-                = new AtomicReference<>();
-
-        // setDelegate can be called asynchronously when the SSLTube flow
-        // is connected. At this time the permanent subscriber (this class)
-        // may already be subscribed (readSubscription != null) or not.
-        // 1. If it's already subscribed (readSubscription != null), we
-        //    are going to signal the SSLFlowDelegate reader, and make sure
-        //    onSubscribed is called within the reader flow
-        // 2. If it's not yet subscribed (readSubscription == null), then
-        //    we're going to wait for onSubscribe to be called.
-        //
-        void setDelegate(Flow.Subscriber<? super List<ByteBuffer>> delegate) {
-            debug.log(Level.DEBUG, "SSLSubscriberWrapper (reader) got delegate: %s",
-                      delegate);
-            assert delegate != null;
-            DelegateWrapper delegateWrapper = new DelegateWrapper(delegate, debug);
-            DelegateWrapper previous;
-            Flow.Subscription subscription;
-            boolean handleNow;
-            synchronized (this) {
-                previous = pendingDelegate.getAndSet(delegateWrapper);
-                subscription = readSubscription;
-                handleNow = this.errorRef.get() != null || finished;
-            }
-            if (previous != null) {
-                previous.dropSubscription();
-            }
-            if (subscription == null) {
-                debug.log(Level.DEBUG, "SSLSubscriberWrapper (reader) no subscription yet");
-                return;
-            }
-            if (handleNow || !sslDelegate.resumeReader()) {
-                processPendingSubscriber();
-            }
-        }
-
-        // Can be called outside of the flow if an error has already been
-        // raise. Otherwise, must be called within the SSLFlowDelegate
-        // downstream reader flow.
-        // If there is a subscription, and if there is a pending delegate,
-        // calls dropSubscription() on the previous delegate (if any),
-        // then subscribe the pending delegate.
-        void processPendingSubscriber() {
-            Flow.Subscription subscription;
-            DelegateWrapper delegateWrapper, previous;
-            synchronized (this) {
-                delegateWrapper = pendingDelegate.get();
-                if (delegateWrapper == null) return;
-                subscription = readSubscription;
-                previous = subscribed;
-            }
-            if (subscription == null) {
-                debug.log(Level.DEBUG,
-                         "SSLSubscriberWrapper (reader) %s",
-                         "processPendingSubscriber: no subscription yet");
-                return;
-            }
-            delegateWrapper = pendingDelegate.getAndSet(null);
-            if (delegateWrapper == null) return;
-            if (previous != null) {
-                previous.dropSubscription();
-            }
-            onNewSubscription(delegateWrapper, subscription);
-        }
-
-        @Override
-        public void dropSubscription() {
-            DelegateWrapper subscriberImpl = subscribed;
-            if (subscriberImpl != null) {
-                subscriberImpl.dropSubscription();
-            }
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            debug.log(Level.DEBUG,
-                      "SSLSubscriberWrapper (reader) onSubscribe(%s)",
-                      subscription);
-            onSubscribeImpl(subscription);
-        }
-
-        // called in the reader flow, from onSubscribe.
-        private void onSubscribeImpl(Flow.Subscription subscription) {
-            assert subscription != null;
-            DelegateWrapper subscriberImpl, pending;
-            synchronized (this) {
-                readSubscription = subscription;
-                subscriberImpl = subscribed;
-                pending = pendingDelegate.get();
-            }
-
-            if (subscriberImpl == null && pending == null) {
-                debug.log(Level.DEBUG,
-                      "SSLSubscriberWrapper (reader) onSubscribeImpl: %s",
-                      "no delegate yet");
-                return;
-            }
-
-            if (pending == null) {
-                // There is no pending delegate, but we have a previously
-                // subscribed delegate. This is obviously a re-subscribe.
-                // We are in the downstream reader flow, so we should call
-                // onSubscribe directly.
-                debug.log(Level.DEBUG,
-                      "SSLSubscriberWrapper (reader) onSubscribeImpl: %s",
-                      "resubscribing");
-                onNewSubscription(subscriberImpl, subscription);
-            } else {
-                // We have some pending subscriber: subscribe it now that we have
-                // a subscription. If we already had a previous delegate then
-                // it will get a dropSubscription().
-                debug.log(Level.DEBUG,
-                      "SSLSubscriberWrapper (reader) onSubscribeImpl: %s",
-                      "subscribing pending");
-                processPendingSubscriber();
-            }
-        }
-
-        private void onNewSubscription(DelegateWrapper subscriberImpl,
-                                       Flow.Subscription subscription) {
-            assert subscriberImpl != null;
-            assert subscription != null;
-
-            Throwable failed;
-            boolean completed;
-            // reset any demand that may have been made by the previous
-            // subscriber
-            sslDelegate.resetReaderDemand();
-            // send the subscription to the subscriber.
-            subscriberImpl.onSubscribe(subscription);
-
-            // The following twisted logic is just here that we don't invoke
-            // onError before onSubscribe. It also prevents race conditions
-            // if onError is invoked concurrently with setDelegate.
-            synchronized (this) {
-                failed = this.errorRef.get();
-                completed = finished;
-                subscribed = subscriberImpl;
-            }
-            if (failed != null) {
-                subscriberImpl.onError(failed);
-            } else if (completed) {
-                subscriberImpl.onComplete();
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            subscribed.onNext(item);
-        }
-
-        public void onErrorImpl(Throwable throwable) {
-            // The following twisted logic is just here that we don't invoke
-            // onError before onSubscribe. It also prevents race conditions
-            // if onError is invoked concurrently with setDelegate.
-            // See setDelegate.
-
-            errorRef.compareAndSet(null, throwable);
-            Throwable failed = errorRef.get();
-            finished = true;
-            debug.log(Level.DEBUG, "%s: onErrorImpl: %s", this, throwable);
-            DelegateWrapper subscriberImpl;
-            synchronized (this) {
-                subscriberImpl = subscribed;
-            }
-            if (subscriberImpl != null) {
-                subscriberImpl.onError(failed);
-            } else {
-                debug.log(Level.DEBUG, "%s: delegate null, stored %s", this, failed);
-            }
-            // now if we have any pending subscriber, we should forward
-            // the error to them immediately as the read scheduler will
-            // already be stopped.
-            processPendingSubscriber();
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            assert !finished && !onCompleteReceived;
-            onErrorImpl(throwable);
-        }
-
-        private boolean handshaking() {
-            HandshakeStatus hs = engine.getHandshakeStatus();
-            return !(hs == NOT_HANDSHAKING || hs == FINISHED);
-        }
-
-        private boolean handshakeFailed() {
-            // sslDelegate can be null if we reach here
-            // during the initial handshake, as that happens
-            // within the SSLFlowDelegate constructor.
-            // In that case we will want to raise an exception.
-            return handshaking()
-                    && (sslDelegate == null
-                    || !sslDelegate.closeNotifyReceived());
-        }
-
-        @Override
-        public void onComplete() {
-            assert !finished && !onCompleteReceived;
-            onCompleteReceived = true;
-            DelegateWrapper subscriberImpl;
-            synchronized(this) {
-                subscriberImpl = subscribed;
-            }
-
-            if (handshakeFailed()) {
-                debug.log(Level.DEBUG,
-                        "handshake: %s, inbound done: %s outbound done: %s",
-                        engine.getHandshakeStatus(),
-                        engine.isInboundDone(),
-                        engine.isOutboundDone());
-                onErrorImpl(new SSLHandshakeException(
-                        "Remote host terminated the handshake"));
-            } else if (subscriberImpl != null) {
-                finished = true;
-                subscriberImpl.onComplete();
-            }
-            // now if we have any pending subscriber, we should complete
-            // them immediately as the read scheduler will already be stopped.
-            processPendingSubscriber();
-        }
-    }
-
-    @Override
-    public void connectFlows(TubePublisher writePub,
-                             TubeSubscriber readSub) {
-        debug.log(Level.DEBUG, "connecting flows");
-        readSubscriber.setDelegate(readSub);
-        writePub.subscribe(this);
-    }
-
-    /** Outstanding write demand from the SSL Flow Delegate. */
-    private final Demand writeDemand = new Demand();
-
-    final class SSLSubscriptionWrapper implements Flow.Subscription {
-
-        volatile Flow.Subscription delegate;
-
-        void setSubscription(Flow.Subscription sub) {
-            long demand = writeDemand.get(); // FIXME: isn't it a racy way of passing the demand?
-            delegate = sub;
-            debug.log(Level.DEBUG, "setSubscription: demand=%d", demand);
-            if (demand > 0)
-                sub.request(demand);
-        }
-
-        @Override
-        public void request(long n) {
-            writeDemand.increase(n);
-            debug.log(Level.DEBUG, "request: n=%d", n);
-            Flow.Subscription sub = delegate;
-            if (sub != null && n > 0) {
-                sub.request(n);
-            }
-        }
-
-        @Override
-        public void cancel() {
-            // TODO:  no-op or error?
-        }
-    }
-
-    /* Subscriber - writing side */
-    @Override
-    public void onSubscribe(Flow.Subscription subscription) {
-        Objects.requireNonNull(subscription);
-        Flow.Subscription x = writeSubscription.delegate;
-        if (x != null)
-            x.cancel();
-
-        writeSubscription.setSubscription(subscription);
-    }
-
-    @Override
-    public void onNext(List<ByteBuffer> item) {
-        Objects.requireNonNull(item);
-        boolean decremented = writeDemand.tryDecrement();
-        assert decremented : "Unexpected writeDemand: ";
-        debug.log(Level.DEBUG,
-                "sending %d  buffers to SSL flow delegate", item.size());
-        sslDelegate.upstreamWriter().onNext(item);
-    }
-
-    @Override
-    public void onError(Throwable throwable) {
-        Objects.requireNonNull(throwable);
-        sslDelegate.upstreamWriter().onError(throwable);
-    }
-
-    @Override
-    public void onComplete() {
-        sslDelegate.upstreamWriter().onComplete();
-    }
-
-    @Override
-    public String toString() {
-        return dbgString();
-    }
-
-    final String dbgString() {
-        return "SSLTube(" + tube + ")";
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/SequentialScheduler.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,362 +0,0 @@
-/*
- * 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 java.net.http.internal.common;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static java.util.Objects.requireNonNull;
-
-/**
- * A scheduler of ( repeatable ) tasks that MUST be run sequentially.
- *
- * <p> This class can be used as a synchronization aid that assists a number of
- * parties in running a task in a mutually exclusive fashion.
- *
- * <p> To run the task, a party invokes {@code runOrSchedule}. To permanently
- * prevent the task from subsequent runs, the party invokes {@code stop}.
- *
- * <p> The parties can, but do not have to, operate in different threads.
- *
- * <p> The task can be either synchronous ( completes when its {@code run}
- * method returns ), or asynchronous ( completed when its
- * {@code DeferredCompleter} is explicitly completed ).
- *
- * <p> The next run of the task will not begin until the previous run has
- * finished.
- *
- * <p> The task may invoke {@code runOrSchedule} itself, which may be a normal
- * situation.
- */
-public final class SequentialScheduler {
-
-    /*
-       Since the task is fixed and known beforehand, no blocking synchronization
-       (locks, queues, etc.) is required. The job can be done solely using
-       nonblocking primitives.
-
-       The machinery below addresses two problems:
-
-         1. Running the task in a sequential order (no concurrent runs):
-
-                begin, end, begin, end...
-
-         2. Avoiding indefinite recursion:
-
-                begin
-                  end
-                    begin
-                      end
-                        ...
-
-       Problem #1 is solved with a finite state machine with 4 states:
-
-           BEGIN, AGAIN, END, and STOP.
-
-       Problem #2 is solved with a "state modifier" OFFLOAD.
-
-       Parties invoke `runOrSchedule()` to signal the task must run. A party
-       that has invoked `runOrSchedule()` either begins the task or exploits the
-       party that is either beginning the task or ending it.
-
-       The party that is trying to end the task either ends it or begins it
-       again.
-
-       To avoid indefinite recursion, before re-running the task the
-       TryEndDeferredCompleter sets the OFFLOAD bit, signalling to its "child"
-       TryEndDeferredCompleter that this ("parent") TryEndDeferredCompleter is
-       available and the "child" must offload the task on to the "parent". Then
-       a race begins. Whichever invocation of TryEndDeferredCompleter.complete
-       manages to unset OFFLOAD bit first does not do the work.
-
-       There is at most 1 thread that is beginning the task and at most 2
-       threads that are trying to end it: "parent" and "child". In case of a
-       synchronous task "parent" and "child" are the same thread.
-     */
-
-    /**
-     * An interface to signal the completion of a {@link RestartableTask}.
-     *
-     * <p> The invocation of {@code complete} completes the task. The invocation
-     * of {@code complete} may restart the task, if an attempt has previously
-     * been made to run the task while it was already running.
-     *
-     * @apiNote {@code DeferredCompleter} is useful when a task is not necessary
-     * complete when its {@code run} method returns, but will complete at a
-     * later time, and maybe in different thread. This type exists for
-     * readability purposes at use-sites only.
-     */
-    public static abstract class DeferredCompleter {
-
-        /** Extensible from this (outer) class ONLY. */
-        private DeferredCompleter() { }
-
-        /** Completes the task. Must be called once, and once only. */
-        public abstract void complete();
-    }
-
-    /**
-     * A restartable task.
-     */
-    @FunctionalInterface
-    public interface RestartableTask {
-
-        /**
-         * The body of the task.
-         *
-         * @param taskCompleter
-         *         A completer that must be invoked once, and only once,
-         *         when this task is logically finished
-         */
-        void run(DeferredCompleter taskCompleter);
-    }
-
-    /**
-     * A simple and self-contained task that completes once its {@code run}
-     * method returns.
-     */
-    public static abstract class CompleteRestartableTask
-        implements RestartableTask
-    {
-        @Override
-        public final void run(DeferredCompleter taskCompleter) {
-            try {
-                run();
-            } finally {
-                taskCompleter.complete();
-            }
-        }
-
-        /** The body of the task. */
-        protected abstract void run();
-    }
-
-    /**
-     * A task that runs its main loop within a synchronized block to provide
-     * memory visibility between runs. Since the main loop can't run concurrently,
-     * the lock shouldn't be contended and no deadlock should ever be possible.
-     */
-    public static final class SynchronizedRestartableTask
-            extends CompleteRestartableTask {
-
-        private final Runnable mainLoop;
-        private final Object lock = new Object();
-
-        public SynchronizedRestartableTask(Runnable mainLoop) {
-            this.mainLoop = mainLoop;
-        }
-
-        @Override
-        protected void run() {
-            synchronized(lock) {
-                mainLoop.run();
-            }
-        }
-    }
-
-    private static final int OFFLOAD =  1;
-    private static final int AGAIN   =  2;
-    private static final int BEGIN   =  4;
-    private static final int STOP    =  8;
-    private static final int END     = 16;
-
-    private final AtomicInteger state = new AtomicInteger(END);
-    private final RestartableTask restartableTask;
-    private final DeferredCompleter completer;
-    private final SchedulableTask schedulableTask;
-
-    /**
-     * An auxiliary task that starts the restartable task:
-     * {@code restartableTask.run(completer)}.
-     */
-    private final class SchedulableTask implements Runnable {
-        @Override
-        public void run() {
-            restartableTask.run(completer);
-        }
-    }
-
-    public SequentialScheduler(RestartableTask restartableTask) {
-        this.restartableTask = requireNonNull(restartableTask);
-        this.completer = new TryEndDeferredCompleter();
-        this.schedulableTask = new SchedulableTask();
-    }
-
-    /**
-     * Runs or schedules the task to be run.
-     *
-     * @implSpec The recursion which is possible here must be bounded:
-     *
-     *  <pre>{@code
-     *     this.runOrSchedule()
-     *         completer.complete()
-     *             this.runOrSchedule()
-     *                 ...
-     * }</pre>
-     *
-     * @implNote The recursion in this implementation has the maximum
-     * depth of 1.
-     */
-    public void runOrSchedule() {
-        runOrSchedule(schedulableTask, null);
-    }
-
-    /**
-     * Executes or schedules the task to be executed in the provided executor.
-     *
-     * <p> This method can be used when potential executing from a calling
-     * thread is not desirable.
-     *
-     * @param executor
-     *         An executor in which to execute the task, if the task needs
-     *         to be executed.
-     *
-     * @apiNote The given executor can be {@code null} in which case calling
-     * {@code runOrSchedule(null)} is strictly equivalent to calling
-     * {@code runOrSchedule()}.
-     */
-    public void runOrSchedule(Executor executor) {
-        runOrSchedule(schedulableTask, executor);
-    }
-
-    private void runOrSchedule(SchedulableTask task, Executor executor) {
-        while (true) {
-            int s = state.get();
-            if (s == END) {
-                if (state.compareAndSet(END, BEGIN)) {
-                    break;
-                }
-            } else if ((s & BEGIN) != 0) {
-                // Tries to change the state to AGAIN, preserving OFFLOAD bit
-                if (state.compareAndSet(s, AGAIN | (s & OFFLOAD))) {
-                    return;
-                }
-            } else if ((s & AGAIN) != 0 || s == STOP) {
-                /* In the case of AGAIN the scheduler does not provide
-                   happens-before relationship between actions prior to
-                   runOrSchedule() and actions that happen in task.run().
-                   The reason is that no volatile write is done in this case,
-                   and the call piggybacks on the call that has actually set
-                   AGAIN state. */
-                return;
-            } else {
-                // Non-existent state, or the one that cannot be offloaded
-                throw new InternalError(String.valueOf(s));
-            }
-        }
-        if (executor == null) {
-            task.run();
-        } else {
-            executor.execute(task);
-        }
-    }
-
-    /** The only concrete {@code DeferredCompleter} implementation. */
-    private class TryEndDeferredCompleter extends DeferredCompleter {
-
-        @Override
-        public void complete() {
-            while (true) {
-                int s;
-                while (((s = state.get()) & OFFLOAD) != 0) {
-                    // Tries to offload ending of the task to the parent
-                    if (state.compareAndSet(s, s & ~OFFLOAD)) {
-                        return;
-                    }
-                }
-                while (true) {
-                    if ((s & OFFLOAD) != 0) {
-                        /* OFFLOAD bit can never be observed here. Otherwise
-                           it would mean there is another invocation of
-                           "complete" that can run the task. */
-                        throw new InternalError(String.valueOf(s));
-                    }
-                    if (s == BEGIN) {
-                        if (state.compareAndSet(BEGIN, END)) {
-                            return;
-                        }
-                    } else if (s == AGAIN) {
-                        if (state.compareAndSet(AGAIN, BEGIN | OFFLOAD)) {
-                            break;
-                        }
-                    } else if (s == STOP) {
-                        return;
-                    } else if (s == END) {
-                        throw new IllegalStateException("Duplicate completion");
-                    } else {
-                        // Non-existent state
-                        throw new InternalError(String.valueOf(s));
-                    }
-                    s = state.get();
-                }
-                restartableTask.run(completer);
-            }
-        }
-    }
-
-    /**
-     * Tells whether, or not, this scheduler has been permanently stopped.
-     *
-     * <p> Should be used from inside the task to poll the status of the
-     * scheduler, pretty much the same way as it is done for threads:
-     * <pre>{@code
-     *     if (!Thread.currentThread().isInterrupted()) {
-     *         ...
-     *     }
-     * }</pre>
-     */
-    public boolean isStopped() {
-        return state.get() == STOP;
-    }
-
-    /**
-     * Stops this scheduler.  Subsequent invocations of {@code runOrSchedule}
-     * are effectively no-ops.
-     *
-     * <p> If the task has already begun, this invocation will not affect it,
-     * unless the task itself uses {@code isStopped()} method to check the state
-     * of the handler.
-     */
-    public void stop() {
-        state.set(STOP);
-    }
-
-    /**
-     * Returns a new {@code SequentialScheduler} that executes the provided
-     * {@code mainLoop} from within a {@link SynchronizedRestartableTask}.
-     *
-     * @apiNote This is equivalent to calling
-     * {@code new SequentialScheduler(new SynchronizedRestartableTask(mainLoop))}
-     * The main loop must not perform any blocking operation.
-     *
-     * @param mainLoop The main loop of the new sequential scheduler
-     * @return a new {@code SequentialScheduler} that executes the provided
-     * {@code mainLoop} from within a {@link SynchronizedRestartableTask}.
-     */
-    public static SequentialScheduler synchronizedScheduler(Runnable mainLoop) {
-        return new SequentialScheduler(new SynchronizedRestartableTask(mainLoop));
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/SubscriberWrapper.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,461 +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 java.net.http.internal.common;
-
-import java.io.Closeable;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * A wrapper for a Flow.Subscriber. This wrapper delivers data to the wrapped
- * Subscriber which is supplied to the constructor. This class takes care of
- * downstream flow control automatically and upstream flow control automatically
- * by default.
- * <p>
- * Processing is done by implementing the {@link #incoming(List, boolean)} method
- * which supplies buffers from upstream. This method (or any other method)
- * can then call the outgoing() method to deliver processed buffers downstream.
- * <p>
- * Upstream error signals are delivered downstream directly. Cancellation from
- * downstream is also propagated upstream immediately.
- * <p>
- * Each SubscriberWrapper has a {@link java.util.concurrent.CompletableFuture}{@code <Void>}
- * which propagates completion/errors from downstream to upstream. Normal completion
- * can only occur after onComplete() is called, but errors can be propagated upwards
- * at any time.
- */
-public abstract class SubscriberWrapper
-    implements FlowTube.TubeSubscriber, Closeable, Flow.Processor<List<ByteBuffer>,List<ByteBuffer>>
-                // TODO: SSLTube Subscriber will never change? Does this really need to be a TS?
-{
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger logger =
-            Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    public enum SchedulingAction { CONTINUE, RETURN, RESCHEDULE }
-
-    volatile Flow.Subscription upstreamSubscription;
-    final SubscriptionBase downstreamSubscription;
-    volatile boolean upstreamCompleted;
-    volatile boolean downstreamCompleted;
-    volatile boolean completionAcknowledged;
-    private volatile Subscriber<? super List<ByteBuffer>> downstreamSubscriber;
-    // processed byte to send to the downstream subscriber.
-    private final ConcurrentLinkedQueue<List<ByteBuffer>> outputQ;
-    private final CompletableFuture<Void> cf;
-    private final SequentialScheduler pushScheduler;
-    private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
-    final AtomicLong upstreamWindow = new AtomicLong(0);
-
-    /**
-     * Wraps the given downstream subscriber. For each call to {@link
-     * #onNext(List<ByteBuffer>) } the given filter function is invoked
-     * and the list (if not empty) returned is passed downstream.
-     *
-     * A {@code CompletableFuture} is supplied which can be used to signal an
-     * error from downstream and which terminates the wrapper or which signals
-     * completion of downstream activity which can be propagated upstream. Error
-     * completion can be signaled at any time, but normal completion must not be
-     * signaled before onComplete() is called.
-     */
-    public SubscriberWrapper()
-    {
-        this.outputQ = new ConcurrentLinkedQueue<>();
-        this.cf = new MinimalFuture<>();
-        this.pushScheduler =
-                SequentialScheduler.synchronizedScheduler(new DownstreamPusher());
-        this.downstreamSubscription = new SubscriptionBase(pushScheduler,
-                                                           this::downstreamCompletion);
-    }
-
-    @Override
-    public final void subscribe(Subscriber<?  super List<ByteBuffer>> downstreamSubscriber) {
-        Objects.requireNonNull(downstreamSubscriber);
-        this.downstreamSubscriber = downstreamSubscriber;
-    }
-
-    /**
-     * Wraps the given downstream wrapper in this. For each call to
-     * {@link #onNext(List<ByteBuffer>) } the incoming() method is called.
-     *
-     * The {@code downstreamCF} from the downstream wrapper is linked to this
-     * wrappers notifier.
-     *
-     * @param downstreamWrapper downstream destination
-     */
-    public SubscriberWrapper(Subscriber<? super List<ByteBuffer>> downstreamWrapper)
-    {
-        this();
-        subscribe(downstreamWrapper);
-    }
-
-    /**
-     * Delivers data to be processed by this wrapper. Generated data to be sent
-     * downstream, must be provided to the {@link #outgoing(List, boolean)}}
-     * method.
-     *
-     * @param buffers a List of ByteBuffers.
-     * @param complete if true then no more data will be added to the list
-     */
-    protected abstract void incoming(List<ByteBuffer> buffers, boolean complete);
-
-    /**
-     * This method is called to determine the window size to use at any time. The
-     * current window is supplied together with the current downstream queue size.
-     * {@code 0} should be returned if no change is
-     * required or a positive integer which will be added to the current window.
-     * The default implementation maintains a downstream queue size of no greater
-     * than 5. The method can be overridden if required.
-     *
-     * @param currentWindow the current upstream subscription window
-     * @param downstreamQsize the current number of buffers waiting to be sent
-     *                        downstream
-     *
-     * @return value to add to currentWindow
-     */
-    protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
-        if (downstreamQsize > 5) {
-            return 0;
-        }
-
-        if (currentWindow == 0) {
-            return 1;
-        } else {
-            return 0;
-        }
-    }
-
-    /**
-     * Override this if anything needs to be done after the upstream subscriber
-     * has subscribed
-     */
-    protected void onSubscribe() {
-    }
-
-    /**
-     * Override this if anything needs to be done before checking for error
-     * and processing the input queue.
-     * @return
-     */
-    protected SchedulingAction enterScheduling() {
-        return SchedulingAction.CONTINUE;
-    }
-
-    protected boolean signalScheduling() {
-        if (downstreamCompleted || pushScheduler.isStopped()) {
-            return false;
-        }
-        pushScheduler.runOrSchedule();
-        return true;
-    }
-
-    /**
-     * Delivers buffers of data downstream. After incoming()
-     * has been called complete == true signifying completion of the upstream
-     * subscription, data may continue to be delivered, up to when outgoing() is
-     * called complete == true, after which, the downstream subscription is
-     * completed.
-     *
-     * It's an error to call outgoing() with complete = true if incoming() has
-     * not previously been called with it.
-     */
-    public void outgoing(ByteBuffer buffer, boolean complete) {
-        Objects.requireNonNull(buffer);
-        assert !complete || !buffer.hasRemaining();
-        outgoing(List.of(buffer), complete);
-    }
-
-    /**
-     * Sometime it might be necessary to complete the downstream subscriber
-     * before the upstream completes. For instance, when an SSL server
-     * sends a notify_close. In that case we should let the outgoing
-     * complete before upstream us completed.
-     * @return true, may be overridden by subclasses.
-     */
-    public boolean closing() {
-        return false;
-    }
-
-    public void outgoing(List<ByteBuffer> buffers, boolean complete) {
-        Objects.requireNonNull(buffers);
-        if (complete) {
-            assert Utils.remaining(buffers) == 0;
-            boolean closing = closing();
-            logger.log(Level.DEBUG,
-                    "completionAcknowledged upstreamCompleted:%s, downstreamCompleted:%s, closing:%s",
-                    upstreamCompleted, downstreamCompleted, closing);
-            if (!upstreamCompleted && !closing)
-                throw new IllegalStateException("upstream not completed");
-            completionAcknowledged = true;
-        } else {
-            logger.log(Level.DEBUG, () -> "Adding "
-                                   + Utils.remaining(buffers)
-                                   + " to outputQ queue");
-            outputQ.add(buffers);
-        }
-        logger.log(Level.DEBUG, () -> "pushScheduler "
-                   + (pushScheduler.isStopped() ? " is stopped!" : " is alive"));
-        pushScheduler.runOrSchedule();
-    }
-
-    /**
-     * Returns a CompletableFuture which completes when this wrapper completes.
-     * Normal completion happens with the following steps (in order):
-     *   1. onComplete() is called
-     *   2. incoming() called with complete = true
-     *   3. outgoing() may continue to be called normally
-     *   4. outgoing called with complete = true
-     *   5. downstream subscriber is called onComplete()
-     *
-     * If the subscription is canceled or onComplete() is invoked the
-     * CompletableFuture completes exceptionally. Exceptional completion
-     * also occurs if downstreamCF completes exceptionally.
-     */
-    public CompletableFuture<Void> completion() {
-        return cf;
-    }
-
-    /**
-     * Invoked whenever it 'may' be possible to push buffers downstream.
-     */
-    class DownstreamPusher implements Runnable {
-        @Override
-        public void run() {
-            try {
-                run1();
-            } catch (Throwable t) {
-                errorCommon(t);
-            }
-        }
-
-        private void run1() {
-            if (downstreamCompleted) {
-                logger.log(Level.DEBUG, "DownstreamPusher: downstream is already completed");
-                return;
-            }
-            switch (enterScheduling()) {
-                case CONTINUE: break;
-                case RESCHEDULE: pushScheduler.runOrSchedule(); return;
-                case RETURN: return;
-                default:
-                    errorRef.compareAndSet(null,
-                            new InternalError("unknown scheduling command"));
-                    break;
-            }
-            // If there was an error, send it downstream.
-            Throwable error = errorRef.get();
-            if (error != null) {
-                synchronized(this) {
-                    if (downstreamCompleted) return;
-                    downstreamCompleted = true;
-                }
-                logger.log(Level.DEBUG,
-                        () -> "DownstreamPusher: forwarding error downstream: " + error);
-                pushScheduler.stop();
-                outputQ.clear();
-                downstreamSubscriber.onError(error);
-                return;
-            }
-
-            // OK - no error, let's proceed
-            if (!outputQ.isEmpty()) {
-                logger.log(Level.DEBUG,
-                    "DownstreamPusher: queue not empty, downstreamSubscription: %s",
-                     downstreamSubscription);
-            } else {
-                logger.log(Level.DEBUG,
-                       "DownstreamPusher: queue empty, downstreamSubscription: %s",
-                       downstreamSubscription);
-            }
-
-            final boolean dbgOn = logger.isLoggable(Level.DEBUG);
-            while (!outputQ.isEmpty() && downstreamSubscription.tryDecrement()) {
-                List<ByteBuffer> b = outputQ.poll();
-                if (dbgOn) logger.log(Level.DEBUG,
-                                            "DownstreamPusher: Pushing "
-                                            + Utils.remaining(b)
-                                            + " bytes downstream");
-                downstreamSubscriber.onNext(b);
-            }
-            upstreamWindowUpdate();
-            checkCompletion();
-        }
-    }
-
-    void upstreamWindowUpdate() {
-        long downstreamQueueSize = outputQ.size();
-        long n = upstreamWindowUpdate(upstreamWindow.get(), downstreamQueueSize);
-        if (n > 0)
-            upstreamRequest(n);
-    }
-
-    @Override
-    public void onSubscribe(Flow.Subscription subscription) {
-        if (upstreamSubscription != null) {
-            throw new IllegalStateException("Single shot publisher");
-        }
-        this.upstreamSubscription = subscription;
-        upstreamRequest(upstreamWindowUpdate(0, 0));
-        logger.log(Level.DEBUG,
-               "calling downstreamSubscriber::onSubscribe on %s",
-               downstreamSubscriber);
-        downstreamSubscriber.onSubscribe(downstreamSubscription);
-        onSubscribe();
-    }
-
-    @Override
-    public void onNext(List<ByteBuffer> item) {
-        logger.log(Level.DEBUG, "onNext");
-        long prev = upstreamWindow.getAndDecrement();
-        if (prev <= 0)
-            throw new IllegalStateException("invalid onNext call");
-        incomingCaller(item, false);
-        upstreamWindowUpdate();
-    }
-
-    private void upstreamRequest(long n) {
-        logger.log(Level.DEBUG, "requesting %d", n);
-        upstreamWindow.getAndAdd(n);
-        upstreamSubscription.request(n);
-    }
-
-    protected void requestMore() {
-        if (upstreamWindow.get() == 0) {
-            upstreamRequest(1);
-        }
-    }
-
-    public long upstreamWindow() {
-        return upstreamWindow.get();
-    }
-
-    @Override
-    public void onError(Throwable throwable) {
-        logger.log(Level.DEBUG, () -> "onError: " + throwable);
-        errorCommon(Objects.requireNonNull(throwable));
-    }
-
-    protected boolean errorCommon(Throwable throwable) {
-        assert throwable != null ||
-                (throwable = new AssertionError("null throwable")) != null;
-        if (errorRef.compareAndSet(null, throwable)) {
-            logger.log(Level.DEBUG, "error", throwable);
-            pushScheduler.runOrSchedule();
-            upstreamCompleted = true;
-            cf.completeExceptionally(throwable);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void close() {
-        errorCommon(new RuntimeException("wrapper closed"));
-    }
-
-    private void incomingCaller(List<ByteBuffer> l, boolean complete) {
-        try {
-            incoming(l, complete);
-        } catch(Throwable t) {
-            errorCommon(t);
-        }
-    }
-
-    @Override
-    public void onComplete() {
-        logger.log(Level.DEBUG, () -> "upstream completed: " + toString());
-        upstreamCompleted = true;
-        incomingCaller(Utils.EMPTY_BB_LIST, true);
-        // pushScheduler will call checkCompletion()
-        pushScheduler.runOrSchedule();
-    }
-
-    /** Adds the given data to the input queue. */
-    public void addData(ByteBuffer l) {
-        if (upstreamSubscription == null) {
-            throw new IllegalStateException("can't add data before upstream subscriber subscribes");
-        }
-        incomingCaller(List.of(l), false);
-    }
-
-    void checkCompletion() {
-        if (downstreamCompleted || !upstreamCompleted) {
-            return;
-        }
-        if (!outputQ.isEmpty()) {
-            return;
-        }
-        if (errorRef.get() != null) {
-            pushScheduler.runOrSchedule();
-            return;
-        }
-        if (completionAcknowledged) {
-            logger.log(Level.DEBUG, "calling downstreamSubscriber.onComplete()");
-            downstreamSubscriber.onComplete();
-            // Fix me subscriber.onComplete.run();
-            downstreamCompleted = true;
-            cf.complete(null);
-        }
-    }
-
-    // called from the downstream Subscription.cancel()
-    void downstreamCompletion() {
-        upstreamSubscription.cancel();
-        cf.complete(null);
-    }
-
-    public void resetDownstreamDemand() {
-        downstreamSubscription.demand.reset();
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("SubscriberWrapper:")
-          .append(" upstreamCompleted: ").append(Boolean.toString(upstreamCompleted))
-          .append(" upstreamWindow: ").append(upstreamWindow.toString())
-          .append(" downstreamCompleted: ").append(Boolean.toString(downstreamCompleted))
-          .append(" completionAcknowledged: ").append(Boolean.toString(completionAcknowledged))
-          .append(" outputQ size: ").append(Integer.toString(outputQ.size()))
-          //.append(" outputQ: ").append(outputQ.toString())
-          .append(" cf: ").append(cf.toString())
-          .append(" downstreamSubscription: ").append(downstreamSubscription.toString());
-
-        return sb.toString();
-    }
-
-    public String dbgString() {
-        return "SubscriberWrapper";
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/SubscriptionBase.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +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 java.net.http.internal.common;
-
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Maintains subscription counter and provides primitives for
- * - accessing window
- * - reducing window when delivering items externally
- * - resume delivery when window was zero previously
- *
- * @author mimcmah
- */
-public class SubscriptionBase implements Flow.Subscription {
-
-    final Demand demand = new Demand();
-
-    final SequentialScheduler scheduler; // when window was zero and is opened, run this
-    final Runnable cancelAction; // when subscription cancelled, run this
-    final AtomicBoolean cancelled;
-
-    public SubscriptionBase(SequentialScheduler scheduler, Runnable cancelAction) {
-        this.scheduler = scheduler;
-        this.cancelAction = cancelAction;
-        this.cancelled = new AtomicBoolean(false);
-    }
-
-    @Override
-    public void request(long n) {
-        if (demand.increase(n))
-            scheduler.runOrSchedule();
-    }
-
-
-
-    @Override
-    public synchronized String toString() {
-        return "SubscriptionBase: window = " + demand.get() +
-                " cancelled = " + cancelled.toString();
-    }
-
-    /**
-     * Returns true if the window was reduced by 1. In that case
-     * items must be supplied to subscribers and the scheduler run
-     * externally. If the window could not be reduced by 1, then false
-     * is returned and the scheduler will run later when the window is updated.
-     */
-    public boolean tryDecrement() {
-        return demand.tryDecrement();
-    }
-
-    public long window() {
-        return demand.get();
-    }
-
-    @Override
-    public void cancel() {
-        if (cancelled.getAndSet(true))
-            return;
-        scheduler.stop();
-        cancelAction.run();
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/common/Utils.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,755 +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 java.net.http.internal.common;
-
-import java.net.http.HttpHeaders;
-import sun.net.NetProperties;
-import sun.net.util.IPAddressUtil;
-import sun.net.www.HeaderParser;
-
-import javax.net.ssl.SSLParameters;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.UncheckedIOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.net.URLPermission;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.ExecutionException;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.util.stream.Collectors.joining;
-
-/**
- * Miscellaneous utilities
- */
-public final class Utils {
-
-    public static final boolean ASSERTIONSENABLED;
-    static {
-        boolean enabled = false;
-        assert enabled = true;
-        ASSERTIONSENABLED = enabled;
-    }
-//    public static final boolean TESTING;
-//    static {
-//        if (ASSERTIONSENABLED) {
-//            PrivilegedAction<String> action = () -> System.getProperty("test.src");
-//            TESTING = AccessController.doPrivileged(action) != null;
-//        } else TESTING = false;
-//    }
-    public static final boolean DEBUG = // Revisit: temporary dev flag.
-            getBooleanProperty(DebugLogger.HTTP_NAME, false);
-    public static final boolean DEBUG_HPACK = // Revisit: temporary dev flag.
-            getBooleanProperty(DebugLogger.HPACK_NAME, false);
-    public static final boolean TESTING = DEBUG;
-
-    /**
-     * Allocated buffer size. Must never be higher than 16K. But can be lower
-     * if smaller allocation units preferred. HTTP/2 mandates that all
-     * implementations support frame payloads of at least 16K.
-     */
-    private static final int DEFAULT_BUFSIZE = 16 * 1024;
-
-    public static final int BUFSIZE = getIntegerNetProperty(
-            "jdk.httpclient.bufsize", DEFAULT_BUFSIZE
-    );
-
-    private static final Set<String> DISALLOWED_HEADERS_SET;
-    static {
-        // A case insensitive TreeSet of strings.
-        TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
-        treeSet.addAll(Set.of("connection", "content-length",
-                "date", "expect", "from", "host", "origin",
-                "referer", "upgrade",
-                "via", "warning"));
-        DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(treeSet);
-    }
-
-    public static final Predicate<String>
-        ALLOWED_HEADERS = header -> !DISALLOWED_HEADERS_SET.contains(header);
-
-    private static final Predicate<String> IS_PROXY_HEADER = (k) ->
-            k != null && k.length() > 6 && "proxy-".equalsIgnoreCase(k.substring(0,6));
-    private static final Predicate<String> NO_PROXY_HEADER =
-            IS_PROXY_HEADER.negate();
-    private static final Predicate<String> ALL_HEADERS = (s) -> true;
-
-    private static final Set<String> PROXY_AUTH_DISABLED_SCHEMES;
-    private static final Set<String> PROXY_AUTH_TUNNEL_DISABLED_SCHEMES;
-    static {
-        String proxyAuthDisabled =
-                getNetProperty("jdk.http.auth.proxying.disabledSchemes");
-        String proxyAuthTunnelDisabled =
-                getNetProperty("jdk.http.auth.tunneling.disabledSchemes");
-        PROXY_AUTH_DISABLED_SCHEMES =
-                proxyAuthDisabled == null ? Set.of() :
-                        Stream.of(proxyAuthDisabled.split(","))
-                                .map(String::trim)
-                                .filter((s) -> !s.isEmpty())
-                                .collect(Collectors.toUnmodifiableSet());
-        PROXY_AUTH_TUNNEL_DISABLED_SCHEMES =
-                proxyAuthTunnelDisabled == null ? Set.of() :
-                        Stream.of(proxyAuthTunnelDisabled.split(","))
-                                .map(String::trim)
-                                .filter((s) -> !s.isEmpty())
-                                .collect(Collectors.toUnmodifiableSet());
-    }
-
-    private static final String WSPACES = " \t\r\n";
-    private static final boolean isAllowedForProxy(String name,
-                                                   List<String> value,
-                                                   Set<String> disabledSchemes,
-                                                   Predicate<String> allowedKeys) {
-        if (!allowedKeys.test(name)) return false;
-        if (disabledSchemes.isEmpty()) return true;
-        if (name.equalsIgnoreCase("proxy-authorization")) {
-            if (value.isEmpty()) return false;
-            for (String scheme : disabledSchemes) {
-                int slen = scheme.length();
-                for (String v : value) {
-                    int vlen = v.length();
-                    if (vlen == slen) {
-                        if (v.equalsIgnoreCase(scheme)) {
-                            return false;
-                        }
-                    } else if (vlen > slen) {
-                        if (v.substring(0,slen).equalsIgnoreCase(scheme)) {
-                            int c = v.codePointAt(slen);
-                            if (WSPACES.indexOf(c) > -1
-                                    || Character.isSpaceChar(c)
-                                    || Character.isWhitespace(c)) {
-                                return false;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        return true;
-    }
-
-    public static final BiPredicate<String, List<String>> PROXY_TUNNEL_FILTER =
-            (s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_TUNNEL_DISABLED_SCHEMES,
-                    IS_PROXY_HEADER);
-    public static final BiPredicate<String, List<String>> PROXY_FILTER =
-            (s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_DISABLED_SCHEMES,
-                    ALL_HEADERS);
-    public static final BiPredicate<String, List<String>> NO_PROXY_HEADERS_FILTER =
-            (n,v) -> Utils.NO_PROXY_HEADER.test(n);
-
-
-    public static boolean proxyHasDisabledSchemes(boolean tunnel) {
-        return tunnel ? ! PROXY_AUTH_TUNNEL_DISABLED_SCHEMES.isEmpty()
-                      : ! PROXY_AUTH_DISABLED_SCHEMES.isEmpty();
-    }
-
-    public static ByteBuffer getBuffer() {
-        return ByteBuffer.allocate(BUFSIZE);
-    }
-
-    public static Throwable getCompletionCause(Throwable x) {
-        if (!(x instanceof CompletionException)
-                && !(x instanceof ExecutionException)) return x;
-        final Throwable cause = x.getCause();
-        if (cause == null) {
-            throw new InternalError("Unexpected null cause", x);
-        }
-        return cause;
-    }
-
-    public static IOException getIOException(Throwable t) {
-        if (t instanceof IOException) {
-            return (IOException) t;
-        }
-        Throwable cause = t.getCause();
-        if (cause != null) {
-            return getIOException(cause);
-        }
-        return new IOException(t);
-    }
-
-    private Utils() { }
-
-    /**
-     * Returns the security permissions required to connect to the proxy, or
-     * {@code null} if none is required or applicable.
-     */
-    public static URLPermission permissionForProxy(InetSocketAddress proxyAddress) {
-        if (proxyAddress == null)
-            return null;
-
-        StringBuilder sb = new StringBuilder();
-        sb.append("socket://")
-          .append(proxyAddress.getHostString()).append(":")
-          .append(proxyAddress.getPort());
-        String urlString = sb.toString();
-        return new URLPermission(urlString, "CONNECT");
-    }
-
-    /**
-     * Returns the security permission required for the given details.
-     */
-    public static URLPermission permissionForServer(URI uri,
-                                                    String method,
-                                                    Stream<String> headers) {
-        String urlString = new StringBuilder()
-                .append(uri.getScheme()).append("://")
-                .append(uri.getAuthority())
-                .append(uri.getPath()).toString();
-
-        StringBuilder actionStringBuilder = new StringBuilder(method);
-        String collected = headers.collect(joining(","));
-        if (!collected.isEmpty()) {
-            actionStringBuilder.append(":").append(collected);
-        }
-        return new URLPermission(urlString, actionStringBuilder.toString());
-    }
-
-
-    // ABNF primitives defined in RFC 7230
-    private static final boolean[] tchar      = new boolean[256];
-    private static final boolean[] fieldvchar = new boolean[256];
-
-    static {
-        char[] allowedTokenChars =
-                ("!#$%&'*+-.^_`|~0123456789" +
-                 "abcdefghijklmnopqrstuvwxyz" +
-                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
-        for (char c : allowedTokenChars) {
-            tchar[c] = true;
-        }
-        for (char c = 0x21; c < 0xFF; c++) {
-            fieldvchar[c] = true;
-        }
-        fieldvchar[0x7F] = false; // a little hole (DEL) in the range
-    }
-
-    /*
-     * Validates a RFC 7230 field-name.
-     */
-    public static boolean isValidName(String token) {
-        for (int i = 0; i < token.length(); i++) {
-            char c = token.charAt(i);
-            if (c > 255 || !tchar[c]) {
-                return false;
-            }
-        }
-        return !token.isEmpty();
-    }
-
-    /**
-     * If the address was created with a domain name, then return
-     * the domain name string. If created with a literal IP address
-     * then return null. We do this to avoid doing a reverse lookup
-     * Used to populate the TLS SNI parameter. So, SNI is only set
-     * when a domain name was supplied.
-     */
-    public static String getServerName(InetSocketAddress addr) {
-        String host = addr.getHostString();
-        if (IPAddressUtil.textToNumericFormatV4(host) != null)
-            return null;
-        if (IPAddressUtil.textToNumericFormatV6(host) != null)
-            return null;
-        return host;
-    }
-
-    /*
-     * Validates a RFC 7230 field-value.
-     *
-     * "Obsolete line folding" rule
-     *
-     *     obs-fold = CRLF 1*( SP / HTAB )
-     *
-     * is not permitted!
-     */
-    public static boolean isValidValue(String token) {
-        boolean accepted = true;
-        for (int i = 0; i < token.length(); i++) {
-            char c = token.charAt(i);
-            if (c > 255) {
-                return false;
-            }
-            if (accepted) {
-                if (c == ' ' || c == '\t') {
-                    accepted = false;
-                } else if (!fieldvchar[c]) {
-                    return false; // forbidden byte
-                }
-            } else {
-                if (c != ' ' && c != '\t') {
-                    if (fieldvchar[c]) {
-                        accepted = true;
-                    } else {
-                        return false; // forbidden byte
-                    }
-                }
-            }
-        }
-        return accepted;
-    }
-
-    public static int getIntegerNetProperty(String name, int defaultValue) {
-        return AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
-                NetProperties.getInteger(name, defaultValue));
-    }
-
-    static String getNetProperty(String name) {
-        return AccessController.doPrivileged((PrivilegedAction<String>) () ->
-                NetProperties.get(name));
-    }
-
-    static boolean getBooleanProperty(String name, boolean def) {
-        return AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
-                Boolean.parseBoolean(System.getProperty(name, String.valueOf(def))));
-    }
-
-    public static SSLParameters copySSLParameters(SSLParameters p) {
-        SSLParameters p1 = new SSLParameters();
-        p1.setAlgorithmConstraints(p.getAlgorithmConstraints());
-        p1.setCipherSuites(p.getCipherSuites());
-        // JDK 8 EXCL START
-        p1.setEnableRetransmissions(p.getEnableRetransmissions());
-        p1.setMaximumPacketSize(p.getMaximumPacketSize());
-        // JDK 8 EXCL END
-        p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm());
-        p1.setNeedClientAuth(p.getNeedClientAuth());
-        String[] protocols = p.getProtocols();
-        if (protocols != null) {
-            p1.setProtocols(protocols.clone());
-        }
-        p1.setSNIMatchers(p.getSNIMatchers());
-        p1.setServerNames(p.getServerNames());
-        p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
-        p1.setWantClientAuth(p.getWantClientAuth());
-        return p1;
-    }
-
-    /**
-     * Set limit to position, and position to mark.
-     */
-    public static void flipToMark(ByteBuffer buffer, int mark) {
-        buffer.limit(buffer.position());
-        buffer.position(mark);
-    }
-
-    public static String stackTrace(Throwable t) {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        String s = null;
-        try {
-            PrintStream p = new PrintStream(bos, true, "US-ASCII");
-            t.printStackTrace(p);
-            s = bos.toString("US-ASCII");
-        } catch (UnsupportedEncodingException ex) {
-            throw new InternalError(ex); // Can't happen
-        }
-        return s;
-    }
-
-    /**
-     * Copies as much of src to dst as possible.
-     * Return number of bytes copied
-     */
-    public static int copy(ByteBuffer src, ByteBuffer dst) {
-        int srcLen = src.remaining();
-        int dstLen = dst.remaining();
-        if (srcLen > dstLen) {
-            int diff = srcLen - dstLen;
-            int limit = src.limit();
-            src.limit(limit - diff);
-            dst.put(src);
-            src.limit(limit);
-        } else {
-            dst.put(src);
-        }
-        return srcLen - src.remaining();
-    }
-
-    /** Threshold beyond which data is no longer copied into the current
-     * buffer, if that buffer has enough unused space. */
-    private static final int COPY_THRESHOLD = 8192;
-
-    /**
-     * Adds the data from buffersToAdd to currentList. Either 1) appends the
-     * data from a particular buffer to the last buffer in the list ( if
-     * there is enough unused space ), or 2) adds it to the list.
-     *
-     * @return the number of bytes added
-     */
-    public static long accumulateBuffers(List<ByteBuffer> currentList,
-                                         List<ByteBuffer> buffersToAdd) {
-        long accumulatedBytes = 0;
-        for (ByteBuffer bufferToAdd : buffersToAdd) {
-            int remaining = bufferToAdd.remaining();
-            if (remaining <= 0)
-                continue;
-            int listSize = currentList.size();
-            if (listSize == 0) {
-                currentList.add(bufferToAdd);
-                accumulatedBytes = remaining;
-                continue;
-            }
-
-            ByteBuffer lastBuffer = currentList.get(listSize - 1);
-            int freeSpace = lastBuffer.capacity() - lastBuffer.limit();
-            if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) {
-                // append the new data to the unused space in the last buffer
-                int position = lastBuffer.position();
-                int limit = lastBuffer.limit();
-                lastBuffer.position(limit);
-                lastBuffer.limit(limit + remaining);
-                lastBuffer.put(bufferToAdd);
-                lastBuffer.position(position);
-            } else {
-                currentList.add(bufferToAdd);
-            }
-            accumulatedBytes += remaining;
-        }
-        return accumulatedBytes;
-    }
-
-    public static ByteBuffer copy(ByteBuffer src) {
-        ByteBuffer dst = ByteBuffer.allocate(src.remaining());
-        dst.put(src);
-        dst.flip();
-        return dst;
-    }
-
-    public static String dump(Object... objects) {
-        return Arrays.toString(objects);
-    }
-
-    public static String stringOf(Collection<?> source) {
-        // We don't know anything about toString implementation of this
-        // collection, so let's create an array
-        return Arrays.toString(source.toArray());
-    }
-
-    public static long remaining(ByteBuffer[] bufs) {
-        long remain = 0;
-        for (ByteBuffer buf : bufs) {
-            remain += buf.remaining();
-        }
-        return remain;
-    }
-
-    public static boolean hasRemaining(List<ByteBuffer> bufs) {
-        synchronized (bufs) {
-            for (ByteBuffer buf : bufs) {
-                if (buf.hasRemaining())
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    public static long remaining(List<ByteBuffer> bufs) {
-        long remain = 0;
-        synchronized (bufs) {
-            for (ByteBuffer buf : bufs) {
-                remain += buf.remaining();
-            }
-        }
-        return remain;
-    }
-
-    public static int remaining(List<ByteBuffer> bufs, int max) {
-        long remain = 0;
-        synchronized (bufs) {
-            for (ByteBuffer buf : bufs) {
-                remain += buf.remaining();
-                if (remain > max) {
-                    throw new IllegalArgumentException("too many bytes");
-                }
-            }
-        }
-        return (int) remain;
-    }
-
-    public static long remaining(ByteBufferReference[] refs) {
-        long remain = 0;
-        for (ByteBufferReference ref : refs) {
-            remain += ref.get().remaining();
-        }
-        return remain;
-    }
-
-    public static int remaining(ByteBufferReference[] refs, int max) {
-        long remain = 0;
-        for (ByteBufferReference ref : refs) {
-            remain += ref.get().remaining();
-            if (remain > max) {
-                throw new IllegalArgumentException("too many bytes");
-            }
-        }
-        return (int) remain;
-    }
-
-    public static int remaining(ByteBuffer[] refs, int max) {
-        long remain = 0;
-        for (ByteBuffer b : refs) {
-            remain += b.remaining();
-            if (remain > max) {
-                throw new IllegalArgumentException("too many bytes");
-            }
-        }
-        return (int) remain;
-    }
-
-    public static void close(Closeable... closeables) {
-        for (Closeable c : closeables) {
-            try {
-                c.close();
-            } catch (IOException ignored) { }
-        }
-    }
-
-    // Put all these static 'empty' singletons here
-    public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
-    public static final ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0];
-    public static final List<ByteBuffer> EMPTY_BB_LIST = List.of();
-    public static final ByteBufferReference[] EMPTY_BBR_ARRAY = new ByteBufferReference[0];
-
-    /**
-     * Returns a slice of size {@code amount} from the given buffer. If the
-     * buffer contains more data than {@code amount}, then the slice's capacity
-     * ( and, but not just, its limit ) is set to {@code amount}. If the buffer
-     * does not contain more data than {@code amount}, then the slice's capacity
-     * will be the same as the given buffer's capacity.
-     */
-    public static ByteBuffer sliceWithLimitedCapacity(ByteBuffer buffer, int amount) {
-        final int index = buffer.position() + amount;
-        final int limit = buffer.limit();
-        if (index != limit) {
-            // additional data in the buffer
-            buffer.limit(index);  // ensures that the slice does not go beyond
-        } else {
-            // no additional data in the buffer
-            buffer.limit(buffer.capacity());  // allows the slice full capacity
-        }
-
-        ByteBuffer newb = buffer.slice();
-        buffer.position(index);
-        buffer.limit(limit);    // restore the original buffer's limit
-        newb.limit(amount);     // slices limit to amount (capacity may be greater)
-        return newb;
-    }
-
-    /**
-     * Get the Charset from the Content-encoding header. Defaults to
-     * UTF_8
-     */
-    public static Charset charsetFrom(HttpHeaders headers) {
-        String type = headers.firstValue("Content-type")
-                .orElse("text/html; charset=utf-8");
-        int i = type.indexOf(";");
-        if (i >= 0) type = type.substring(i+1);
-        try {
-            HeaderParser parser = new HeaderParser(type);
-            String value = parser.findValue("charset");
-            if (value == null) return StandardCharsets.UTF_8;
-            return Charset.forName(value);
-        } catch (Throwable x) {
-            Log.logTrace("Can't find charset in \"{0}\" ({1})", type, x);
-            return StandardCharsets.UTF_8;
-        }
-    }
-
-    public static UncheckedIOException unchecked(IOException e) {
-        return new UncheckedIOException(e);
-    }
-
-    /**
-     * Get a logger for debug HTTP traces.
-     *
-     * The logger should only be used with levels whose severity is
-     * {@code <= DEBUG}. By default, this logger will forward all messages
-     * logged to an internal logger named "jdk.internal.httpclient.debug".
-     * In addition, if the property -Djdk.internal.httpclient.debug=true is set,
-     * it will print the messages on stderr.
-     * The logger will add some decoration to the printed message, in the form of
-     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
-     *
-     * @param dbgTag A lambda that returns a string that identifies the caller
-     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
-     *
-     * @return A logger for HTTP internal debug traces
-     */
-    public static Logger getDebugLogger(Supplier<String> dbgTag) {
-        return getDebugLogger(dbgTag, DEBUG);
-    }
-
-    /**
-     * Get a logger for debug HTTP traces.The logger should only be used
-     * with levels whose severity is {@code <= DEBUG}.
-     *
-     * By default, this logger will forward all messages logged to an internal
-     * logger named "jdk.internal.httpclient.debug".
-     * In addition, if the message severity level is >= to
-     * the provided {@code errLevel} it will print the messages on stderr.
-     * The logger will add some decoration to the printed message, in the form of
-     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
-     *
-     * @apiNote To obtain a logger that will always print things on stderr in
-     *          addition to forwarding to the internal logger, use
-     *          {@code getDebugLogger(this::dbgTag, Level.ALL);}.
-     *          This is also equivalent to calling
-     *          {@code getDebugLogger(this::dbgTag, true);}.
-     *          To obtain a logger that will only forward to the internal logger,
-     *          use {@code getDebugLogger(this::dbgTag, Level.OFF);}.
-     *          This is also equivalent to calling
-     *          {@code getDebugLogger(this::dbgTag, false);}.
-     *
-     * @param dbgTag A lambda that returns a string that identifies the caller
-     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
-     * @param errLevel The level above which messages will be also printed on
-     *               stderr (in addition to be forwarded to the internal logger).
-     *
-     * @return A logger for HTTP internal debug traces
-     */
-    static Logger getDebugLogger(Supplier<String> dbgTag, Level errLevel) {
-        return DebugLogger.createHttpLogger(dbgTag, Level.OFF, errLevel);
-    }
-
-    /**
-     * Get a logger for debug HTTP traces.The logger should only be used
-     * with levels whose severity is {@code <= DEBUG}.
-     *
-     * By default, this logger will forward all messages logged to an internal
-     * logger named "jdk.internal.httpclient.debug".
-     * In addition, the provided boolean {@code on==true}, it will print the
-     * messages on stderr.
-     * The logger will add some decoration to the printed message, in the form of
-     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
-     *
-     * @apiNote To obtain a logger that will always print things on stderr in
-     *          addition to forwarding to the internal logger, use
-     *          {@code getDebugLogger(this::dbgTag, true);}.
-     *          This is also equivalent to calling
-     *          {@code getDebugLogger(this::dbgTag, Level.ALL);}.
-     *          To obtain a logger that will only forward to the internal logger,
-     *          use {@code getDebugLogger(this::dbgTag, false);}.
-     *          This is also equivalent to calling
-     *          {@code getDebugLogger(this::dbgTag, Level.OFF);}.
-     *
-     * @param dbgTag A lambda that returns a string that identifies the caller
-     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
-     * @param on  Whether messages should also be printed on
-     *               stderr (in addition to be forwarded to the internal logger).
-     *
-     * @return A logger for HTTP internal debug traces
-     */
-    public static Logger getDebugLogger(Supplier<String> dbgTag, boolean on) {
-        Level errLevel = on ? Level.ALL : Level.OFF;
-        return getDebugLogger(dbgTag, errLevel);
-    }
-
-    /**
-     * Get a logger for debug HPACK traces.The logger should only be used
-     * with levels whose severity is {@code <= DEBUG}.
-     *
-     * By default, this logger will forward all messages logged to an internal
-     * logger named "jdk.internal.httpclient.hpack.debug".
-     * In addition, if the message severity level is >= to
-     * the provided {@code outLevel} it will print the messages on stdout.
-     * The logger will add some decoration to the printed message, in the form of
-     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
-     *
-     * @apiNote To obtain a logger that will always print things on stdout in
-     *          addition to forwarding to the internal logger, use
-     *          {@code getHpackLogger(this::dbgTag, Level.ALL);}.
-     *          This is also equivalent to calling
-     *          {@code getHpackLogger(this::dbgTag, true);}.
-     *          To obtain a logger that will only forward to the internal logger,
-     *          use {@code getHpackLogger(this::dbgTag, Level.OFF);}.
-     *          This is also equivalent to calling
-     *          {@code getHpackLogger(this::dbgTag, false);}.
-     *
-     * @param dbgTag A lambda that returns a string that identifies the caller
-     *               (e.g: "Http2Connection(SocketTube(3))/hpack.Decoder(3)")
-     * @param outLevel The level above which messages will be also printed on
-     *               stdout (in addition to be forwarded to the internal logger).
-     *
-     * @return A logger for HPACK internal debug traces
-     */
-    public static Logger getHpackLogger(Supplier<String> dbgTag, Level outLevel) {
-        Level errLevel = Level.OFF;
-        return DebugLogger.createHpackLogger(dbgTag, outLevel, errLevel);
-    }
-
-    /**
-     * Get a logger for debug HPACK traces.The logger should only be used
-     * with levels whose severity is {@code <= DEBUG}.
-     *
-     * By default, this logger will forward all messages logged to an internal
-     * logger named "jdk.internal.httpclient.hpack.debug".
-     * In addition, the provided boolean {@code on==true}, it will print the
-     * messages on stdout.
-     * The logger will add some decoration to the printed message, in the form of
-     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
-     *
-     * @apiNote To obtain a logger that will always print things on stdout in
-     *          addition to forwarding to the internal logger, use
-     *          {@code getHpackLogger(this::dbgTag, true);}.
-     *          This is also equivalent to calling
-     *          {@code getHpackLogger(this::dbgTag, Level.ALL);}.
-     *          To obtain a logger that will only forward to the internal logger,
-     *          use {@code getHpackLogger(this::dbgTag, false);}.
-     *          This is also equivalent to calling
-     *          {@code getHpackLogger(this::dbgTag, Level.OFF);}.
-     *
-     * @param dbgTag A lambda that returns a string that identifies the caller
-     *               (e.g: "Http2Connection(SocketTube(3))/hpack.Decoder(3)")
-     * @param on  Whether messages should also be printed on
-     *            stdout (in addition to be forwarded to the internal logger).
-     *
-     * @return A logger for HPACK internal debug traces
-     */
-    public static Logger getHpackLogger(Supplier<String> dbgTag, boolean on) {
-        Level outLevel = on ? Level.ALL : Level.OFF;
-        return getHpackLogger(dbgTag, outLevel);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/ContinuationFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +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 java.net.http.internal.frame;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-
-public class ContinuationFrame extends HeaderFrame {
-
-    public static final int TYPE = 0x9;
-
-    public ContinuationFrame(int streamid, int flags, List<ByteBuffer> headerBlocks) {
-        super(streamid, flags, headerBlocks);
-    }
-
-    public ContinuationFrame(int streamid, ByteBuffer headersBlock) {
-        this(streamid, 0, List.of(headersBlock));
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return headerLength;
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/DataFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +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 java.net.http.internal.frame;
-
-import java.net.http.internal.common.Utils;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-
-public class DataFrame extends Http2Frame {
-
-    public static final int TYPE = 0x0;
-
-    // Flags
-    public static final int END_STREAM = 0x1;
-    public static final int PADDED = 0x8;
-
-    private int padLength;
-    private final List<ByteBuffer> data;
-    private final int dataLength;
-
-    public DataFrame(int streamid, int flags, ByteBuffer data) {
-        this(streamid, flags, List.of(data));
-    }
-
-    public DataFrame(int streamid, int flags, List<ByteBuffer> data) {
-        super(streamid, flags);
-        this.data = data;
-        this.dataLength = Utils.remaining(data, Integer.MAX_VALUE);
-    }
-
-    public DataFrame(int streamid, int flags, List<ByteBuffer> data, int padLength) {
-        this(streamid, flags, data);
-        if (padLength > 0) {
-            setPadLength(padLength);
-        }
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return dataLength + (((flags & PADDED) != 0) ? (padLength + 1) : 0);
-    }
-
-    @Override
-    public String flagAsString(int flag) {
-        switch (flag) {
-        case END_STREAM:
-            return "END_STREAM";
-        case PADDED:
-            return "PADDED";
-        }
-        return super.flagAsString(flag);
-    }
-
-    public List<ByteBuffer> getData() {
-        return data;
-    }
-
-    public int getDataLength() {
-        return dataLength;
-    }
-
-    int getPadLength() {
-        return padLength;
-    }
-
-    public void setPadLength(int padLength) {
-        this.padLength = padLength;
-        flags |= PADDED;
-    }
-
-    public int payloadLength() {
-        // RFC 7540 6.1:
-        // The entire DATA frame payload is included in flow control,
-        // including the Pad Length and Padding fields if present
-        if ((flags & PADDED) != 0) {
-            return dataLength + (1 + padLength);
-        } else {
-            return dataLength;
-        }
-    }
-
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/ErrorFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +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 java.net.http.internal.frame;
-
-public abstract class ErrorFrame extends Http2Frame {
-
-    // error codes
-    public static final int NO_ERROR = 0x0;
-    public static final int PROTOCOL_ERROR = 0x1;
-    public static final int INTERNAL_ERROR = 0x2;
-    public static final int FLOW_CONTROL_ERROR = 0x3;
-    public static final int SETTINGS_TIMEOUT = 0x4;
-    public static final int STREAM_CLOSED = 0x5;
-    public static final int FRAME_SIZE_ERROR = 0x6;
-    public static final int REFUSED_STREAM = 0x7;
-    public static final int CANCEL = 0x8;
-    public static final int COMPRESSION_ERROR = 0x9;
-    public static final int CONNECT_ERROR = 0xa;
-    public static final int ENHANCE_YOUR_CALM = 0xb;
-    public static final int INADEQUATE_SECURITY = 0xc;
-    public static final int HTTP_1_1_REQUIRED = 0xd;
-    static final int LAST_ERROR = 0xd;
-
-    static final String[] errorStrings = {
-        "Not an error",
-        "Protocol error",
-        "Internal error",
-        "Flow control error",
-        "Settings timeout",
-        "Stream is closed",
-        "Frame size error",
-        "Stream not processed",
-        "Stream cancelled",
-        "Compression state not updated",
-        "TCP Connection error on CONNECT",
-        "Processing capacity exceeded",
-        "Negotiated TLS parameters not acceptable",
-        "Use HTTP/1.1 for request"
-    };
-
-    public static String stringForCode(int code) {
-        if (code < 0) {
-            throw new IllegalArgumentException();
-        }
-
-        if (code > LAST_ERROR) {
-            return "Error: " + Integer.toString(code);
-        } else {
-            return errorStrings[code];
-        }
-    }
-
-    int errorCode;
-
-    public ErrorFrame(int streamid, int flags, int errorCode) {
-        super(streamid, flags);
-        this.errorCode = errorCode;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + " Error: " + stringForCode(errorCode);
-    }
-
-    public int getErrorCode() {
-        return this.errorCode;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/FramesDecoder.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,545 +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 java.net.http.internal.frame;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.List;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.Utils;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Frames Decoder
- * <p>
- * collect buffers until frame decoding is possible,
- * all decoded frames are passed to the FrameProcessor callback in order of decoding.
- *
- * It's a stateful class due to the fact that FramesDecoder stores buffers inside.
- * Should be allocated only the single instance per connection.
- */
-public class FramesDecoder {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("FramesDecoder"::toString, DEBUG);
-
-    @FunctionalInterface
-    public interface FrameProcessor {
-        void processFrame(Http2Frame frame) throws IOException;
-    }
-
-    private final FrameProcessor frameProcessor;
-    private final int maxFrameSize;
-
-    private ByteBuffer currentBuffer; // current buffer either null or hasRemaining
-
-    private final ArrayDeque<ByteBuffer> tailBuffers = new ArrayDeque<>();
-    private int tailSize = 0;
-
-    private boolean slicedToDataFrame = false;
-
-    private final List<ByteBuffer> prepareToRelease = new ArrayList<>();
-
-    // if true  - Frame Header was parsed (9 bytes consumed) and subsequent fields have meaning
-    // otherwise - stopped at frames boundary
-    private boolean frameHeaderParsed = false;
-    private int frameLength;
-    private int frameType;
-    private int frameFlags;
-    private int frameStreamid;
-    private boolean closed;
-
-    /**
-     * Creates Frame Decoder
-     *
-     * @param frameProcessor - callback for decoded frames
-     */
-    public FramesDecoder(FrameProcessor frameProcessor) {
-        this(frameProcessor, 16 * 1024);
-    }
-
-    /**
-     * Creates Frame Decoder
-     * @param frameProcessor - callback for decoded frames
-     * @param maxFrameSize - maxFrameSize accepted by this decoder
-     */
-    public FramesDecoder(FrameProcessor frameProcessor, int maxFrameSize) {
-        this.frameProcessor = frameProcessor;
-        this.maxFrameSize = Math.min(Math.max(16 * 1024, maxFrameSize), 16 * 1024 * 1024 - 1);
-    }
-
-    /** Threshold beyond which data is no longer copied into the current buffer,
-     * if that buffer has enough unused space. */
-    private static final int COPY_THRESHOLD = 8192;
-
-    /**
-     * Adds the data from the given buffer, and performs frame decoding if
-     * possible.   Either 1) appends the data from the given buffer to the
-     * current buffer ( if there is enough unused space ), or 2) adds it to the
-     * next buffer in the queue.
-     *
-     * If there is enough data to perform frame decoding then, all buffers are
-     * decoded and the FrameProcessor is invoked.
-     */
-    public void decode(ByteBuffer inBoundBuffer) throws IOException {
-        if (closed) {
-            DEBUG_LOGGER.log(Level.DEBUG, "closed: ignoring buffer (%s bytes)",
-                    inBoundBuffer.remaining());
-            inBoundBuffer.position(inBoundBuffer.limit());
-            return;
-        }
-        int remaining = inBoundBuffer.remaining();
-        DEBUG_LOGGER.log(Level.DEBUG, "decodes: %d", remaining);
-        if (remaining > 0) {
-            if (currentBuffer == null) {
-                currentBuffer = inBoundBuffer;
-            } else {
-                ByteBuffer b = currentBuffer;
-                if (!tailBuffers.isEmpty()) {
-                    b = tailBuffers.getLast();
-                }
-
-                int limit = b.limit();
-                int freeSpace = b.capacity() - limit;
-                if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) {
-                    // append the new data to the unused space in the current buffer
-                    int position = b.position();
-                    b.position(limit);
-                    b.limit(limit + inBoundBuffer.remaining());
-                    b.put(inBoundBuffer);
-                    b.position(position);
-                    if (b != currentBuffer)
-                        tailSize += remaining;
-                    DEBUG_LOGGER.log(Level.DEBUG, "copied: %d", remaining);
-                } else {
-                    DEBUG_LOGGER.log(Level.DEBUG, "added: %d", remaining);
-                    tailBuffers.add(inBoundBuffer);
-                    tailSize += remaining;
-                }
-            }
-        }
-        DEBUG_LOGGER.log(Level.DEBUG, "Tail size is now: %d, current=",
-                tailSize,
-                (currentBuffer == null ? 0 :
-                   currentBuffer.remaining()));
-        Http2Frame frame;
-        while ((frame = nextFrame()) != null) {
-            DEBUG_LOGGER.log(Level.DEBUG, "Got frame: %s", frame);
-            frameProcessor.processFrame(frame);
-            frameProcessed();
-        }
-    }
-
-    private Http2Frame nextFrame() throws IOException {
-        while (true) {
-            if (currentBuffer == null) {
-                return null; // no data at all
-            }
-            long available = currentBuffer.remaining() + tailSize;
-            if (!frameHeaderParsed) {
-                if (available >= Http2Frame.FRAME_HEADER_SIZE) {
-                    parseFrameHeader();
-                    if (frameLength > maxFrameSize) {
-                        // connection error
-                        return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
-                                "Frame type("+frameType+") "
-                                +"length("+frameLength
-                                +") exceeds MAX_FRAME_SIZE("
-                                + maxFrameSize+")");
-                    }
-                    frameHeaderParsed = true;
-                } else {
-                    DEBUG_LOGGER.log(Level.DEBUG,
-                            "Not enough data to parse header, needs: %d, has: %d",
-                            Http2Frame.FRAME_HEADER_SIZE, available);
-                    return null;
-                }
-            }
-            available = currentBuffer == null ? 0 : currentBuffer.remaining() + tailSize;
-            if ((frameLength == 0) ||
-                    (currentBuffer != null && available >= frameLength)) {
-                Http2Frame frame = parseFrameBody();
-                frameHeaderParsed = false;
-                // frame == null means we have to skip this frame and try parse next
-                if (frame != null) {
-                    return frame;
-                }
-            } else {
-                DEBUG_LOGGER.log(Level.DEBUG,
-                        "Not enough data to parse frame body, needs: %d,  has: %d",
-                        frameLength, available);
-                return null;  // no data for the whole frame header
-            }
-        }
-    }
-
-    private void frameProcessed() {
-        prepareToRelease.clear();
-    }
-
-    private void parseFrameHeader() throws IOException {
-        int x = getInt();
-        this.frameLength = (x >>> 8) & 0x00ffffff;
-        this.frameType = x & 0xff;
-        this.frameFlags = getByte();
-        this.frameStreamid = getInt() & 0x7fffffff;
-        // R: A reserved 1-bit field.  The semantics of this bit are undefined,
-        // MUST be ignored when receiving.
-    }
-
-    // move next buffer from tailBuffers to currentBuffer if required
-    private void nextBuffer() {
-        if (!currentBuffer.hasRemaining()) {
-            if (!slicedToDataFrame) {
-                prepareToRelease.add(currentBuffer);
-            }
-            slicedToDataFrame = false;
-            currentBuffer = tailBuffers.poll();
-            if (currentBuffer != null) {
-                tailSize -= currentBuffer.remaining();
-            }
-        }
-    }
-
-    public int getByte() {
-        int res = currentBuffer.get() & 0xff;
-        nextBuffer();
-        return res;
-    }
-
-    public int getShort() {
-        if (currentBuffer.remaining() >= 2) {
-            int res = currentBuffer.getShort() & 0xffff;
-            nextBuffer();
-            return res;
-        }
-        int val = getByte();
-        val = (val << 8) + getByte();
-        return val;
-    }
-
-    public int getInt() {
-        if (currentBuffer.remaining() >= 4) {
-            int res = currentBuffer.getInt();
-            nextBuffer();
-            return res;
-        }
-        int val = getByte();
-        val = (val << 8) + getByte();
-        val = (val << 8) + getByte();
-        val = (val << 8) + getByte();
-        return val;
-
-    }
-
-    public byte[] getBytes(int n) {
-        byte[] bytes = new byte[n];
-        int offset = 0;
-        while (n > 0) {
-            int length = Math.min(n, currentBuffer.remaining());
-            currentBuffer.get(bytes, offset, length);
-            offset += length;
-            n -= length;
-            nextBuffer();
-        }
-        return bytes;
-
-    }
-
-    private List<ByteBuffer> getBuffers(boolean isDataFrame, int bytecount) {
-        List<ByteBuffer> res = new ArrayList<>();
-        while (bytecount > 0) {
-            int remaining = currentBuffer.remaining();
-            int extract = Math.min(remaining, bytecount);
-            ByteBuffer extractedBuf;
-            if (isDataFrame) {
-                extractedBuf = Utils.sliceWithLimitedCapacity(currentBuffer, extract)
-                                    .asReadOnlyBuffer();
-                slicedToDataFrame = true;
-            } else {
-                // Header frames here
-                // HPACK decoding should performed under lock and immediately after frame decoding.
-                // in that case it is safe to release original buffer,
-                // because of sliced buffer has a very short life
-                extractedBuf = Utils.sliceWithLimitedCapacity(currentBuffer, extract);
-            }
-            res.add(extractedBuf);
-            bytecount -= extract;
-            nextBuffer();
-        }
-        return res;
-    }
-
-    public void close(String msg) {
-        closed = true;
-        tailBuffers.clear();
-        int bytes = tailSize;
-        ByteBuffer b = currentBuffer;
-        if (b != null) {
-            bytes += b.remaining();
-            b.position(b.limit());
-        }
-        tailSize = 0;
-        currentBuffer = null;
-        DEBUG_LOGGER.log(Level.DEBUG, "closed %s, ignoring %d bytes", msg, bytes);
-    }
-
-    public void skipBytes(int bytecount) {
-        while (bytecount > 0) {
-            int remaining = currentBuffer.remaining();
-            int extract = Math.min(remaining, bytecount);
-            currentBuffer.position(currentBuffer.position() + extract);
-            bytecount -= remaining;
-            nextBuffer();
-        }
-    }
-
-    private Http2Frame parseFrameBody() throws IOException {
-        assert frameHeaderParsed;
-        switch (frameType) {
-            case DataFrame.TYPE:
-                return parseDataFrame(frameLength, frameStreamid, frameFlags);
-            case HeadersFrame.TYPE:
-                return parseHeadersFrame(frameLength, frameStreamid, frameFlags);
-            case PriorityFrame.TYPE:
-                return parsePriorityFrame(frameLength, frameStreamid, frameFlags);
-            case ResetFrame.TYPE:
-                return parseResetFrame(frameLength, frameStreamid, frameFlags);
-            case SettingsFrame.TYPE:
-                return parseSettingsFrame(frameLength, frameStreamid, frameFlags);
-            case PushPromiseFrame.TYPE:
-                return parsePushPromiseFrame(frameLength, frameStreamid, frameFlags);
-            case PingFrame.TYPE:
-                return parsePingFrame(frameLength, frameStreamid, frameFlags);
-            case GoAwayFrame.TYPE:
-                return parseGoAwayFrame(frameLength, frameStreamid, frameFlags);
-            case WindowUpdateFrame.TYPE:
-                return parseWindowUpdateFrame(frameLength, frameStreamid, frameFlags);
-            case ContinuationFrame.TYPE:
-                return parseContinuationFrame(frameLength, frameStreamid, frameFlags);
-            default:
-                // RFC 7540 4.1
-                // Implementations MUST ignore and discard any frame that has a type that is unknown.
-                Log.logTrace("Unknown incoming frame type: {0}", frameType);
-                skipBytes(frameLength);
-                return null;
-        }
-    }
-
-    private Http2Frame parseDataFrame(int frameLength, int streamid, int flags) {
-        // non-zero stream
-        if (streamid == 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                                      "zero streamId for DataFrame");
-        }
-        int padLength = 0;
-        if ((flags & DataFrame.PADDED) != 0) {
-            padLength = getByte();
-            if (padLength >= frameLength) {
-                return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                        "the length of the padding is the length of the frame payload or greater");
-            }
-            frameLength--;
-        }
-        DataFrame df = new DataFrame(streamid, flags,
-                getBuffers(true, frameLength - padLength), padLength);
-        skipBytes(padLength);
-        return df;
-
-    }
-
-    private Http2Frame parseHeadersFrame(int frameLength, int streamid, int flags) {
-        // non-zero stream
-        if (streamid == 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                                      "zero streamId for HeadersFrame");
-        }
-        int padLength = 0;
-        if ((flags & HeadersFrame.PADDED) != 0) {
-            padLength = getByte();
-            frameLength--;
-        }
-        boolean hasPriority = (flags & HeadersFrame.PRIORITY) != 0;
-        boolean exclusive = false;
-        int streamDependency = 0;
-        int weight = 0;
-        if (hasPriority) {
-            int x = getInt();
-            exclusive = (x & 0x80000000) != 0;
-            streamDependency = x & 0x7fffffff;
-            weight = getByte();
-            frameLength -= 5;
-        }
-        if(frameLength < padLength) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "Padding exceeds the size remaining for the header block");
-        }
-        HeadersFrame hf = new HeadersFrame(streamid, flags,
-                getBuffers(false, frameLength - padLength), padLength);
-        skipBytes(padLength);
-        if (hasPriority) {
-            hf.setPriority(streamDependency, exclusive, weight);
-        }
-        return hf;
-    }
-
-    private Http2Frame parsePriorityFrame(int frameLength, int streamid, int flags) {
-        // non-zero stream; no flags
-        if (streamid == 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "zero streamId for PriorityFrame");
-        }
-        if(frameLength != 5) {
-            skipBytes(frameLength);
-            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR, streamid,
-                    "PriorityFrame length is "+ frameLength+", expected 5");
-        }
-        int x = getInt();
-        int weight = getByte();
-        return new PriorityFrame(streamid, x & 0x7fffffff, (x & 0x80000000) != 0, weight);
-    }
-
-    private Http2Frame parseResetFrame(int frameLength, int streamid, int flags) {
-        // non-zero stream; no flags
-        if (streamid == 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "zero streamId for ResetFrame");
-        }
-        if(frameLength != 4) {
-            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
-                    "ResetFrame length is "+ frameLength+", expected 4");
-        }
-        return new ResetFrame(streamid, getInt());
-    }
-
-    private Http2Frame parseSettingsFrame(int frameLength, int streamid, int flags) {
-        // only zero stream
-        if (streamid != 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "non-zero streamId for SettingsFrame");
-        }
-        if ((SettingsFrame.ACK & flags) != 0 && frameLength > 0) {
-            // RFC 7540 6.5
-            // Receipt of a SETTINGS frame with the ACK flag set and a length
-            // field value other than 0 MUST be treated as a connection error
-            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
-                    "ACK SettingsFrame is not empty");
-        }
-        if (frameLength % 6 != 0) {
-            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
-                    "invalid SettingsFrame size: "+frameLength);
-        }
-        SettingsFrame sf = new SettingsFrame(flags);
-        int n = frameLength / 6;
-        for (int i=0; i<n; i++) {
-            int id = getShort();
-            int val = getInt();
-            if (id > 0 && id <= SettingsFrame.MAX_PARAM) {
-                // a known parameter. Ignore otherwise
-                sf.setParameter(id, val); // TODO parameters validation
-            }
-        }
-        return sf;
-    }
-
-    private Http2Frame parsePushPromiseFrame(int frameLength, int streamid, int flags) {
-        // non-zero stream
-        if (streamid == 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "zero streamId for PushPromiseFrame");
-        }
-        int padLength = 0;
-        if ((flags & PushPromiseFrame.PADDED) != 0) {
-            padLength = getByte();
-            frameLength--;
-        }
-        int promisedStream = getInt() & 0x7fffffff;
-        frameLength -= 4;
-        if(frameLength < padLength) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "Padding exceeds the size remaining for the PushPromiseFrame");
-        }
-        PushPromiseFrame ppf = new PushPromiseFrame(streamid, flags, promisedStream,
-                getBuffers(false, frameLength - padLength), padLength);
-        skipBytes(padLength);
-        return ppf;
-    }
-
-    private Http2Frame parsePingFrame(int frameLength, int streamid, int flags) {
-        // only zero stream
-        if (streamid != 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "non-zero streamId for PingFrame");
-        }
-        if(frameLength != 8) {
-            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
-                    "PingFrame length is "+ frameLength+", expected 8");
-        }
-        return new PingFrame(flags, getBytes(8));
-    }
-
-    private Http2Frame parseGoAwayFrame(int frameLength, int streamid, int flags) {
-        // only zero stream; no flags
-        if (streamid != 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "non-zero streamId for GoAwayFrame");
-        }
-        if (frameLength < 8) {
-            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
-                    "Invalid GoAway frame size");
-        }
-        int lastStream = getInt() & 0x7fffffff;
-        int errorCode = getInt();
-        byte[] debugData = getBytes(frameLength - 8);
-        if (debugData.length > 0) {
-            Log.logError("GoAway debugData " + new String(debugData, UTF_8));
-        }
-        return new GoAwayFrame(lastStream, errorCode, debugData);
-    }
-
-    private Http2Frame parseWindowUpdateFrame(int frameLength, int streamid, int flags) {
-        // any stream; no flags
-        if(frameLength != 4) {
-            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
-                    "WindowUpdateFrame length is "+ frameLength+", expected 4");
-        }
-        return new WindowUpdateFrame(streamid, getInt() & 0x7fffffff);
-    }
-
-    private Http2Frame parseContinuationFrame(int frameLength, int streamid, int flags) {
-        // non-zero stream;
-        if (streamid == 0) {
-            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
-                    "zero streamId for ContinuationFrame");
-        }
-        return new ContinuationFrame(streamid, flags, getBuffers(false, frameLength));
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/FramesEncoder.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,293 +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 java.net.http.internal.frame;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Frames Encoder
- *
- * Encode framed into ByteBuffers.
- * The class is stateless.
- */
-public class FramesEncoder {
-
-
-    public FramesEncoder() {
-    }
-
-    public List<ByteBuffer> encodeFrames(List<HeaderFrame> frames) {
-        List<ByteBuffer> bufs = new ArrayList<>(frames.size() * 2);
-        for (HeaderFrame f : frames) {
-            bufs.addAll(encodeFrame(f));
-        }
-        return bufs;
-    }
-
-    public ByteBuffer encodeConnectionPreface(byte[] preface, SettingsFrame frame) {
-        final int length = frame.length();
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length + preface.length);
-        buf.put(preface);
-        putSettingsFrame(buf, frame, length);
-        buf.flip();
-        return buf;
-    }
-
-    public List<ByteBuffer> encodeFrame(Http2Frame frame) {
-        switch (frame.type()) {
-            case DataFrame.TYPE:
-                return encodeDataFrame((DataFrame) frame);
-            case HeadersFrame.TYPE:
-                return encodeHeadersFrame((HeadersFrame) frame);
-            case PriorityFrame.TYPE:
-                return encodePriorityFrame((PriorityFrame) frame);
-            case ResetFrame.TYPE:
-                return encodeResetFrame((ResetFrame) frame);
-            case SettingsFrame.TYPE:
-                return encodeSettingsFrame((SettingsFrame) frame);
-            case PushPromiseFrame.TYPE:
-                return encodePushPromiseFrame((PushPromiseFrame) frame);
-            case PingFrame.TYPE:
-                return encodePingFrame((PingFrame) frame);
-            case GoAwayFrame.TYPE:
-                return encodeGoAwayFrame((GoAwayFrame) frame);
-            case WindowUpdateFrame.TYPE:
-                return encodeWindowUpdateFrame((WindowUpdateFrame) frame);
-            case ContinuationFrame.TYPE:
-                return encodeContinuationFrame((ContinuationFrame) frame);
-            default:
-                throw new UnsupportedOperationException("Not supported frame "+frame.type()+" ("+frame.getClass().getName()+")");
-        }
-    }
-
-    private static final int NO_FLAGS = 0;
-    private static final int ZERO_STREAM = 0;
-
-    private List<ByteBuffer> encodeDataFrame(DataFrame frame) {
-        // non-zero stream
-        assert frame.streamid() != 0;
-        ByteBuffer buf = encodeDataFrameStart(frame);
-        if (frame.getFlag(DataFrame.PADDED)) {
-            return joinWithPadding(buf, frame.getData(), frame.getPadLength());
-        } else {
-            return join(buf, frame.getData());
-        }
-    }
-
-    private ByteBuffer encodeDataFrameStart(DataFrame frame) {
-        boolean isPadded = frame.getFlag(DataFrame.PADDED);
-        final int length = frame.getDataLength() + (isPadded ? (frame.getPadLength() + 1) : 0);
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0));
-        putHeader(buf, length, DataFrame.TYPE, frame.getFlags(), frame.streamid());
-        if (isPadded) {
-            buf.put((byte) frame.getPadLength());
-        }
-        buf.flip();
-        return buf;
-    }
-
-    private List<ByteBuffer> encodeHeadersFrame(HeadersFrame frame) {
-        // non-zero stream
-        assert frame.streamid() != 0;
-        ByteBuffer buf = encodeHeadersFrameStart(frame);
-        if (frame.getFlag(HeadersFrame.PADDED)) {
-            return joinWithPadding(buf, frame.getHeaderBlock(), frame.getPadLength());
-        } else {
-            return join(buf, frame.getHeaderBlock());
-        }
-    }
-
-    private ByteBuffer encodeHeadersFrameStart(HeadersFrame frame) {
-        boolean isPadded = frame.getFlag(HeadersFrame.PADDED);
-        boolean hasPriority = frame.getFlag(HeadersFrame.PRIORITY);
-        final int length = frame.getHeaderLength() + (isPadded ? (frame.getPadLength() + 1) : 0) + (hasPriority ? 5 : 0);
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0) + (hasPriority ? 5 : 0));
-        putHeader(buf, length, HeadersFrame.TYPE, frame.getFlags(), frame.streamid());
-        if (isPadded) {
-            buf.put((byte) frame.getPadLength());
-        }
-        if (hasPriority) {
-            putPriority(buf, frame.getExclusive(), frame.getStreamDependency(), frame.getWeight());
-        }
-        buf.flip();
-        return buf;
-    }
-
-    private List<ByteBuffer> encodePriorityFrame(PriorityFrame frame) {
-        // non-zero stream; no flags
-        assert frame.streamid() != 0;
-        final int length = 5;
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
-        putHeader(buf, length, PriorityFrame.TYPE, NO_FLAGS, frame.streamid());
-        putPriority(buf, frame.exclusive(), frame.streamDependency(), frame.weight());
-        buf.flip();
-        return List.of(buf);
-    }
-
-    private List<ByteBuffer> encodeResetFrame(ResetFrame frame) {
-        // non-zero stream; no flags
-        assert frame.streamid() != 0;
-        final int length = 4;
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
-        putHeader(buf, length, ResetFrame.TYPE, NO_FLAGS, frame.streamid());
-        buf.putInt(frame.getErrorCode());
-        buf.flip();
-        return List.of(buf);
-    }
-
-    private List<ByteBuffer> encodeSettingsFrame(SettingsFrame frame) {
-        // only zero stream
-        assert frame.streamid() == 0;
-        final int length = frame.length();
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
-        putSettingsFrame(buf, frame, length);
-        buf.flip();
-        return List.of(buf);
-    }
-
-    private List<ByteBuffer> encodePushPromiseFrame(PushPromiseFrame frame) {
-        // non-zero stream
-        assert frame.streamid() != 0;
-        boolean isPadded = frame.getFlag(PushPromiseFrame.PADDED);
-        final int length = frame.getHeaderLength() + (isPadded ? 5 : 4);
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 5 : 4));
-        putHeader(buf, length, PushPromiseFrame.TYPE, frame.getFlags(), frame.streamid());
-        if (isPadded) {
-            buf.put((byte) frame.getPadLength());
-        }
-        buf.putInt(frame.getPromisedStream());
-        buf.flip();
-
-        if (frame.getFlag(PushPromiseFrame.PADDED)) {
-            return joinWithPadding(buf, frame.getHeaderBlock(), frame.getPadLength());
-        } else {
-            return join(buf, frame.getHeaderBlock());
-        }
-    }
-
-    private List<ByteBuffer> encodePingFrame(PingFrame frame) {
-        // only zero stream
-        assert frame.streamid() == 0;
-        final int length = 8;
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
-        putHeader(buf, length, PingFrame.TYPE, frame.getFlags(), ZERO_STREAM);
-        buf.put(frame.getData());
-        buf.flip();
-        return List.of(buf);
-    }
-
-    private List<ByteBuffer> encodeGoAwayFrame(GoAwayFrame frame) {
-        // only zero stream; no flags
-        assert frame.streamid() == 0;
-        byte[] debugData = frame.getDebugData();
-        final int length = 8 + debugData.length;
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
-        putHeader(buf, length, GoAwayFrame.TYPE, NO_FLAGS, ZERO_STREAM);
-        buf.putInt(frame.getLastStream());
-        buf.putInt(frame.getErrorCode());
-        if (debugData.length > 0) {
-            buf.put(debugData);
-        }
-        buf.flip();
-        return List.of(buf);
-    }
-
-    private List<ByteBuffer> encodeWindowUpdateFrame(WindowUpdateFrame frame) {
-        // any stream; no flags
-        final int length = 4;
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
-        putHeader(buf, length, WindowUpdateFrame.TYPE, NO_FLAGS, frame.streamid);
-        buf.putInt(frame.getUpdate());
-        buf.flip();
-        return List.of(buf);
-    }
-
-    private List<ByteBuffer> encodeContinuationFrame(ContinuationFrame frame) {
-        // non-zero stream;
-        assert frame.streamid() != 0;
-        final int length = frame.getHeaderLength();
-        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE);
-        putHeader(buf, length, ContinuationFrame.TYPE, frame.getFlags(), frame.streamid());
-        buf.flip();
-        return join(buf, frame.getHeaderBlock());
-    }
-
-    private List<ByteBuffer> joinWithPadding(ByteBuffer buf, List<ByteBuffer> data, int padLength) {
-        int len = data.size();
-        if (len == 0) return List.of(buf, getPadding(padLength));
-        else if (len == 1) return List.of(buf, data.get(0), getPadding(padLength));
-        else if (len == 2) return List.of(buf, data.get(0), data.get(1), getPadding(padLength));
-        List<ByteBuffer> res = new ArrayList<>(len+2);
-        res.add(buf);
-        res.addAll(data);
-        res.add(getPadding(padLength));
-        return res;
-    }
-
-    private List<ByteBuffer> join(ByteBuffer buf, List<ByteBuffer> data) {
-        int len = data.size();
-        if (len == 0) return List.of(buf);
-        else if (len == 1) return List.of(buf, data.get(0));
-        else if (len == 2) return List.of(buf, data.get(0), data.get(1));
-        List<ByteBuffer> joined = new ArrayList<>(len + 1);
-        joined.add(buf);
-        joined.addAll(data);
-        return joined;
-    }
-
-    private void putSettingsFrame(ByteBuffer buf, SettingsFrame frame, int length) {
-        // only zero stream;
-        assert frame.streamid() == 0;
-        putHeader(buf, length, SettingsFrame.TYPE, frame.getFlags(), ZERO_STREAM);
-        frame.toByteBuffer(buf);
-    }
-
-    private void putHeader(ByteBuffer buf, int length, int type, int flags, int streamId) {
-        int x = (length << 8) + type;
-        buf.putInt(x);
-        buf.put((byte) flags);
-        buf.putInt(streamId);
-    }
-
-    private void putPriority(ByteBuffer buf, boolean exclusive, int streamDependency, int weight) {
-        buf.putInt(exclusive ? (1 << 31) + streamDependency : streamDependency);
-        buf.put((byte) weight);
-    }
-
-    private ByteBuffer getBuffer(int capacity) {
-        return ByteBuffer.allocate(capacity);
-    }
-
-    public ByteBuffer getPadding(int length) {
-        if (length > 255) {
-            throw new IllegalArgumentException("Padding too big");
-        }
-        return ByteBuffer.allocate(length); // zeroed!
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/GoAwayFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +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 java.net.http.internal.frame;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class GoAwayFrame extends ErrorFrame {
-
-    private final int lastStream;
-    private final byte[] debugData;
-
-    public static final int TYPE = 0x7;
-
-
-    public GoAwayFrame(int lastStream, int errorCode) {
-        this(lastStream, errorCode, new byte[0]);
-    }
-
-    public GoAwayFrame(int lastStream, int errorCode, byte[] debugData) {
-        super(0, 0, errorCode);
-        this.lastStream = lastStream;
-        this.debugData = debugData.clone();
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return 8 + debugData.length;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + " Debugdata: " + new String(debugData, UTF_8);
-    }
-
-    public int getLastStream() {
-        return this.lastStream;
-    }
-
-    public byte[] getDebugData() {
-        return debugData.clone();
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/HeaderFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +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 java.net.http.internal.frame;
-
-import java.net.http.internal.common.Utils;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-
-/**
- * Either a HeadersFrame or a ContinuationFrame
- */
-public abstract class HeaderFrame extends Http2Frame {
-
-    final int headerLength;
-    final List<ByteBuffer> headerBlocks;
-
-    public static final int END_STREAM = 0x1;
-    public static final int END_HEADERS = 0x4;
-
-    public HeaderFrame(int streamid, int flags, List<ByteBuffer> headerBlocks) {
-        super(streamid, flags);
-        this.headerBlocks = headerBlocks;
-        this.headerLength = Utils.remaining(headerBlocks, Integer.MAX_VALUE);
-    }
-
-    @Override
-    public String flagAsString(int flag) {
-        switch (flag) {
-            case END_HEADERS:
-                return "END_HEADERS";
-            case END_STREAM:
-                return "END_STREAM";
-        }
-        return super.flagAsString(flag);
-    }
-
-
-    public List<ByteBuffer> getHeaderBlock() {
-        return headerBlocks;
-    }
-
-    int getHeaderLength() {
-        return headerLength;
-    }
-
-    /**
-     * Returns true if this block is the final block of headers.
-     */
-    public boolean endHeaders() {
-        return getFlag(END_HEADERS);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/HeadersFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +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 java.net.http.internal.frame;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-
-public class HeadersFrame extends HeaderFrame {
-
-    public static final int TYPE = 0x1;
-
-    // Flags
-    public static final int END_STREAM = 0x1;
-    public static final int PADDED = 0x8;
-    public static final int PRIORITY = 0x20;
-
-
-    private int padLength;
-    private int streamDependency;
-    private int weight;
-    private boolean exclusive;
-
-    public HeadersFrame(int streamid, int flags, List<ByteBuffer> headerBlocks, int padLength) {
-        super(streamid, flags, headerBlocks);
-        if (padLength > 0) {
-            setPadLength(padLength);
-        }
-    }
-
-    public HeadersFrame(int streamid, int flags, List<ByteBuffer> headerBlocks) {
-        super(streamid, flags, headerBlocks);
-    }
-
-    public HeadersFrame(int streamid, int flags, ByteBuffer headerBlock) {
-        this(streamid, flags, List.of(headerBlock));
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return headerLength
-                + ((flags & PADDED) != 0 ? (1 + padLength) : 0)
-                + ((flags & PRIORITY) != 0 ? 5 : 0);
-    }
-
-    @Override
-    public String flagAsString(int flag) {
-        switch (flag) {
-            case END_STREAM:
-                return "END_STREAM";
-            case PADDED:
-                return "PADDED";
-            case PRIORITY:
-                return "PRIORITY";
-        }
-        return super.flagAsString(flag);
-    }
-
-    public void setPadLength(int padLength) {
-        this.padLength = padLength;
-        flags |= PADDED;
-    }
-
-    int getPadLength() {
-        return padLength;
-    }
-
-    public void setPriority(int streamDependency, boolean exclusive, int weight) {
-        this.streamDependency = streamDependency;
-        this.exclusive = exclusive;
-        this.weight = weight;
-        this.flags |= PRIORITY;
-    }
-
-    public int getStreamDependency() {
-        return streamDependency;
-    }
-
-    public int getWeight() {
-        return weight;
-    }
-
-    public boolean getExclusive() {
-        return exclusive;
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/Http2Frame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +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 java.net.http.internal.frame;
-
-/**
- * When sending a frame, the length field must be set in sub-class
- * by calling computeLength()
- */
-public abstract class Http2Frame {
-
-    public static final int FRAME_HEADER_SIZE = 9;
-
-    protected int streamid;
-    protected int flags;
-
-    public Http2Frame(int streamid, int flags) {
-        this.streamid = streamid;
-        this.flags = flags;
-    }
-
-    public int streamid() {
-        return streamid;
-    }
-
-    public void setFlag(int flag) {
-        flags |= flag;
-    }
-
-    public int getFlags() {
-        return flags;
-    }
-
-    public boolean getFlag(int flag) {
-        return (flags & flag) != 0;
-    }
-
-//    public void clearFlag(int flag) {
-//        flags &= 0xffffffff ^ flag;
-//    }
-
-    public void streamid(int streamid) {
-        this.streamid = streamid;
-    }
-
-
-    private String typeAsString() {
-        return asString(type());
-    }
-
-    public int type() {
-        return -1; // Unknown type
-    }
-
-    int length() {
-        return -1; // Unknown length
-    }
-
-
-    public static String asString(int type) {
-        switch (type) {
-          case DataFrame.TYPE:
-            return "DATA";
-          case HeadersFrame.TYPE:
-            return "HEADERS";
-          case ContinuationFrame.TYPE:
-            return "CONTINUATION";
-          case ResetFrame.TYPE:
-            return "RESET";
-          case PriorityFrame.TYPE:
-            return "PRIORITY";
-          case SettingsFrame.TYPE:
-            return "SETTINGS";
-          case GoAwayFrame.TYPE:
-            return "GOAWAY";
-          case PingFrame.TYPE:
-            return "PING";
-          case PushPromiseFrame.TYPE:
-            return "PUSH_PROMISE";
-          case WindowUpdateFrame.TYPE:
-            return "WINDOW_UPDATE";
-          default:
-            return "UNKNOWN";
-        }
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(typeAsString())
-                .append(": length=")
-                .append(Integer.toString(length()))
-                .append(", streamid=")
-                .append(streamid)
-                .append(", flags=");
-
-        int f = flags;
-        int i = 0;
-        if (f == 0) {
-            sb.append("0 ");
-        } else {
-            while (f != 0) {
-                if ((f & 1) == 1) {
-                    sb.append(flagAsString(1 << i))
-                      .append(' ');
-                }
-                f = f >> 1;
-                i++;
-            }
-        }
-        return sb.toString();
-    }
-
-    // Override
-    public String flagAsString(int f) {
-        return "unknown";
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/MalformedFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +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 java.net.http.internal.frame;
-
-public class MalformedFrame extends Http2Frame {
-
-    private int errorCode;
-    // if errorStream == 0 means Connection Error; RFC 7540 5.4.1
-    // if errorStream != 0 means Stream Error; RFC 7540 5.4.2
-    private int errorStream;
-    private String msg;
-
-    /**
-     * Creates Connection Error malformed frame
-     * @param errorCode - error code, as specified by RFC 7540
-     * @param msg - internal debug message
-     */
-    public MalformedFrame(int errorCode, String msg) {
-        this(errorCode, 0 , msg);
-    }
-
-    /**
-     * Creates Stream Error malformed frame
-     * @param errorCode - error code, as specified by RFC 7540
-     * @param errorStream - id of error stream (RST_FRAME will be send for this stream)
-     * @param msg - internal debug message
-     */
-    public MalformedFrame(int errorCode, int errorStream, String msg) {
-        super(0, 0);
-        this.errorCode = errorCode;
-        this.errorStream = errorStream;
-        this.msg = msg;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + " MalformedFrame, Error: " + ErrorFrame.stringForCode(errorCode)
-                + " streamid: " + streamid + " reason: " + msg;
-    }
-
-    public int getErrorCode() {
-        return errorCode;
-    }
-
-    public String getMessage() {
-        return msg;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/OutgoingHeaders.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +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 java.net.http.internal.frame;
-
-import java.net.http.HttpHeaders;
-
-/**
- * Contains all parameters for outgoing headers. Is converted to
- * HeadersFrame and ContinuationFrames by Http2Connection.
- */
-public class OutgoingHeaders<T> extends Http2Frame {
-
-    int streamDependency;
-    int weight;
-    boolean exclusive;
-    T attachment;
-
-    public static final int PRIORITY = 0x20;
-
-    HttpHeaders user, system;
-
-    public OutgoingHeaders(HttpHeaders hdrs1, HttpHeaders hdrs2, T attachment) {
-        super(0, 0);
-        this.user = hdrs2;
-        this.system = hdrs1;
-        this.attachment = attachment;
-    }
-
-    public void setPriority(int streamDependency, boolean exclusive, int weight) {
-        this.streamDependency = streamDependency;
-        this.exclusive = exclusive;
-        this.weight = weight;
-        this.flags |= PRIORITY;
-    }
-
-    public int getStreamDependency() {
-        return streamDependency;
-    }
-
-    public int getWeight() {
-        return weight;
-    }
-
-    public boolean getExclusive() {
-        return exclusive;
-    }
-
-    public T getAttachment() {
-        return attachment;
-    }
-
-    public HttpHeaders getUserHeaders() {
-        return user;
-    }
-
-    public HttpHeaders getSystemHeaders() {
-        return system;
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/PingFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +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 java.net.http.internal.frame;
-
-public class PingFrame extends Http2Frame {
-
-
-    private final byte[] data;
-
-    public static final int TYPE = 0x6;
-
-    // Flags
-    public static final int ACK = 0x1;
-
-    public PingFrame(int flags, byte[] data) {
-        super(0, flags);
-        assert data.length == 8;
-        this.data = data.clone();
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return 8;
-    }
-
-    @Override
-    public String flagAsString(int flag) {
-        switch (flag) {
-        case ACK:
-            return "ACK";
-        }
-        return super.flagAsString(flag);
-    }
-
-    public byte[] getData() {
-        return data.clone();
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/PriorityFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +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 java.net.http.internal.frame;
-
-public class PriorityFrame extends Http2Frame {
-
-    private final int streamDependency;
-    private final int weight;
-    private final boolean exclusive;
-
-    public static final int TYPE = 0x2;
-
-    public PriorityFrame(int streamId, int streamDependency, boolean exclusive, int weight) {
-        super(streamId, 0);
-        this.streamDependency = streamDependency;
-        this.exclusive = exclusive;
-        this.weight = weight;
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return 5;
-    }
-
-    public int streamDependency() {
-        return streamDependency;
-    }
-
-    public int weight() {
-        return weight;
-    }
-
-    public boolean exclusive() {
-        return exclusive;
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/PushPromiseFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +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 java.net.http.internal.frame;
-
-import java.nio.ByteBuffer;
-import java.util.List;
-
-public class PushPromiseFrame extends HeaderFrame {
-
-    private int padLength;
-    private final int promisedStream;
-
-    public static final int TYPE = 0x5;
-
-    // Flags
-    public static final int END_HEADERS = 0x4;
-    public static final int PADDED = 0x8;
-
-    public PushPromiseFrame(int streamid, int flags, int promisedStream, List<ByteBuffer> buffers, int padLength) {
-        super(streamid, flags, buffers);
-        this.promisedStream = promisedStream;
-        if(padLength > 0 ) {
-            setPadLength(padLength);
-        }
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return headerLength + ((flags & PADDED) != 0 ? 5 : 4);
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + " promisedStreamid: " + promisedStream
-                + " headerLength: " + headerLength;
-    }
-
-    @Override
-    public String flagAsString(int flag) {
-        switch (flag) {
-            case PADDED:
-                return "PADDED";
-            case END_HEADERS:
-                return "END_HEADERS";
-        }
-        return super.flagAsString(flag);
-    }
-
-    public void setPadLength(int padLength) {
-        this.padLength = padLength;
-        flags |= PADDED;
-    }
-
-    public int getPadLength() {
-        return padLength;
-    }
-
-    public int getPromisedStream() {
-        return promisedStream;
-    }
-
-    @Override
-    public boolean endHeaders() {
-        return getFlag(END_HEADERS);
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/ResetFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +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 java.net.http.internal.frame;
-
-public class ResetFrame extends ErrorFrame {
-
-    public static final int TYPE = 0x3;
-
-    // See ErrorFrame for error values
-
-    public ResetFrame(int streamid, int errorCode) {
-        super(streamid, 0, errorCode);
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return 4;
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/SettingsFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +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 java.net.http.internal.frame;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-public class SettingsFrame extends Http2Frame {
-
-    private final int[] parameters;
-
-    public static final int TYPE = 0x4;
-
-    // Flags
-    public static final int ACK = 0x1;
-
-    @Override
-    public String flagAsString(int flag) {
-        switch (flag) {
-        case ACK:
-            return "ACK";
-        }
-        return super.flagAsString(flag);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(super.toString())
-          .append(" Settings: ");
-
-        for (int i = 0; i < MAX_PARAM; i++) {
-            if (parameters[i] != -1) {
-                sb.append(name(i))
-                  .append("=")
-                  .append(Integer.toString(parameters[i]))
-                  .append(' ');
-            }
-        }
-        return sb.toString();
-    }
-
-    // Parameters
-    public static final int HEADER_TABLE_SIZE = 0x1;
-    public static final int ENABLE_PUSH = 0x2;
-    public static final int MAX_CONCURRENT_STREAMS = 0x3;
-    public static final int INITIAL_WINDOW_SIZE = 0x4;
-    public static final int MAX_FRAME_SIZE = 0x5;
-    public static final int MAX_HEADER_LIST_SIZE = 0x6;
-
-    private String name(int i) {
-        switch (i+1) {
-        case HEADER_TABLE_SIZE:
-            return "HEADER_TABLE_SIZE";
-        case ENABLE_PUSH:
-            return "ENABLE_PUSH";
-        case MAX_CONCURRENT_STREAMS:
-            return "MAX_CONCURRENT_STREAMS";
-        case INITIAL_WINDOW_SIZE:
-            return "INITIAL_WINDOW_SIZE";
-        case MAX_FRAME_SIZE:
-            return "MAX_FRAME_SIZE";
-        case MAX_HEADER_LIST_SIZE:
-            return "MAX_HEADER_LIST_SIZE";
-        }
-        return "unknown parameter";
-    }
-    public static final int MAX_PARAM = 0x6;
-
-    public SettingsFrame(int flags) {
-        super(0, flags);
-        parameters = new int [MAX_PARAM];
-        Arrays.fill(parameters, -1);
-    }
-
-    public SettingsFrame() {
-        this(0);
-    }
-
-    public SettingsFrame(SettingsFrame other) {
-        super(0, other.flags);
-        parameters = Arrays.copyOf(other.parameters, MAX_PARAM);
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    public int getParameter(int paramID) {
-        if (paramID > MAX_PARAM) {
-            throw new IllegalArgumentException("illegal parameter");
-        }
-        return parameters[paramID-1];
-    }
-
-    public SettingsFrame setParameter(int paramID, int value) {
-        if (paramID > MAX_PARAM) {
-            throw new IllegalArgumentException("illegal parameter");
-        }
-        parameters[paramID-1] = value;
-        return this;
-    }
-
-    int length() {
-        int len = 0;
-        for (int i : parameters) {
-            if (i != -1) {
-                len  += 6;
-            }
-        }
-        return len;
-    }
-
-    void toByteBuffer(ByteBuffer buf) {
-        for (int i = 0; i < MAX_PARAM; i++) {
-            if (parameters[i] != -1) {
-                buf.putShort((short) (i + 1));
-                buf.putInt(parameters[i]);
-            }
-        }
-    }
-
-    public byte[] toByteArray() {
-        byte[] bytes = new byte[length()];
-        ByteBuffer buf = ByteBuffer.wrap(bytes);
-        toByteBuffer(buf);
-        return bytes;
-    }
-
-    private static final int K = 1024;
-
-    public static SettingsFrame getDefaultSettings() {
-        SettingsFrame f = new SettingsFrame();
-        // TODO: check these values
-        f.setParameter(ENABLE_PUSH, 1);
-        f.setParameter(HEADER_TABLE_SIZE, 4 * K);
-        f.setParameter(MAX_CONCURRENT_STREAMS, 35);
-        f.setParameter(INITIAL_WINDOW_SIZE, 64 * K - 1);
-        f.setParameter(MAX_FRAME_SIZE, 16 * K);
-        return f;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/frame/WindowUpdateFrame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +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 java.net.http.internal.frame;
-
-public class WindowUpdateFrame extends Http2Frame {
-
-    private final int windowUpdate;
-
-    public static final int TYPE = 0x8;
-
-    public WindowUpdateFrame(int streamid, int windowUpdate) {
-        super(streamid, 0);
-        this.windowUpdate = windowUpdate;
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return 4;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(super.toString())
-          .append(" WindowUpdate: ")
-          .append(windowUpdate);
-        return sb.toString();
-    }
-
-    public int getUpdate() {
-        return this.windowUpdate;
-    }
-
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/BinaryRepresentationWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-
-interface BinaryRepresentationWriter {
-
-    boolean write(HeaderTable table, ByteBuffer destination);
-
-    BinaryRepresentationWriter reset();
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/BulkSizeUpdateWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * 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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-import java.util.Iterator;
-
-import static java.util.Objects.requireNonNull;
-
-final class BulkSizeUpdateWriter implements BinaryRepresentationWriter {
-
-    private final SizeUpdateWriter writer = new SizeUpdateWriter();
-    private Iterator<Integer> maxSizes;
-    private boolean writing;
-    private boolean configured;
-
-    BulkSizeUpdateWriter maxHeaderTableSizes(Iterable<Integer> sizes) {
-        if (configured) {
-            throw new IllegalStateException("Already configured");
-        }
-        requireNonNull(sizes, "sizes");
-        maxSizes = sizes.iterator();
-        configured = true;
-        return this;
-    }
-
-    @Override
-    public boolean write(HeaderTable table, ByteBuffer destination) {
-        if (!configured) {
-            throw new IllegalStateException("Configure first");
-        }
-        while (true) {
-            if (writing) {
-                if (!writer.write(table, destination)) {
-                    return false;
-                }
-                writing = false;
-            } else if (maxSizes.hasNext()) {
-                writing = true;
-                writer.reset();
-                writer.maxHeaderTableSize(maxSizes.next());
-            } else {
-                configured = false;
-                return true;
-            }
-        }
-    }
-
-    @Override
-    public BulkSizeUpdateWriter reset() {
-        maxSizes = null;
-        writing = false;
-        configured = false;
-        return this;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/Decoder.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,594 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import java.net.http.internal.hpack.HPACK.Logger;
-import jdk.internal.vm.annotation.Stable;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static java.net.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.NORMAL;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-
-/**
- * Decodes headers from their binary representation.
- *
- * <p> Typical lifecycle looks like this:
- *
- * <p> {@link #Decoder(int) new Decoder}
- * ({@link #setMaxCapacity(int) setMaxCapacity}?
- * {@link #decode(ByteBuffer, boolean, DecodingCallback) decode})*
- *
- * @apiNote
- *
- * <p> The design intentions behind Decoder were to facilitate flexible and
- * incremental style of processing.
- *
- * <p> {@code Decoder} does not require a complete header block in a single
- * {@code ByteBuffer}. The header block can be spread across many buffers of any
- * size and decoded one-by-one the way it makes most sense for the user. This
- * way also allows not to limit the size of the header block.
- *
- * <p> Headers are delivered to the {@linkplain DecodingCallback callback} as
- * soon as they become decoded. Using the callback also gives the user a freedom
- * to decide how headers are processed. The callback does not limit the number
- * of headers decoded during single decoding operation.
- *
- * @since 9
- */
-public final class Decoder {
-
-    private final Logger logger;
-    private static final AtomicLong DECODERS_IDS = new AtomicLong();
-
-    @Stable
-    private static final State[] states = new State[256];
-
-    static {
-        // To be able to do a quick lookup, each of 256 possibilities are mapped
-        // to corresponding states.
-        //
-        // We can safely do this since patterns 1, 01, 001, 0001, 0000 are
-        // Huffman prefixes and therefore are inherently not ambiguous.
-        //
-        // I do it mainly for better debugging (to not go each time step by step
-        // through if...else tree). As for performance win for the decoding, I
-        // believe is negligible.
-        for (int i = 0; i < states.length; i++) {
-            if ((i & 0b1000_0000) == 0b1000_0000) {
-                states[i] = State.INDEXED;
-            } else if ((i & 0b1100_0000) == 0b0100_0000) {
-                states[i] = State.LITERAL_WITH_INDEXING;
-            } else if ((i & 0b1110_0000) == 0b0010_0000) {
-                states[i] = State.SIZE_UPDATE;
-            } else if ((i & 0b1111_0000) == 0b0001_0000) {
-                states[i] = State.LITERAL_NEVER_INDEXED;
-            } else if ((i & 0b1111_0000) == 0b0000_0000) {
-                states[i] = State.LITERAL;
-            } else {
-                throw new InternalError(String.valueOf(i));
-            }
-        }
-    }
-
-    private final long id;
-    private final HeaderTable table;
-
-    private State state = State.READY;
-    private final IntegerReader integerReader;
-    private final StringReader stringReader;
-    private final StringBuilder name;
-    private final StringBuilder value;
-    private int intValue;
-    private boolean firstValueRead;
-    private boolean firstValueIndex;
-    private boolean nameHuffmanEncoded;
-    private boolean valueHuffmanEncoded;
-    private int capacity;
-
-    /**
-     * Constructs a {@code Decoder} with the specified initial capacity of the
-     * header table.
-     *
-     * <p> The value has to be agreed between decoder and encoder out-of-band,
-     * e.g. by a protocol that uses HPACK
-     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
-     *
-     * @param capacity
-     *         a non-negative integer
-     *
-     * @throws IllegalArgumentException
-     *         if capacity is negative
-     */
-    public Decoder(int capacity) {
-        id = DECODERS_IDS.incrementAndGet();
-        logger = HPACK.getLogger().subLogger("Decoder#" + id);
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("new decoder with maximum table size %s",
-                                            capacity));
-        }
-        if (logger.isLoggable(NORMAL)) {
-            /* To correlate with logging outside HPACK, knowing
-               hashCode/toString is important */
-            logger.log(NORMAL, () -> {
-                String hashCode = Integer.toHexString(
-                        System.identityHashCode(this));
-                return format("toString='%s', identityHashCode=%s",
-                              toString(), hashCode);
-            });
-        }
-        setMaxCapacity0(capacity);
-        table = new HeaderTable(capacity, logger.subLogger("HeaderTable"));
-        integerReader = new IntegerReader();
-        stringReader = new StringReader();
-        name = new StringBuilder(512);
-        value = new StringBuilder(1024);
-    }
-
-    /**
-     * Sets a maximum capacity of the header table.
-     *
-     * <p> The value has to be agreed between decoder and encoder out-of-band,
-     * e.g. by a protocol that uses HPACK
-     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
-     *
-     * @param capacity
-     *         a non-negative integer
-     *
-     * @throws IllegalArgumentException
-     *         if capacity is negative
-     */
-    public void setMaxCapacity(int capacity) {
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("setting maximum table size to %s",
-                                            capacity));
-        }
-        setMaxCapacity0(capacity);
-    }
-
-    private void setMaxCapacity0(int capacity) {
-        if (capacity < 0) {
-            throw new IllegalArgumentException("capacity >= 0: " + capacity);
-        }
-        // FIXME: await capacity update if less than what was prior to it
-        this.capacity = capacity;
-    }
-
-    /**
-     * Decodes a header block from the given buffer to the given callback.
-     *
-     * <p> Suppose a header block is represented by a sequence of
-     * {@code ByteBuffer}s in the form of {@code Iterator<ByteBuffer>}. And the
-     * consumer of decoded headers is represented by the callback. Then to
-     * decode the header block, the following approach might be used:
-     *
-     * <pre>{@code
-     * while (buffers.hasNext()) {
-     *     ByteBuffer input = buffers.next();
-     *     decoder.decode(input, callback, !buffers.hasNext());
-     * }
-     * }</pre>
-     *
-     * <p> The decoder reads as much as possible of the header block from the
-     * given buffer, starting at the buffer's position, and increments its
-     * position to reflect the bytes read. The buffer's mark and limit will not
-     * be modified.
-     *
-     * <p> Once the method is invoked with {@code endOfHeaderBlock == true}, the
-     * current header block is deemed ended, and inconsistencies, if any, are
-     * reported immediately by throwing an {@code IOException}.
-     *
-     * <p> Each callback method is called only after the implementation has
-     * processed the corresponding bytes. If the bytes revealed a decoding
-     * error, the callback method is not called.
-     *
-     * <p> In addition to exceptions thrown directly by the method, any
-     * exceptions thrown from the {@code callback} will bubble up.
-     *
-     * @apiNote The method asks for {@code endOfHeaderBlock} flag instead of
-     * returning it for two reasons. The first one is that the user of the
-     * decoder always knows which chunk is the last. The second one is to throw
-     * the most detailed exception possible, which might be useful for
-     * diagnosing issues.
-     *
-     * @implNote This implementation is not atomic in respect to decoding
-     * errors. In other words, if the decoding operation has thrown a decoding
-     * error, the decoder is no longer usable.
-     *
-     * @param headerBlock
-     *         the chunk of the header block, may be empty
-     * @param endOfHeaderBlock
-     *         true if the chunk is the final (or the only one) in the sequence
-     *
-     * @param consumer
-     *         the callback
-     * @throws IOException
-     *         in case of a decoding error
-     * @throws NullPointerException
-     *         if either headerBlock or consumer are null
-     */
-    public void decode(ByteBuffer headerBlock,
-                       boolean endOfHeaderBlock,
-                       DecodingCallback consumer) throws IOException {
-        requireNonNull(headerBlock, "headerBlock");
-        requireNonNull(consumer, "consumer");
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("reading %s, end of header block? %s",
-                                            headerBlock, endOfHeaderBlock));
-        }
-        while (headerBlock.hasRemaining()) {
-            proceed(headerBlock, consumer);
-        }
-        if (endOfHeaderBlock && state != State.READY) {
-            logger.log(NORMAL, () -> format("unexpected end of %s representation",
-                                            state));
-            throw new IOException("Unexpected end of header block");
-        }
-    }
-
-    private void proceed(ByteBuffer input, DecodingCallback action)
-            throws IOException {
-        switch (state) {
-            case READY:
-                resumeReady(input);
-                break;
-            case INDEXED:
-                resumeIndexed(input, action);
-                break;
-            case LITERAL:
-                resumeLiteral(input, action);
-                break;
-            case LITERAL_WITH_INDEXING:
-                resumeLiteralWithIndexing(input, action);
-                break;
-            case LITERAL_NEVER_INDEXED:
-                resumeLiteralNeverIndexed(input, action);
-                break;
-            case SIZE_UPDATE:
-                resumeSizeUpdate(input, action);
-                break;
-            default:
-                throw new InternalError("Unexpected decoder state: " + state);
-        }
-    }
-
-    private void resumeReady(ByteBuffer input) {
-        int b = input.get(input.position()) & 0xff; // absolute read
-        State s = states[b];
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("next binary representation %s (first byte 0x%02x)",
-                                           s, b));
-        }
-        switch (s) {
-            case INDEXED:
-                integerReader.configure(7);
-                state = State.INDEXED;
-                firstValueIndex = true;
-                break;
-            case LITERAL:
-                state = State.LITERAL;
-                firstValueIndex = (b & 0b0000_1111) != 0;
-                if (firstValueIndex) {
-                    integerReader.configure(4);
-                }
-                break;
-            case LITERAL_WITH_INDEXING:
-                state = State.LITERAL_WITH_INDEXING;
-                firstValueIndex = (b & 0b0011_1111) != 0;
-                if (firstValueIndex) {
-                    integerReader.configure(6);
-                }
-                break;
-            case LITERAL_NEVER_INDEXED:
-                state = State.LITERAL_NEVER_INDEXED;
-                firstValueIndex = (b & 0b0000_1111) != 0;
-                if (firstValueIndex) {
-                    integerReader.configure(4);
-                }
-                break;
-            case SIZE_UPDATE:
-                integerReader.configure(5);
-                state = State.SIZE_UPDATE;
-                firstValueIndex = true;
-                break;
-            default:
-                throw new InternalError(String.valueOf(s));
-        }
-        if (!firstValueIndex) {
-            input.get(); // advance, next stop: "String Literal"
-        }
-    }
-
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 1 |        Index (7+)         |
-    //            +---+---------------------------+
-    //
-    private void resumeIndexed(ByteBuffer input, DecodingCallback action)
-            throws IOException {
-        if (!integerReader.read(input)) {
-            return;
-        }
-        intValue = integerReader.get();
-        integerReader.reset();
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("indexed %s", intValue));
-        }
-        try {
-            HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
-            action.onIndexed(intValue, f.name, f.value);
-        } finally {
-            state = State.READY;
-        }
-    }
-
-    private HeaderTable.HeaderField getHeaderFieldAt(int index)
-            throws IOException
-    {
-        HeaderTable.HeaderField f;
-        try {
-            f = table.get(index);
-        } catch (IndexOutOfBoundsException e) {
-            throw new IOException("header fields table index", e);
-        }
-        return f;
-    }
-
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 0 | 0 | 0 | 0 |  Index (4+)   |
-    //            +---+---+-----------------------+
-    //            | H |     Value Length (7+)     |
-    //            +---+---------------------------+
-    //            | Value String (Length octets)  |
-    //            +-------------------------------+
-    //
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 0 | 0 | 0 | 0 |       0       |
-    //            +---+---+-----------------------+
-    //            | H |     Name Length (7+)      |
-    //            +---+---------------------------+
-    //            |  Name String (Length octets)  |
-    //            +---+---------------------------+
-    //            | H |     Value Length (7+)     |
-    //            +---+---------------------------+
-    //            | Value String (Length octets)  |
-    //            +-------------------------------+
-    //
-    private void resumeLiteral(ByteBuffer input, DecodingCallback action)
-            throws IOException {
-        if (!completeReading(input)) {
-            return;
-        }
-        try {
-            if (firstValueIndex) {
-                if (logger.isLoggable(NORMAL)) {
-                    logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
-                                                    intValue, value));
-                }
-                HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
-                action.onLiteral(intValue, f.name, value, valueHuffmanEncoded);
-            } else {
-                if (logger.isLoggable(NORMAL)) {
-                    logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
-                                                    name, value));
-                }
-                action.onLiteral(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
-            }
-        } finally {
-            cleanUpAfterReading();
-        }
-    }
-
-    //
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 0 | 1 |      Index (6+)       |
-    //            +---+---+-----------------------+
-    //            | H |     Value Length (7+)     |
-    //            +---+---------------------------+
-    //            | Value String (Length octets)  |
-    //            +-------------------------------+
-    //
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 0 | 1 |           0           |
-    //            +---+---+-----------------------+
-    //            | H |     Name Length (7+)      |
-    //            +---+---------------------------+
-    //            |  Name String (Length octets)  |
-    //            +---+---------------------------+
-    //            | H |     Value Length (7+)     |
-    //            +---+---------------------------+
-    //            | Value String (Length octets)  |
-    //            +-------------------------------+
-    //
-    private void resumeLiteralWithIndexing(ByteBuffer input,
-                                           DecodingCallback action)
-            throws IOException {
-        if (!completeReading(input)) {
-            return;
-        }
-        try {
-            //
-            // 1. (name, value) will be stored in the table as strings
-            // 2. Most likely the callback will also create strings from them
-            // ------------------------------------------------------------------------
-            //    Let's create those string beforehand (and only once!) to benefit everyone
-            //
-            String n;
-            String v = value.toString();
-            if (firstValueIndex) {
-                if (logger.isLoggable(NORMAL)) {
-                    logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
-                                                    intValue, value));
-                }
-                HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
-                n = f.name;
-                action.onLiteralWithIndexing(intValue, n, v, valueHuffmanEncoded);
-            } else {
-                n = name.toString();
-                if (logger.isLoggable(NORMAL)) {
-                    logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
-                                                    n, value));
-                }
-                action.onLiteralWithIndexing(n, nameHuffmanEncoded, v, valueHuffmanEncoded);
-            }
-            table.put(n, v);
-        } finally {
-            cleanUpAfterReading();
-        }
-    }
-
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 0 | 0 | 0 | 1 |  Index (4+)   |
-    //            +---+---+-----------------------+
-    //            | H |     Value Length (7+)     |
-    //            +---+---------------------------+
-    //            | Value String (Length octets)  |
-    //            +-------------------------------+
-    //
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 0 | 0 | 0 | 1 |       0       |
-    //            +---+---+-----------------------+
-    //            | H |     Name Length (7+)      |
-    //            +---+---------------------------+
-    //            |  Name String (Length octets)  |
-    //            +---+---------------------------+
-    //            | H |     Value Length (7+)     |
-    //            +---+---------------------------+
-    //            | Value String (Length octets)  |
-    //            +-------------------------------+
-    //
-    private void resumeLiteralNeverIndexed(ByteBuffer input,
-                                           DecodingCallback action)
-            throws IOException {
-        if (!completeReading(input)) {
-            return;
-        }
-        try {
-            if (firstValueIndex) {
-                if (logger.isLoggable(NORMAL)) {
-                    logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
-                                                    intValue, value));
-                }
-                HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
-                action.onLiteralNeverIndexed(intValue, f.name, value, valueHuffmanEncoded);
-            } else {
-                if (logger.isLoggable(NORMAL)) {
-                    logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
-                                                    name, value));
-                }
-                action.onLiteralNeverIndexed(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
-            }
-        } finally {
-            cleanUpAfterReading();
-        }
-    }
-
-    //              0   1   2   3   4   5   6   7
-    //            +---+---+---+---+---+---+---+---+
-    //            | 0 | 0 | 1 |   Max size (5+)   |
-    //            +---+---------------------------+
-    //
-    private void resumeSizeUpdate(ByteBuffer input,
-                                  DecodingCallback action) throws IOException {
-        if (!integerReader.read(input)) {
-            return;
-        }
-        intValue = integerReader.get();
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("dynamic table size update %s",
-                                            intValue));
-        }
-        assert intValue >= 0;
-        if (intValue > capacity) {
-            throw new IOException(
-                    format("Received capacity exceeds expected: capacity=%s, expected=%s",
-                           intValue, capacity));
-        }
-        integerReader.reset();
-        try {
-            action.onSizeUpdate(intValue);
-            table.setMaxSize(intValue);
-        } finally {
-            state = State.READY;
-        }
-    }
-
-    private boolean completeReading(ByteBuffer input) throws IOException {
-        if (!firstValueRead) {
-            if (firstValueIndex) {
-                if (!integerReader.read(input)) {
-                    return false;
-                }
-                intValue = integerReader.get();
-                integerReader.reset();
-            } else {
-                if (!stringReader.read(input, name)) {
-                    return false;
-                }
-                nameHuffmanEncoded = stringReader.isHuffmanEncoded();
-                stringReader.reset();
-            }
-            firstValueRead = true;
-            return false;
-        } else {
-            if (!stringReader.read(input, value)) {
-                return false;
-            }
-        }
-        valueHuffmanEncoded = stringReader.isHuffmanEncoded();
-        stringReader.reset();
-        return true;
-    }
-
-    private void cleanUpAfterReading() {
-        name.setLength(0);
-        value.setLength(0);
-        firstValueRead = false;
-        state = State.READY;
-    }
-
-    private enum State {
-        READY,
-        INDEXED,
-        LITERAL_NEVER_INDEXED,
-        LITERAL,
-        LITERAL_WITH_INDEXING,
-        SIZE_UPDATE
-    }
-
-    HeaderTable getTable() {
-        return table;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/DecodingCallback.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,295 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-
-/**
- * Delivers results of the {@link Decoder#decode(ByteBuffer, boolean,
- * DecodingCallback) decoding operation}.
- *
- * <p> Methods of the callback are never called by a decoder with any of the
- * arguments being {@code null}.
- *
- * @apiNote
- *
- * <p> The callback provides methods for all possible
- * <a href="https://tools.ietf.org/html/rfc7541#section-6">binary representations</a>.
- * This could be useful for implementing an intermediary, logging, debugging,
- * etc.
- *
- * <p> The callback is an interface in order to interoperate with lambdas (in
- * the most common use case):
- * <pre>{@code
- *     DecodingCallback callback = (name, value) -> System.out.println(name + ", " + value);
- * }</pre>
- *
- * <p> Names and values are {@link CharSequence}s rather than {@link String}s in
- * order to allow users to decide whether or not they need to create objects. A
- * {@code CharSequence} might be used in-place, for example, to be appended to
- * an {@link Appendable} (e.g. {@link StringBuilder}) and then discarded.
- *
- * <p> That said, if a passed {@code CharSequence} needs to outlast the method
- * call, it needs to be copied.
- *
- * @since 9
- */
-@FunctionalInterface
-public interface DecodingCallback {
-
-    /**
-     * A method the more specific methods of the callback forward their calls
-     * to.
-     *
-     * @param name
-     *         header name
-     * @param value
-     *         header value
-     */
-    void onDecoded(CharSequence name, CharSequence value);
-
-    /**
-     * A more finer-grained version of {@link #onDecoded(CharSequence,
-     * CharSequence)} that also reports on value sensitivity.
-     *
-     * <p> Value sensitivity must be considered, for example, when implementing
-     * an intermediary. A {@code value} is sensitive if it was represented as <a
-     * href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal Header
-     * Field Never Indexed</a>.
-     *
-     * <p> It is required that intermediaries MUST use the {@linkplain
-     * Encoder#header(CharSequence, CharSequence, boolean) same representation}
-     * for encoding this header field in order to protect its value which is not
-     * to be put at risk by compressing it.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes {@code onDecoded(name, value)}.
-     *
-     * @param name
-     *         header name
-     * @param value
-     *         header value
-     * @param sensitive
-     *         whether or not the value is sensitive
-     *
-     * @see #onLiteralNeverIndexed(int, CharSequence, CharSequence, boolean)
-     * @see #onLiteralNeverIndexed(CharSequence, boolean, CharSequence, boolean)
-     */
-    default void onDecoded(CharSequence name,
-                           CharSequence value,
-                           boolean sensitive) {
-        onDecoded(name, value);
-    }
-
-    /**
-     * An <a href="https://tools.ietf.org/html/rfc7541#section-6.1">Indexed
-     * Header Field</a> decoded.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes
-     * {@code onDecoded(name, value, false)}.
-     *
-     * @param index
-     *         index of an entry in the table
-     * @param name
-     *         header name
-     * @param value
-     *         header value
-     */
-    default void onIndexed(int index, CharSequence name, CharSequence value) {
-        onDecoded(name, value, false);
-    }
-
-    /**
-     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.2">Literal
-     * Header Field without Indexing</a> decoded, where a {@code name} was
-     * referred by an {@code index}.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes
-     * {@code onDecoded(name, value, false)}.
-     *
-     * @param index
-     *         index of an entry in the table
-     * @param name
-     *         header name
-     * @param value
-     *         header value
-     * @param valueHuffman
-     *         if the {@code value} was Huffman encoded
-     */
-    default void onLiteral(int index,
-                           CharSequence name,
-                           CharSequence value,
-                           boolean valueHuffman) {
-        onDecoded(name, value, false);
-    }
-
-    /**
-     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.2">Literal
-     * Header Field without Indexing</a> decoded, where both a {@code name} and
-     * a {@code value} were literal.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes
-     * {@code onDecoded(name, value, false)}.
-     *
-     * @param name
-     *         header name
-     * @param nameHuffman
-     *         if the {@code name} was Huffman encoded
-     * @param value
-     *         header value
-     * @param valueHuffman
-     *         if the {@code value} was Huffman encoded
-     */
-    default void onLiteral(CharSequence name,
-                           boolean nameHuffman,
-                           CharSequence value,
-                           boolean valueHuffman) {
-        onDecoded(name, value, false);
-    }
-
-    /**
-     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal
-     * Header Field Never Indexed</a> decoded, where a {@code name}
-     * was referred by an {@code index}.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes
-     * {@code onDecoded(name, value, true)}.
-     *
-     * @param index
-     *         index of an entry in the table
-     * @param name
-     *         header name
-     * @param value
-     *         header value
-     * @param valueHuffman
-     *         if the {@code value} was Huffman encoded
-     */
-    default void onLiteralNeverIndexed(int index,
-                                       CharSequence name,
-                                       CharSequence value,
-                                       boolean valueHuffman) {
-        onDecoded(name, value, true);
-    }
-
-    /**
-     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal
-     * Header Field Never Indexed</a> decoded, where both a {@code
-     * name} and a {@code value} were literal.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes
-     * {@code onDecoded(name, value, true)}.
-     *
-     * @param name
-     *         header name
-     * @param nameHuffman
-     *         if the {@code name} was Huffman encoded
-     * @param value
-     *         header value
-     * @param valueHuffman
-     *         if the {@code value} was Huffman encoded
-     */
-    default void onLiteralNeverIndexed(CharSequence name,
-                                       boolean nameHuffman,
-                                       CharSequence value,
-                                       boolean valueHuffman) {
-        onDecoded(name, value, true);
-    }
-
-    /**
-     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.1">Literal
-     * Header Field with Incremental Indexing</a> decoded, where a {@code name}
-     * was referred by an {@code index}.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes
-     * {@code onDecoded(name, value, false)}.
-     *
-     * @param index
-     *         index of an entry in the table
-     * @param name
-     *         header name
-     * @param value
-     *         header value
-     * @param valueHuffman
-     *         if the {@code value} was Huffman encoded
-     */
-    default void onLiteralWithIndexing(int index,
-                                       CharSequence name,
-                                       CharSequence value,
-                                       boolean valueHuffman) {
-        onDecoded(name, value, false);
-    }
-
-    /**
-     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.1">Literal
-     * Header Field with Incremental Indexing</a> decoded, where both a {@code
-     * name} and a {@code value} were literal.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation invokes
-     * {@code onDecoded(name, value, false)}.
-     *
-     * @param name
-     *         header name
-     * @param nameHuffman
-     *         if the {@code name} was Huffman encoded
-     * @param value
-     *         header value
-     * @param valueHuffman
-     *         if the {@code value} was Huffman encoded
-     */
-    default void onLiteralWithIndexing(CharSequence name,
-                                       boolean nameHuffman,
-                                       CharSequence value,
-                                       boolean valueHuffman) {
-        onDecoded(name, value, false);
-    }
-
-    /**
-     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.3">Dynamic Table
-     * Size Update</a> decoded.
-     *
-     * @implSpec
-     *
-     * <p> The default implementation does nothing.
-     *
-     * @param capacity
-     *         new capacity of the header table
-     */
-    default void onSizeUpdate(int capacity) { }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/Encoder.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,525 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import java.net.http.internal.hpack.HPACK.Logger;
-
-import java.nio.ByteBuffer;
-import java.nio.ReadOnlyBufferException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.NORMAL;
-
-/**
- * Encodes headers to their binary representation.
- *
- * <p> Typical lifecycle looks like this:
- *
- * <p> {@link #Encoder(int) new Encoder}
- * ({@link #setMaxCapacity(int) setMaxCapacity}?
- * {@link #encode(ByteBuffer) encode})*
- *
- * <p> Suppose headers are represented by {@code Map<String, List<String>>}.
- * A supplier and a consumer of {@link ByteBuffer}s in forms of
- * {@code Supplier<ByteBuffer>} and {@code Consumer<ByteBuffer>} respectively.
- * Then to encode headers, the following approach might be used:
- *
- * <pre>{@code
- *     for (Map.Entry<String, List<String>> h : headers.entrySet()) {
- *         String name = h.getKey();
- *         for (String value : h.getValue()) {
- *             encoder.header(name, value);        // Set up header
- *             boolean encoded;
- *             do {
- *                 ByteBuffer b = buffersSupplier.get();
- *                 encoded = encoder.encode(b);    // Encode the header
- *                 buffersConsumer.accept(b);
- *             } while (!encoded);
- *         }
- *     }
- * }</pre>
- *
- * <p> Though the specification <a href="https://tools.ietf.org/html/rfc7541#section-2">does not define</a>
- * how an encoder is to be implemented, a default implementation is provided by
- * the method {@link #header(CharSequence, CharSequence, boolean)}.
- *
- * <p> To provide a custom encoding implementation, {@code Encoder} has to be
- * extended. A subclass then can access methods for encoding using specific
- * representations (e.g. {@link #literal(int, CharSequence, boolean) literal},
- * {@link #indexed(int) indexed}, etc.)
- *
- * @apiNote
- *
- * <p> An Encoder provides an incremental way of encoding headers.
- * {@link #encode(ByteBuffer)} takes a buffer a returns a boolean indicating
- * whether, or not, the buffer was sufficiently sized to hold the
- * remaining of the encoded representation.
- *
- * <p> This way, there's no need to provide a buffer of a specific size, or to
- * resize (and copy) the buffer on demand, when the remaining encoded
- * representation will not fit in the buffer's remaining space. Instead, an
- * array of existing buffers can be used, prepended with a frame that encloses
- * the resulting header block afterwards.
- *
- * <p> Splitting the encoding operation into header set up and header encoding,
- * separates long lived arguments ({@code name}, {@code value},
- * {@code sensitivity}, etc.) from the short lived ones (e.g. {@code buffer}),
- * simplifying each operation itself.
- *
- * @implNote
- *
- * <p> The default implementation does not use dynamic table. It reports to a
- * coupled Decoder a size update with the value of {@code 0}, and never changes
- * it afterwards.
- *
- * @since 9
- */
-public class Encoder {
-
-    private static final AtomicLong ENCODERS_IDS = new AtomicLong();
-
-    // TODO: enum: no huffman/smart huffman/always huffman
-    private static final boolean DEFAULT_HUFFMAN = true;
-
-    private final Logger logger;
-    private final long id;
-    private final IndexedWriter indexedWriter = new IndexedWriter();
-    private final LiteralWriter literalWriter = new LiteralWriter();
-    private final LiteralNeverIndexedWriter literalNeverIndexedWriter
-            = new LiteralNeverIndexedWriter();
-    private final LiteralWithIndexingWriter literalWithIndexingWriter
-            = new LiteralWithIndexingWriter();
-    private final SizeUpdateWriter sizeUpdateWriter = new SizeUpdateWriter();
-    private final BulkSizeUpdateWriter bulkSizeUpdateWriter
-            = new BulkSizeUpdateWriter();
-
-    private BinaryRepresentationWriter writer;
-    private final HeaderTable headerTable;
-
-    private boolean encoding;
-
-    private int maxCapacity;
-    private int currCapacity;
-    private int lastCapacity;
-    private long minCapacity;
-    private boolean capacityUpdate;
-    private boolean configuredCapacityUpdate;
-
-    /**
-     * Constructs an {@code Encoder} with the specified maximum capacity of the
-     * header table.
-     *
-     * <p> The value has to be agreed between decoder and encoder out-of-band,
-     * e.g. by a protocol that uses HPACK
-     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
-     *
-     * @param maxCapacity
-     *         a non-negative integer
-     *
-     * @throws IllegalArgumentException
-     *         if maxCapacity is negative
-     */
-    public Encoder(int maxCapacity) {
-        id = ENCODERS_IDS.incrementAndGet();
-        this.logger = HPACK.getLogger().subLogger("Encoder#" + id);
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("new encoder with maximum table size %s",
-                                            maxCapacity));
-        }
-        if (logger.isLoggable(EXTRA)) {
-            /* To correlate with logging outside HPACK, knowing
-               hashCode/toString is important */
-            logger.log(EXTRA, () -> {
-                String hashCode = Integer.toHexString(
-                        System.identityHashCode(this));
-                /* Since Encoder can be subclassed hashCode AND identity
-                   hashCode might be different. So let's print both. */
-                return format("toString='%s', hashCode=%s, identityHashCode=%s",
-                              toString(), hashCode(), hashCode);
-            });
-        }
-        if (maxCapacity < 0) {
-            throw new IllegalArgumentException(
-                    "maxCapacity >= 0: " + maxCapacity);
-        }
-        // Initial maximum capacity update mechanics
-        minCapacity = Long.MAX_VALUE;
-        currCapacity = -1;
-        setMaxCapacity0(maxCapacity);
-        headerTable = new HeaderTable(lastCapacity, logger.subLogger("HeaderTable"));
-    }
-
-    /**
-     * Sets up the given header {@code (name, value)}.
-     *
-     * <p> Fixates {@code name} and {@code value} for the duration of encoding.
-     *
-     * @param name
-     *         the name
-     * @param value
-     *         the value
-     *
-     * @throws NullPointerException
-     *         if any of the arguments are {@code null}
-     * @throws IllegalStateException
-     *         if the encoder hasn't fully encoded the previous header, or
-     *         hasn't yet started to encode it
-     * @see #header(CharSequence, CharSequence, boolean)
-     */
-    public void header(CharSequence name, CharSequence value)
-            throws IllegalStateException {
-        header(name, value, false);
-    }
-
-    /**
-     * Sets up the given header {@code (name, value)} with possibly sensitive
-     * value.
-     *
-     * <p> If the {@code value} is sensitive (think security, secrecy, etc.)
-     * this encoder will compress it using a special representation
-     * (see <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">6.2.3.  Literal Header Field Never Indexed</a>).
-     *
-     * <p> Fixates {@code name} and {@code value} for the duration of encoding.
-     *
-     * @param name
-     *         the name
-     * @param value
-     *         the value
-     * @param sensitive
-     *         whether or not the value is sensitive
-     *
-     * @throws NullPointerException
-     *         if any of the arguments are {@code null}
-     * @throws IllegalStateException
-     *         if the encoder hasn't fully encoded the previous header, or
-     *         hasn't yet started to encode it
-     * @see #header(CharSequence, CharSequence)
-     * @see DecodingCallback#onDecoded(CharSequence, CharSequence, boolean)
-     */
-    public void header(CharSequence name,
-                       CharSequence value,
-                       boolean sensitive) throws IllegalStateException {
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("encoding ('%s', '%s'), sensitive: %s",
-                                            name, value, sensitive));
-        }
-        // Arguably a good balance between complexity of implementation and
-        // efficiency of encoding
-        requireNonNull(name, "name");
-        requireNonNull(value, "value");
-        HeaderTable t = getHeaderTable();
-        int index = t.indexOf(name, value);
-        if (index > 0) {
-            indexed(index);
-        } else if (index < 0) {
-            if (sensitive) {
-                literalNeverIndexed(-index, value, DEFAULT_HUFFMAN);
-            } else {
-                literal(-index, value, DEFAULT_HUFFMAN);
-            }
-        } else {
-            if (sensitive) {
-                literalNeverIndexed(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
-            } else {
-                literal(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
-            }
-        }
-    }
-
-    /**
-     * Sets a maximum capacity of the header table.
-     *
-     * <p> The value has to be agreed between decoder and encoder out-of-band,
-     * e.g. by a protocol that uses HPACK
-     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
-     *
-     * <p> May be called any number of times after or before a complete header
-     * has been encoded.
-     *
-     * <p> If the encoder decides to change the actual capacity, an update will
-     * be encoded before a new encoding operation starts.
-     *
-     * @param capacity
-     *         a non-negative integer
-     *
-     * @throws IllegalArgumentException
-     *         if capacity is negative
-     * @throws IllegalStateException
-     *         if the encoder hasn't fully encoded the previous header, or
-     *         hasn't yet started to encode it
-     */
-    public void setMaxCapacity(int capacity) {
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("setting maximum table size to %s",
-                                            capacity));
-        }
-        setMaxCapacity0(capacity);
-    }
-
-    private void setMaxCapacity0(int capacity) {
-        checkEncoding();
-        if (capacity < 0) {
-            throw new IllegalArgumentException("capacity >= 0: " + capacity);
-        }
-        int calculated = calculateCapacity(capacity);
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("actual maximum table size will be %s",
-                                            calculated));
-        }
-        if (calculated < 0 || calculated > capacity) {
-            throw new IllegalArgumentException(
-                    format("0 <= calculated <= capacity: calculated=%s, capacity=%s",
-                            calculated, capacity));
-        }
-        capacityUpdate = true;
-        // maxCapacity needs to be updated unconditionally, so the encoder
-        // always has the newest one (in case it decides to update it later
-        // unsolicitedly)
-        // Suppose maxCapacity = 4096, and the encoder has decided to use only
-        // 2048. It later can choose anything else from the region [0, 4096].
-        maxCapacity = capacity;
-        lastCapacity = calculated;
-        minCapacity = Math.min(minCapacity, lastCapacity);
-    }
-
-    /**
-     * Calculates actual capacity to be used by this encoder in response to
-     * a request to update maximum table size.
-     *
-     * <p> Default implementation does not add anything to the headers table,
-     * hence this method returns {@code 0}.
-     *
-     * <p> It is an error to return a value {@code c}, where {@code c < 0} or
-     * {@code c > maxCapacity}.
-     *
-     * @param maxCapacity
-     *         upper bound
-     *
-     * @return actual capacity
-     */
-    protected int calculateCapacity(int maxCapacity) {
-        return 0;
-    }
-
-    /**
-     * Encodes the {@linkplain #header(CharSequence, CharSequence) set up}
-     * header into the given buffer.
-     *
-     * <p> The encoder writes as much as possible of the header's binary
-     * representation into the given buffer, starting at the buffer's position,
-     * and increments its position to reflect the bytes written. The buffer's
-     * mark and limit will not be modified.
-     *
-     * <p> Once the method has returned {@code true}, the current header is
-     * deemed encoded. A new header may be set up.
-     *
-     * @param headerBlock
-     *         the buffer to encode the header into, may be empty
-     *
-     * @return {@code true} if the current header has been fully encoded,
-     *         {@code false} otherwise
-     *
-     * @throws NullPointerException
-     *         if the buffer is {@code null}
-     * @throws ReadOnlyBufferException
-     *         if this buffer is read-only
-     * @throws IllegalStateException
-     *         if there is no set up header
-     */
-    public final boolean encode(ByteBuffer headerBlock) {
-        if (!encoding) {
-            throw new IllegalStateException("A header hasn't been set up");
-        }
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("writing to %s", headerBlock));
-        }
-        if (!prependWithCapacityUpdate(headerBlock)) { // TODO: log
-            return false;
-        }
-        boolean done = writer.write(headerTable, headerBlock);
-        if (done) {
-            writer.reset(); // FIXME: WHY?
-            encoding = false;
-        }
-        return done;
-    }
-
-    private boolean prependWithCapacityUpdate(ByteBuffer headerBlock) {
-        if (capacityUpdate) {
-            if (!configuredCapacityUpdate) {
-                List<Integer> sizes = new LinkedList<>();
-                if (minCapacity < currCapacity) {
-                    sizes.add((int) minCapacity);
-                    if (minCapacity != lastCapacity) {
-                        sizes.add(lastCapacity);
-                    }
-                } else if (lastCapacity != currCapacity) {
-                    sizes.add(lastCapacity);
-                }
-                bulkSizeUpdateWriter.maxHeaderTableSizes(sizes);
-                configuredCapacityUpdate = true;
-            }
-            boolean done = bulkSizeUpdateWriter.write(headerTable, headerBlock);
-            if (done) {
-                minCapacity = lastCapacity;
-                currCapacity = lastCapacity;
-                bulkSizeUpdateWriter.reset();
-                capacityUpdate = false;
-                configuredCapacityUpdate = false;
-            }
-            return done;
-        }
-        return true;
-    }
-
-    protected final void indexed(int index) throws IndexOutOfBoundsException {
-        checkEncoding();
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("indexed %s", index));
-        }
-        encoding = true;
-        writer = indexedWriter.index(index);
-    }
-
-    protected final void literal(int index,
-                                 CharSequence value,
-                                 boolean useHuffman)
-            throws IndexOutOfBoundsException {
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("literal without indexing ('%s', '%s')",
-                                           index, value));
-        }
-        checkEncoding();
-        encoding = true;
-        writer = literalWriter
-                .index(index).value(value, useHuffman);
-    }
-
-    protected final void literal(CharSequence name,
-                                 boolean nameHuffman,
-                                 CharSequence value,
-                                 boolean valueHuffman) {
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("literal without indexing ('%s', '%s')",
-                                           name, value));
-        }
-        checkEncoding();
-        encoding = true;
-        writer = literalWriter
-                .name(name, nameHuffman).value(value, valueHuffman);
-    }
-
-    protected final void literalNeverIndexed(int index,
-                                             CharSequence value,
-                                             boolean valueHuffman)
-            throws IndexOutOfBoundsException {
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("literal never indexed ('%s', '%s')",
-                                           index, value));
-        }
-        checkEncoding();
-        encoding = true;
-        writer = literalNeverIndexedWriter
-                .index(index).value(value, valueHuffman);
-    }
-
-    protected final void literalNeverIndexed(CharSequence name,
-                                             boolean nameHuffman,
-                                             CharSequence value,
-                                             boolean valueHuffman) {
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("literal never indexed ('%s', '%s')",
-                                           name, value));
-        }
-        checkEncoding();
-        encoding = true;
-        writer = literalNeverIndexedWriter
-                .name(name, nameHuffman).value(value, valueHuffman);
-    }
-
-    protected final void literalWithIndexing(int index,
-                                             CharSequence value,
-                                             boolean valueHuffman)
-            throws IndexOutOfBoundsException {
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("literal with incremental indexing ('%s', '%s')",
-                                           index, value));
-        }
-        checkEncoding();
-        encoding = true;
-        writer = literalWithIndexingWriter
-                .index(index).value(value, valueHuffman);
-    }
-
-    protected final void literalWithIndexing(CharSequence name,
-                                             boolean nameHuffman,
-                                             CharSequence value,
-                                             boolean valueHuffman) {
-        if (logger.isLoggable(EXTRA)) { // TODO: include huffman info?
-            logger.log(EXTRA, () -> format("literal with incremental indexing ('%s', '%s')",
-                                           name, value));
-        }
-        checkEncoding();
-        encoding = true;
-        writer = literalWithIndexingWriter
-                .name(name, nameHuffman).value(value, valueHuffman);
-    }
-
-    protected final void sizeUpdate(int capacity)
-            throws IllegalArgumentException {
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("dynamic table size update %s",
-                                           capacity));
-        }
-        checkEncoding();
-        // Ensure subclass follows the contract
-        if (capacity > this.maxCapacity) {
-            throw new IllegalArgumentException(
-                    format("capacity <= maxCapacity: capacity=%s, maxCapacity=%s",
-                            capacity, maxCapacity));
-        }
-        writer = sizeUpdateWriter.maxHeaderTableSize(capacity);
-    }
-
-    protected final int getMaxCapacity() {
-        return maxCapacity;
-    }
-
-    protected final HeaderTable getHeaderTable() {
-        return headerTable;
-    }
-
-    protected final void checkEncoding() { // TODO: better name e.g. checkIfEncodingInProgress()
-        if (encoding) {
-            throw new IllegalStateException(
-                    "Previous encoding operation hasn't finished yet");
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/HPACK.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,174 +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 java.net.http.internal.hpack;
-
-import java.net.http.internal.common.Utils;
-import java.net.http.internal.hpack.HPACK.Logger.Level;
-
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.Map;
-import java.util.ResourceBundle;
-import java.util.function.Supplier;
-
-import static java.lang.String.format;
-import static java.util.stream.Collectors.joining;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.NONE;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.NORMAL;
-
-/**
- * Internal utilities and stuff.
- */
-public final class HPACK {
-
-    private static final RootLogger LOGGER;
-    private static final Map<String, Level> logLevels =
-            Map.of("NORMAL", NORMAL, "EXTRA", EXTRA);
-
-    static {
-        String PROPERTY = "jdk.internal.httpclient.hpack.log.level";
-
-        String value = AccessController.doPrivileged(
-                (PrivilegedAction<String>) () -> System.getProperty(PROPERTY));
-
-        if (value == null) {
-            LOGGER = new RootLogger(NONE);
-        } else {
-            String upperCasedValue = value.toUpperCase();
-            Level l = logLevels.get(upperCasedValue);
-            if (l == null) {
-                LOGGER = new RootLogger(NONE);
-                LOGGER.log(System.Logger.Level.INFO,
-                        () -> format("%s value '%s' not recognized (use %s); logging disabled",
-                                     PROPERTY, value, logLevels.keySet().stream().collect(joining(", "))));
-            } else {
-                LOGGER = new RootLogger(l);
-                LOGGER.log(System.Logger.Level.DEBUG,
-                        () -> format("logging level %s", l));
-            }
-        }
-    }
-
-    public static Logger getLogger() {
-        return LOGGER;
-    }
-
-    private HPACK() { }
-
-    /**
-     * The purpose of this logger is to provide means of diagnosing issues _in
-     * the HPACK implementation_. It's not a general purpose logger.
-     */
-    // implements System.Logger to make it possible to skip this class
-    // when looking for the Caller.
-    public static class Logger implements System.Logger {
-
-        /**
-         * Log detail level.
-         */
-        public enum Level {
-
-            NONE(0, System.Logger.Level.OFF),
-            NORMAL(1, System.Logger.Level.DEBUG),
-            EXTRA(2, System.Logger.Level.TRACE);
-
-            private final int level;
-            final System.Logger.Level systemLevel;
-
-            Level(int i, System.Logger.Level system) {
-                level = i;
-                systemLevel = system;
-            }
-
-            public final boolean implies(Level other) {
-                return this.level >= other.level;
-            }
-        }
-
-        private final String name;
-        private final Level level;
-        private final String path;
-        private final System.Logger logger;
-
-        private Logger(String path, String name, Level level) {
-            this(path, name, level, null);
-        }
-
-        private Logger(String p, String name, Level level, System.Logger logger) {
-            this.path = p;
-            this.name = name;
-            this.level = level;
-            this.logger = Utils.getHpackLogger(path::toString, level.systemLevel);
-        }
-
-        public final String getName() {
-            return name;
-        }
-
-        @Override
-        public boolean isLoggable(System.Logger.Level level) {
-            return logger.isLoggable(level);
-        }
-
-        @Override
-        public void log(System.Logger.Level level, ResourceBundle bundle, String msg, Throwable thrown) {
-            logger.log(level, bundle, msg,thrown);
-        }
-
-        @Override
-        public void log(System.Logger.Level level, ResourceBundle bundle, String format, Object... params) {
-            logger.log(level, bundle, format, params);
-        }
-
-        /*
-         * Usual performance trick for logging, reducing performance overhead in
-         * the case where logging with the specified level is a NOP.
-         */
-
-        public boolean isLoggable(Level level) {
-            return this.level.implies(level);
-        }
-
-        public void log(Level level, Supplier<String> s) {
-            if (this.level.implies(level)) {
-                logger.log(level.systemLevel, s);
-            }
-        }
-
-        public Logger subLogger(String name) {
-            return new Logger(path + "/" + name, name, level);
-        }
-
-    }
-
-    private static final class RootLogger extends Logger {
-
-        protected RootLogger(Level level) {
-            super("hpack", "hpack", level);
-        }
-
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/HeaderTable.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,546 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import java.net.http.internal.hpack.HPACK.Logger;
-import jdk.internal.vm.annotation.Stable;
-
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.NoSuchElementException;
-
-import static java.lang.String.format;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static java.net.http.internal.hpack.HPACK.Logger.Level.NORMAL;
-
-//
-// Header Table combined from two tables: static and dynamic.
-//
-// There is a single address space for index values. Index-aware methods
-// correspond to the table as a whole. Size-aware methods only to the dynamic
-// part of it.
-//
-final class HeaderTable {
-
-    @Stable
-    private static final HeaderField[] staticTable = {
-            null, // To make index 1-based, instead of 0-based
-            new HeaderField(":authority"),
-            new HeaderField(":method", "GET"),
-            new HeaderField(":method", "POST"),
-            new HeaderField(":path", "/"),
-            new HeaderField(":path", "/index.html"),
-            new HeaderField(":scheme", "http"),
-            new HeaderField(":scheme", "https"),
-            new HeaderField(":status", "200"),
-            new HeaderField(":status", "204"),
-            new HeaderField(":status", "206"),
-            new HeaderField(":status", "304"),
-            new HeaderField(":status", "400"),
-            new HeaderField(":status", "404"),
-            new HeaderField(":status", "500"),
-            new HeaderField("accept-charset"),
-            new HeaderField("accept-encoding", "gzip, deflate"),
-            new HeaderField("accept-language"),
-            new HeaderField("accept-ranges"),
-            new HeaderField("accept"),
-            new HeaderField("access-control-allow-origin"),
-            new HeaderField("age"),
-            new HeaderField("allow"),
-            new HeaderField("authorization"),
-            new HeaderField("cache-control"),
-            new HeaderField("content-disposition"),
-            new HeaderField("content-encoding"),
-            new HeaderField("content-language"),
-            new HeaderField("content-length"),
-            new HeaderField("content-location"),
-            new HeaderField("content-range"),
-            new HeaderField("content-type"),
-            new HeaderField("cookie"),
-            new HeaderField("date"),
-            new HeaderField("etag"),
-            new HeaderField("expect"),
-            new HeaderField("expires"),
-            new HeaderField("from"),
-            new HeaderField("host"),
-            new HeaderField("if-match"),
-            new HeaderField("if-modified-since"),
-            new HeaderField("if-none-match"),
-            new HeaderField("if-range"),
-            new HeaderField("if-unmodified-since"),
-            new HeaderField("last-modified"),
-            new HeaderField("link"),
-            new HeaderField("location"),
-            new HeaderField("max-forwards"),
-            new HeaderField("proxy-authenticate"),
-            new HeaderField("proxy-authorization"),
-            new HeaderField("range"),
-            new HeaderField("referer"),
-            new HeaderField("refresh"),
-            new HeaderField("retry-after"),
-            new HeaderField("server"),
-            new HeaderField("set-cookie"),
-            new HeaderField("strict-transport-security"),
-            new HeaderField("transfer-encoding"),
-            new HeaderField("user-agent"),
-            new HeaderField("vary"),
-            new HeaderField("via"),
-            new HeaderField("www-authenticate")
-    };
-
-    private static final int STATIC_TABLE_LENGTH = staticTable.length - 1;
-    private static final int ENTRY_SIZE = 32;
-    private static final Map<String, LinkedHashMap<String, Integer>> staticIndexes;
-
-    static {
-        staticIndexes = new HashMap<>(STATIC_TABLE_LENGTH); // TODO: Map.of
-        for (int i = 1; i <= STATIC_TABLE_LENGTH; i++) {
-            HeaderField f = staticTable[i];
-            Map<String, Integer> values = staticIndexes
-                    .computeIfAbsent(f.name, k -> new LinkedHashMap<>());
-            values.put(f.value, i);
-        }
-    }
-
-    private final Logger logger;
-    private final Table dynamicTable = new Table(0);
-    private int maxSize;
-    private int size;
-
-    public HeaderTable(int maxSize, Logger logger) {
-        this.logger = logger;
-        setMaxSize(maxSize);
-    }
-
-    //
-    // The method returns:
-    //
-    // * a positive integer i where i (i = [1..Integer.MAX_VALUE]) is an
-    // index of an entry with a header (n, v), where n.equals(name) &&
-    // v.equals(value)
-    //
-    // * a negative integer j where j (j = [-Integer.MAX_VALUE..-1]) is an
-    // index of an entry with a header (n, v), where n.equals(name)
-    //
-    // * 0 if there's no entry e such that e.getName().equals(name)
-    //
-    // The rationale behind this design is to allow to pack more useful data
-    // into a single invocation, facilitating a single pass where possible
-    // (the idea is the same as in java.util.Arrays.binarySearch(int[], int)).
-    //
-    public int indexOf(CharSequence name, CharSequence value) {
-        // Invoking toString() will possibly allocate Strings for the sake of
-        // the search, which doesn't feel right.
-        String n = name.toString();
-        String v = value.toString();
-
-        // 1. Try exact match in the static region
-        Map<String, Integer> values = staticIndexes.get(n);
-        if (values != null) {
-            Integer idx = values.get(v);
-            if (idx != null) {
-                return idx;
-            }
-        }
-        // 2. Try exact match in the dynamic region
-        int didx = dynamicTable.indexOf(n, v);
-        if (didx > 0) {
-            return STATIC_TABLE_LENGTH + didx;
-        } else if (didx < 0) {
-            if (values != null) {
-                // 3. Return name match from the static region
-                return -values.values().iterator().next(); // Iterator allocation
-            } else {
-                // 4. Return name match from the dynamic region
-                return -STATIC_TABLE_LENGTH + didx;
-            }
-        } else {
-            if (values != null) {
-                // 3. Return name match from the static region
-                return -values.values().iterator().next(); // Iterator allocation
-            } else {
-                return 0;
-            }
-        }
-    }
-
-    public int size() {
-        return size;
-    }
-
-    public int maxSize() {
-        return maxSize;
-    }
-
-    public int length() {
-        return STATIC_TABLE_LENGTH + dynamicTable.size();
-    }
-
-    HeaderField get(int index) {
-        checkIndex(index);
-        if (index <= STATIC_TABLE_LENGTH) {
-            return staticTable[index];
-        } else {
-            return dynamicTable.get(index - STATIC_TABLE_LENGTH);
-        }
-    }
-
-    void put(CharSequence name, CharSequence value) {
-        // Invoking toString() will possibly allocate Strings. But that's
-        // unavoidable at this stage. If a CharSequence is going to be stored in
-        // the table, it must not be mutable (e.g. for the sake of hashing).
-        put(new HeaderField(name.toString(), value.toString()));
-    }
-
-    private void put(HeaderField h) {
-        if (logger.isLoggable(NORMAL)) {
-            logger.log(NORMAL, () -> format("adding ('%s', '%s')",
-                                            h.name, h.value));
-        }
-        int entrySize = sizeOf(h);
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("size of ('%s', '%s') is %s",
-                                           h.name, h.value, entrySize));
-        }
-        while (entrySize > maxSize - size && size != 0) {
-            if (logger.isLoggable(EXTRA)) {
-                logger.log(EXTRA, () -> format("insufficient space %s, must evict entry",
-                                               (maxSize - size)));
-            }
-            evictEntry();
-        }
-        if (entrySize > maxSize - size) {
-            if (logger.isLoggable(EXTRA)) {
-                logger.log(EXTRA, () -> format("not adding ('%s, '%s'), too big",
-                                               h.name, h.value));
-            }
-            return;
-        }
-        size += entrySize;
-        dynamicTable.add(h);
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("('%s, '%s') added", h.name, h.value));
-            logger.log(EXTRA, this::toString);
-        }
-    }
-
-    void setMaxSize(int maxSize) {
-        if (maxSize < 0) {
-            throw new IllegalArgumentException(
-                    "maxSize >= 0: maxSize=" + maxSize);
-        }
-        while (maxSize < size && size != 0) {
-            evictEntry();
-        }
-        this.maxSize = maxSize;
-        int upperBound = (maxSize / ENTRY_SIZE) + 1;
-        this.dynamicTable.setCapacity(upperBound);
-    }
-
-    HeaderField evictEntry() {
-        HeaderField f = dynamicTable.remove();
-        int s = sizeOf(f);
-        this.size -= s;
-        if (logger.isLoggable(EXTRA)) {
-            logger.log(EXTRA, () -> format("evicted entry ('%s', '%s') of size %s",
-                                           f.name, f.value, s));
-            logger.log(EXTRA, this::toString);
-        }
-        return f;
-    }
-
-    @Override
-    public String toString() {
-        double used = maxSize == 0 ? 0 : 100 * (((double) size) / maxSize);
-        return format("dynamic length: %d, full length: %s, used space: %s/%s (%.1f%%)",
-                      dynamicTable.size(), length(), size, maxSize, used);
-    }
-
-    private int checkIndex(int index) {
-        int len = length();
-        if (index < 1 || index > len) {
-            throw new IndexOutOfBoundsException(
-                    format("1 <= index <= length(): index=%s, length()=%s",
-                           index, len));
-        }
-        return index;
-    }
-
-    int sizeOf(HeaderField f) {
-        return f.name.length() + f.value.length() + ENTRY_SIZE;
-    }
-
-    //
-    // Diagnostic information in the form used in the RFC 7541
-    //
-    String getStateString() {
-        if (size == 0) {
-            return "empty.";
-        }
-
-        StringBuilder b = new StringBuilder();
-        for (int i = 1, size = dynamicTable.size(); i <= size; i++) {
-            HeaderField e = dynamicTable.get(i);
-            b.append(format("[%3d] (s = %3d) %s: %s\n", i,
-                    sizeOf(e), e.name, e.value));
-        }
-        b.append(format("      Table size:%4s", this.size));
-        return b.toString();
-    }
-
-    // Convert to a Value Object (JDK-8046159)?
-    static final class HeaderField {
-
-        final String name;
-        final String value;
-
-        public HeaderField(String name) {
-            this(name, "");
-        }
-
-        public HeaderField(String name, String value) {
-            this.name = name;
-            this.value = value;
-        }
-
-        @Override
-        public String toString() {
-            return value.isEmpty() ? name : name + ": " + value;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            HeaderField that = (HeaderField) o;
-            return name.equals(that.name) && value.equals(that.value);
-        }
-
-        @Override
-        public int hashCode() {
-            return 31 * name.hashCode() + value.hashCode();
-        }
-    }
-
-    //
-    // To quickly find an index of an entry in the dynamic table with the given
-    // contents an effective inverse mapping is needed. Here's a simple idea
-    // behind such a mapping.
-    //
-    // # The problem:
-    //
-    // We have a queue with an O(1) lookup by index:
-    //
-    //     get: index -> x
-    //
-    // What we want is an O(1) reverse lookup:
-    //
-    //     indexOf: x -> index
-    //
-    // # Solution:
-    //
-    // Let's store an inverse mapping in a Map<x, Integer>. This have a problem
-    // that when a new element is added to the queue, all indexes in the map
-    // become invalid. Namely, the new element is assigned with an index of 1,
-    // and each index i, i > 1 becomes shifted by 1 to the left:
-    //
-    //     1, 1, 2, 3, ... , n-1, n
-    //
-    // Re-establishing the invariant would seem to require a pass through the
-    // map incrementing all indexes (map values) by 1, which is O(n).
-    //
-    // The good news is we can do much better then this!
-    //
-    // Let's create a single field of type long, called 'counter'. Then each
-    // time a new element 'x' is added to the queue, a value of this field gets
-    // incremented. Then the resulting value of the 'counter_x' is then put as a
-    // value under key 'x' to the map:
-    //
-    //    map.put(x, counter_x)
-    //
-    // It gives us a map that maps an element to a value the counter had at the
-    // time the element had been added.
-    //
-    // In order to retrieve an index of any element 'x' in the queue (at any
-    // given time) we simply need to subtract the value (the snapshot of the
-    // counter at the time when the 'x' was added) from the current value of the
-    // counter. This operation basically answers the question:
-    //
-    //     How many elements ago 'x' was the tail of the queue?
-    //
-    // Which is the same as its index in the queue now. Given, of course, it's
-    // still in the queue.
-    //
-    // I'm pretty sure in a real life long overflow will never happen, so it's
-    // not too practical to add recalibrating code, but a pedantic person might
-    // want to do so:
-    //
-    //     if (counter == Long.MAX_VALUE) {
-    //         recalibrate();
-    //     }
-    //
-    // Where 'recalibrate()' goes through the table doing this:
-    //
-    //     value -= counter
-    //
-    // That's given, of course, the size of the table itself is less than
-    // Long.MAX_VALUE :-)
-    //
-    private static final class Table {
-
-        private final Map<String, Map<String, Long>> map;
-        private final CircularBuffer<HeaderField> buffer;
-        private long counter = 1;
-
-        Table(int capacity) {
-            buffer = new CircularBuffer<>(capacity);
-            map = new HashMap<>(capacity);
-        }
-
-        void add(HeaderField f) {
-            buffer.add(f);
-            Map<String, Long> values = map.computeIfAbsent(f.name, k -> new HashMap<>());
-            values.put(f.value, counter++);
-        }
-
-        HeaderField get(int index) {
-            return buffer.get(index - 1);
-        }
-
-        int indexOf(String name, String value) {
-            Map<String, Long> values = map.get(name);
-            if (values == null) {
-                return 0;
-            }
-            Long index = values.get(value);
-            if (index != null) {
-                return (int) (counter - index);
-            } else {
-                assert !values.isEmpty();
-                Long any = values.values().iterator().next(); // Iterator allocation
-                return -(int) (counter - any);
-            }
-        }
-
-        HeaderField remove() {
-            HeaderField f = buffer.remove();
-            Map<String, Long> values = map.get(f.name);
-            Long index = values.remove(f.value);
-            assert index != null;
-            if (values.isEmpty()) {
-                map.remove(f.name);
-            }
-            return f;
-        }
-
-        int size() {
-            return buffer.size;
-        }
-
-        public void setCapacity(int capacity) {
-            buffer.resize(capacity);
-        }
-    }
-
-    //                    head
-    //                    v
-    // [ ][ ][A][B][C][D][ ][ ][ ]
-    //        ^
-    //        tail
-    //
-    //       |<- size ->| (4)
-    // |<------ capacity ------->| (9)
-    //
-    static final class CircularBuffer<E> {
-
-        int tail, head, size, capacity;
-        Object[] elements;
-
-        CircularBuffer(int capacity) {
-            this.capacity = capacity;
-            elements = new Object[capacity];
-        }
-
-        void add(E elem) {
-            if (size == capacity) {
-                throw new IllegalStateException(
-                        format("No room for '%s': capacity=%s", elem, capacity));
-            }
-            elements[head] = elem;
-            head = (head + 1) % capacity;
-            size++;
-        }
-
-        @SuppressWarnings("unchecked")
-        E remove() {
-            if (size == 0) {
-                throw new NoSuchElementException("Empty");
-            }
-            E elem = (E) elements[tail];
-            elements[tail] = null;
-            tail = (tail + 1) % capacity;
-            size--;
-            return elem;
-        }
-
-        @SuppressWarnings("unchecked")
-        E get(int index) {
-            if (index < 0 || index >= size) {
-                throw new IndexOutOfBoundsException(
-                        format("0 <= index <= capacity: index=%s, capacity=%s",
-                                index, capacity));
-            }
-            int idx = (tail + (size - index - 1)) % capacity;
-            return (E) elements[idx];
-        }
-
-        public void resize(int newCapacity) {
-            if (newCapacity < size) {
-                throw new IllegalStateException(
-                        format("newCapacity >= size: newCapacity=%s, size=%s",
-                                newCapacity, size));
-            }
-
-            Object[] newElements = new Object[newCapacity];
-
-            if (tail < head || size == 0) {
-                System.arraycopy(elements, tail, newElements, 0, size);
-            } else {
-                System.arraycopy(elements, tail, newElements, 0, elements.length - tail);
-                System.arraycopy(elements, 0, newElements, elements.length - tail, head);
-            }
-
-            elements = newElements;
-            tail = 0;
-            head = size;
-            this.capacity = newCapacity;
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/Huffman.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,681 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import static java.lang.String.format;
-
-/**
- * Huffman coding table.
- *
- * <p> Instances of this class are safe for use by multiple threads.
- *
- * @since 9
- */
-public final class Huffman {
-
-    // TODO: check if reset is done in both reader and writer
-
-    static final class Reader {
-
-        private Node curr; // position in the trie
-        private int len;   // length of the path from the root to 'curr'
-        private int p;     // byte probe
-
-        {
-            reset();
-        }
-
-        public void read(ByteBuffer source,
-                         Appendable destination,
-                         boolean isLast) throws IOException {
-            read(source, destination, true, isLast);
-        }
-
-        // Takes 'isLast' rather than returns whether the reading is done or
-        // not, for more informative exceptions.
-        void read(ByteBuffer source,
-                  Appendable destination,
-                  boolean reportEOS, /* reportEOS is exposed for tests */
-                  boolean isLast) throws IOException {
-            Node c = curr;
-            int l = len;
-            /*
-               Since ByteBuffer is itself stateful, its position is
-               remembered here NOT as a part of Reader's state,
-               but to set it back in the case of a failure
-             */
-            int pos = source.position();
-
-            while (source.hasRemaining()) {
-                int d = source.get();
-                for (; p != 0; p >>= 1) {
-                    c = c.getChild(p & d);
-                    l++;
-                    if (c.isLeaf()) {
-                        if (reportEOS && c.isEOSPath) {
-                            throw new IOException("Encountered EOS");
-                        }
-                        char ch;
-                        try {
-                            ch = c.getChar();
-                        } catch (IllegalStateException e) {
-                            source.position(pos); // do we need this?
-                            throw new IOException(e);
-                        }
-                        try {
-                            destination.append(ch);
-                        } catch (IOException e) {
-                            source.position(pos); // do we need this?
-                            throw e;
-                        }
-                        c = INSTANCE.root;
-                        l = 0;
-                    }
-                    curr = c;
-                    len = l;
-                }
-                resetProbe();
-                pos++;
-            }
-            if (!isLast) {
-                return; // it's too early to jump to any conclusions, let's wait
-            }
-            if (c.isLeaf()) {
-                return; // it's perfectly ok, no extra padding bits
-            }
-            if (c.isEOSPath && len <= 7) {
-                return; // it's ok, some extra padding bits
-            }
-            if (c.isEOSPath) {
-                throw new IOException(
-                        "Padding is too long (len=" + len + ") " +
-                                "or unexpected end of data");
-            }
-            throw new IOException(
-                    "Not a EOS prefix padding or unexpected end of data");
-        }
-
-        public void reset() {
-            curr = INSTANCE.root;
-            len = 0;
-            resetProbe();
-        }
-
-        private void resetProbe() {
-            p = 0x80;
-        }
-    }
-
-    static final class Writer {
-
-        private int pos;       // position in 'source'
-        private int avail = 8; // number of least significant bits available in 'curr'
-        private int curr;      // next byte to put to the destination
-        private int rem;       // number of least significant bits in 'code' yet to be processed
-        private int code;      // current code being written
-
-        private CharSequence source;
-        private int end;
-
-        public Writer from(CharSequence input, int start, int end) {
-            if (start < 0 || end < 0 || end > input.length() || start > end) {
-                throw new IndexOutOfBoundsException(
-                        String.format("input.length()=%s, start=%s, end=%s",
-                                input.length(), start, end));
-            }
-            pos = start;
-            this.end = end;
-            this.source = input;
-            return this;
-        }
-
-        public boolean write(ByteBuffer destination) {
-            for (; pos < end; pos++) {
-                if (rem == 0) {
-                    Code desc = INSTANCE.codeOf(source.charAt(pos));
-                    rem = desc.length;
-                    code = desc.code;
-                }
-                while (rem > 0) {
-                    if (rem < avail) {
-                        curr |= (code << (avail - rem));
-                        avail -= rem;
-                        rem = 0;
-                    } else {
-                        int c = (curr | (code >>> (rem - avail)));
-                        if (destination.hasRemaining()) {
-                            destination.put((byte) c);
-                        } else {
-                            return false;
-                        }
-                        curr = c;
-                        code <<= (32 - rem + avail);  // throw written bits off the cliff (is this Sparta?)
-                        code >>>= (32 - rem + avail); // return to the position
-                        rem -= avail;
-                        curr = 0;
-                        avail = 8;
-                    }
-                }
-            }
-
-            if (avail < 8) { // have to pad
-                if (destination.hasRemaining()) {
-                    destination.put((byte) (curr | (INSTANCE.EOS.code >>> (INSTANCE.EOS.length - avail))));
-                    avail = 8;
-                } else {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        public Writer reset() {
-            source = null;
-            end = -1;
-            pos = -1;
-            avail = 8;
-            curr = 0;
-            code = 0;
-            return this;
-        }
-    }
-
-    /**
-     * Shared instance.
-     */
-    public static final Huffman INSTANCE = new Huffman();
-
-    private final Code EOS = new Code(0x3fffffff, 30);
-    private final Code[] codes = new Code[257];
-    private final Node root = new Node() {
-        @Override
-        public String toString() { return "root"; }
-    };
-
-    // TODO: consider builder and immutable trie
-    private Huffman() {
-        // @formatter:off
-        addChar(0,   0x1ff8,     13);
-        addChar(1,   0x7fffd8,   23);
-        addChar(2,   0xfffffe2,  28);
-        addChar(3,   0xfffffe3,  28);
-        addChar(4,   0xfffffe4,  28);
-        addChar(5,   0xfffffe5,  28);
-        addChar(6,   0xfffffe6,  28);
-        addChar(7,   0xfffffe7,  28);
-        addChar(8,   0xfffffe8,  28);
-        addChar(9,   0xffffea,   24);
-        addChar(10,  0x3ffffffc, 30);
-        addChar(11,  0xfffffe9,  28);
-        addChar(12,  0xfffffea,  28);
-        addChar(13,  0x3ffffffd, 30);
-        addChar(14,  0xfffffeb,  28);
-        addChar(15,  0xfffffec,  28);
-        addChar(16,  0xfffffed,  28);
-        addChar(17,  0xfffffee,  28);
-        addChar(18,  0xfffffef,  28);
-        addChar(19,  0xffffff0,  28);
-        addChar(20,  0xffffff1,  28);
-        addChar(21,  0xffffff2,  28);
-        addChar(22,  0x3ffffffe, 30);
-        addChar(23,  0xffffff3,  28);
-        addChar(24,  0xffffff4,  28);
-        addChar(25,  0xffffff5,  28);
-        addChar(26,  0xffffff6,  28);
-        addChar(27,  0xffffff7,  28);
-        addChar(28,  0xffffff8,  28);
-        addChar(29,  0xffffff9,  28);
-        addChar(30,  0xffffffa,  28);
-        addChar(31,  0xffffffb,  28);
-        addChar(32,  0x14,        6);
-        addChar(33,  0x3f8,      10);
-        addChar(34,  0x3f9,      10);
-        addChar(35,  0xffa,      12);
-        addChar(36,  0x1ff9,     13);
-        addChar(37,  0x15,        6);
-        addChar(38,  0xf8,        8);
-        addChar(39,  0x7fa,      11);
-        addChar(40,  0x3fa,      10);
-        addChar(41,  0x3fb,      10);
-        addChar(42,  0xf9,        8);
-        addChar(43,  0x7fb,      11);
-        addChar(44,  0xfa,        8);
-        addChar(45,  0x16,        6);
-        addChar(46,  0x17,        6);
-        addChar(47,  0x18,        6);
-        addChar(48,  0x0,         5);
-        addChar(49,  0x1,         5);
-        addChar(50,  0x2,         5);
-        addChar(51,  0x19,        6);
-        addChar(52,  0x1a,        6);
-        addChar(53,  0x1b,        6);
-        addChar(54,  0x1c,        6);
-        addChar(55,  0x1d,        6);
-        addChar(56,  0x1e,        6);
-        addChar(57,  0x1f,        6);
-        addChar(58,  0x5c,        7);
-        addChar(59,  0xfb,        8);
-        addChar(60,  0x7ffc,     15);
-        addChar(61,  0x20,        6);
-        addChar(62,  0xffb,      12);
-        addChar(63,  0x3fc,      10);
-        addChar(64,  0x1ffa,     13);
-        addChar(65,  0x21,        6);
-        addChar(66,  0x5d,        7);
-        addChar(67,  0x5e,        7);
-        addChar(68,  0x5f,        7);
-        addChar(69,  0x60,        7);
-        addChar(70,  0x61,        7);
-        addChar(71,  0x62,        7);
-        addChar(72,  0x63,        7);
-        addChar(73,  0x64,        7);
-        addChar(74,  0x65,        7);
-        addChar(75,  0x66,        7);
-        addChar(76,  0x67,        7);
-        addChar(77,  0x68,        7);
-        addChar(78,  0x69,        7);
-        addChar(79,  0x6a,        7);
-        addChar(80,  0x6b,        7);
-        addChar(81,  0x6c,        7);
-        addChar(82,  0x6d,        7);
-        addChar(83,  0x6e,        7);
-        addChar(84,  0x6f,        7);
-        addChar(85,  0x70,        7);
-        addChar(86,  0x71,        7);
-        addChar(87,  0x72,        7);
-        addChar(88,  0xfc,        8);
-        addChar(89,  0x73,        7);
-        addChar(90,  0xfd,        8);
-        addChar(91,  0x1ffb,     13);
-        addChar(92,  0x7fff0,    19);
-        addChar(93,  0x1ffc,     13);
-        addChar(94,  0x3ffc,     14);
-        addChar(95,  0x22,        6);
-        addChar(96,  0x7ffd,     15);
-        addChar(97,  0x3,         5);
-        addChar(98,  0x23,        6);
-        addChar(99,  0x4,         5);
-        addChar(100, 0x24,        6);
-        addChar(101, 0x5,         5);
-        addChar(102, 0x25,        6);
-        addChar(103, 0x26,        6);
-        addChar(104, 0x27,        6);
-        addChar(105, 0x6,         5);
-        addChar(106, 0x74,        7);
-        addChar(107, 0x75,        7);
-        addChar(108, 0x28,        6);
-        addChar(109, 0x29,        6);
-        addChar(110, 0x2a,        6);
-        addChar(111, 0x7,         5);
-        addChar(112, 0x2b,        6);
-        addChar(113, 0x76,        7);
-        addChar(114, 0x2c,        6);
-        addChar(115, 0x8,         5);
-        addChar(116, 0x9,         5);
-        addChar(117, 0x2d,        6);
-        addChar(118, 0x77,        7);
-        addChar(119, 0x78,        7);
-        addChar(120, 0x79,        7);
-        addChar(121, 0x7a,        7);
-        addChar(122, 0x7b,        7);
-        addChar(123, 0x7ffe,     15);
-        addChar(124, 0x7fc,      11);
-        addChar(125, 0x3ffd,     14);
-        addChar(126, 0x1ffd,     13);
-        addChar(127, 0xffffffc,  28);
-        addChar(128, 0xfffe6,    20);
-        addChar(129, 0x3fffd2,   22);
-        addChar(130, 0xfffe7,    20);
-        addChar(131, 0xfffe8,    20);
-        addChar(132, 0x3fffd3,   22);
-        addChar(133, 0x3fffd4,   22);
-        addChar(134, 0x3fffd5,   22);
-        addChar(135, 0x7fffd9,   23);
-        addChar(136, 0x3fffd6,   22);
-        addChar(137, 0x7fffda,   23);
-        addChar(138, 0x7fffdb,   23);
-        addChar(139, 0x7fffdc,   23);
-        addChar(140, 0x7fffdd,   23);
-        addChar(141, 0x7fffde,   23);
-        addChar(142, 0xffffeb,   24);
-        addChar(143, 0x7fffdf,   23);
-        addChar(144, 0xffffec,   24);
-        addChar(145, 0xffffed,   24);
-        addChar(146, 0x3fffd7,   22);
-        addChar(147, 0x7fffe0,   23);
-        addChar(148, 0xffffee,   24);
-        addChar(149, 0x7fffe1,   23);
-        addChar(150, 0x7fffe2,   23);
-        addChar(151, 0x7fffe3,   23);
-        addChar(152, 0x7fffe4,   23);
-        addChar(153, 0x1fffdc,   21);
-        addChar(154, 0x3fffd8,   22);
-        addChar(155, 0x7fffe5,   23);
-        addChar(156, 0x3fffd9,   22);
-        addChar(157, 0x7fffe6,   23);
-        addChar(158, 0x7fffe7,   23);
-        addChar(159, 0xffffef,   24);
-        addChar(160, 0x3fffda,   22);
-        addChar(161, 0x1fffdd,   21);
-        addChar(162, 0xfffe9,    20);
-        addChar(163, 0x3fffdb,   22);
-        addChar(164, 0x3fffdc,   22);
-        addChar(165, 0x7fffe8,   23);
-        addChar(166, 0x7fffe9,   23);
-        addChar(167, 0x1fffde,   21);
-        addChar(168, 0x7fffea,   23);
-        addChar(169, 0x3fffdd,   22);
-        addChar(170, 0x3fffde,   22);
-        addChar(171, 0xfffff0,   24);
-        addChar(172, 0x1fffdf,   21);
-        addChar(173, 0x3fffdf,   22);
-        addChar(174, 0x7fffeb,   23);
-        addChar(175, 0x7fffec,   23);
-        addChar(176, 0x1fffe0,   21);
-        addChar(177, 0x1fffe1,   21);
-        addChar(178, 0x3fffe0,   22);
-        addChar(179, 0x1fffe2,   21);
-        addChar(180, 0x7fffed,   23);
-        addChar(181, 0x3fffe1,   22);
-        addChar(182, 0x7fffee,   23);
-        addChar(183, 0x7fffef,   23);
-        addChar(184, 0xfffea,    20);
-        addChar(185, 0x3fffe2,   22);
-        addChar(186, 0x3fffe3,   22);
-        addChar(187, 0x3fffe4,   22);
-        addChar(188, 0x7ffff0,   23);
-        addChar(189, 0x3fffe5,   22);
-        addChar(190, 0x3fffe6,   22);
-        addChar(191, 0x7ffff1,   23);
-        addChar(192, 0x3ffffe0,  26);
-        addChar(193, 0x3ffffe1,  26);
-        addChar(194, 0xfffeb,    20);
-        addChar(195, 0x7fff1,    19);
-        addChar(196, 0x3fffe7,   22);
-        addChar(197, 0x7ffff2,   23);
-        addChar(198, 0x3fffe8,   22);
-        addChar(199, 0x1ffffec,  25);
-        addChar(200, 0x3ffffe2,  26);
-        addChar(201, 0x3ffffe3,  26);
-        addChar(202, 0x3ffffe4,  26);
-        addChar(203, 0x7ffffde,  27);
-        addChar(204, 0x7ffffdf,  27);
-        addChar(205, 0x3ffffe5,  26);
-        addChar(206, 0xfffff1,   24);
-        addChar(207, 0x1ffffed,  25);
-        addChar(208, 0x7fff2,    19);
-        addChar(209, 0x1fffe3,   21);
-        addChar(210, 0x3ffffe6,  26);
-        addChar(211, 0x7ffffe0,  27);
-        addChar(212, 0x7ffffe1,  27);
-        addChar(213, 0x3ffffe7,  26);
-        addChar(214, 0x7ffffe2,  27);
-        addChar(215, 0xfffff2,   24);
-        addChar(216, 0x1fffe4,   21);
-        addChar(217, 0x1fffe5,   21);
-        addChar(218, 0x3ffffe8,  26);
-        addChar(219, 0x3ffffe9,  26);
-        addChar(220, 0xffffffd,  28);
-        addChar(221, 0x7ffffe3,  27);
-        addChar(222, 0x7ffffe4,  27);
-        addChar(223, 0x7ffffe5,  27);
-        addChar(224, 0xfffec,    20);
-        addChar(225, 0xfffff3,   24);
-        addChar(226, 0xfffed,    20);
-        addChar(227, 0x1fffe6,   21);
-        addChar(228, 0x3fffe9,   22);
-        addChar(229, 0x1fffe7,   21);
-        addChar(230, 0x1fffe8,   21);
-        addChar(231, 0x7ffff3,   23);
-        addChar(232, 0x3fffea,   22);
-        addChar(233, 0x3fffeb,   22);
-        addChar(234, 0x1ffffee,  25);
-        addChar(235, 0x1ffffef,  25);
-        addChar(236, 0xfffff4,   24);
-        addChar(237, 0xfffff5,   24);
-        addChar(238, 0x3ffffea,  26);
-        addChar(239, 0x7ffff4,   23);
-        addChar(240, 0x3ffffeb,  26);
-        addChar(241, 0x7ffffe6,  27);
-        addChar(242, 0x3ffffec,  26);
-        addChar(243, 0x3ffffed,  26);
-        addChar(244, 0x7ffffe7,  27);
-        addChar(245, 0x7ffffe8,  27);
-        addChar(246, 0x7ffffe9,  27);
-        addChar(247, 0x7ffffea,  27);
-        addChar(248, 0x7ffffeb,  27);
-        addChar(249, 0xffffffe,  28);
-        addChar(250, 0x7ffffec,  27);
-        addChar(251, 0x7ffffed,  27);
-        addChar(252, 0x7ffffee,  27);
-        addChar(253, 0x7ffffef,  27);
-        addChar(254, 0x7fffff0,  27);
-        addChar(255, 0x3ffffee,  26);
-        addEOS (256, EOS.code,   EOS.length);
-        // @formatter:on
-    }
-
-
-    /**
-     * Calculates the number of bytes required to represent the given {@code
-     * CharSequence} with the Huffman coding.
-     *
-     * @param value
-     *         characters
-     *
-     * @return number of bytes
-     *
-     * @throws NullPointerException
-     *         if the value is null
-     */
-    public int lengthOf(CharSequence value) {
-        return lengthOf(value, 0, value.length());
-    }
-
-    /**
-     * Calculates the number of bytes required to represent a subsequence of the
-     * given {@code CharSequence} with the Huffman coding.
-     *
-     * @param value
-     *         characters
-     * @param start
-     *         the start index, inclusive
-     * @param end
-     *         the end index, exclusive
-     *
-     * @return number of bytes
-     *
-     * @throws NullPointerException
-     *         if the value is null
-     * @throws IndexOutOfBoundsException
-     *         if any invocation of {@code value.charAt(i)}, where
-     *         {@code start <= i < end} would throw an IndexOutOfBoundsException
-     */
-    public int lengthOf(CharSequence value, int start, int end) {
-        int len = 0;
-        for (int i = start; i < end; i++) {
-            char c = value.charAt(i);
-            len += INSTANCE.codeOf(c).length;
-        }
-        // Integer division with ceiling, assumption:
-        assert (len / 8 + (len % 8 != 0 ? 1 : 0)) == (len + 7) / 8 : len;
-        return (len + 7) / 8;
-    }
-
-    private void addChar(int c, int code, int bitLength) {
-        addLeaf(c, code, bitLength, false);
-        codes[c] = new Code(code, bitLength);
-    }
-
-    private void addEOS(int c, int code, int bitLength) {
-        addLeaf(c, code, bitLength, true);
-        codes[c] = new Code(code, bitLength);
-    }
-
-    private void addLeaf(int c, int code, int bitLength, boolean isEOS) {
-        if (bitLength < 1) {
-            throw new IllegalArgumentException("bitLength < 1");
-        }
-        Node curr = root;
-        for (int p = 1 << bitLength - 1; p != 0 && !curr.isLeaf(); p = p >> 1) {
-            curr.isEOSPath |= isEOS; // If it's already true, it can't become false
-            curr = curr.addChildIfAbsent(p & code);
-        }
-        curr.isEOSPath |= isEOS; // The last one needs to have this property as well
-        if (curr.isLeaf()) {
-            throw new IllegalStateException("Specified code is already taken");
-        }
-        curr.setChar((char) c);
-    }
-
-    private Code codeOf(char c) {
-        if (c > 255) {
-            throw new IllegalArgumentException("char=" + ((int) c));
-        }
-        return codes[c];
-    }
-
-    //
-    // For debugging/testing purposes
-    //
-    Node getRoot() {
-        return root;
-    }
-
-    //
-    // Guarantees:
-    //
-    //  if (isLeaf() == true) => getChar() is a legal call
-    //  if (isLeaf() == false) => getChild(i) is a legal call (though it can
-    //                                                           return null)
-    //
-    static class Node {
-
-        Node left;
-        Node right;
-        boolean isEOSPath;
-
-        boolean charIsSet;
-        char c;
-
-        Node getChild(int selector) {
-            if (isLeaf()) {
-                throw new IllegalStateException("This is a leaf node");
-            }
-            Node result = selector == 0 ? left : right;
-            if (result == null) {
-                throw new IllegalStateException(format(
-                        "Node doesn't have a child (selector=%s)", selector));
-            }
-            return result;
-        }
-
-        boolean isLeaf() {
-            return charIsSet;
-        }
-
-        char getChar() {
-            if (!isLeaf()) {
-                throw new IllegalStateException("This node is not a leaf node");
-            }
-            return c;
-        }
-
-        void setChar(char c) {
-            if (charIsSet) {
-                throw new IllegalStateException(
-                        "This node has been taken already");
-            }
-            if (left != null || right != null) {
-                throw new IllegalStateException("The node cannot be made "
-                        + "a leaf as it's already has a child");
-            }
-            this.c = c;
-            charIsSet = true;
-        }
-
-        Node addChildIfAbsent(int i) {
-            if (charIsSet) {
-                throw new IllegalStateException("The node cannot have a child "
-                        + "as it's already a leaf node");
-            }
-            Node child;
-            if (i == 0) {
-                if ((child = left) == null) {
-                    child = left = new Node();
-                }
-            } else {
-                if ((child = right) == null) {
-                    child = right = new Node();
-                }
-            }
-            return child;
-        }
-
-        @Override
-        public String toString() {
-            if (isLeaf()) {
-                if (isEOSPath) {
-                    return "EOS";
-                } else {
-                    return format("char: (%3s) '%s'", (int) c, c);
-                }
-            }
-            return "/\\";
-        }
-    }
-
-    // TODO: value-based class?
-    // FIXME: can we re-use Node instead of this class?
-    private static final class Code {
-
-        final int code;
-        final int length;
-
-        private Code(int code, int length) {
-            this.code = code;
-            this.length = length;
-        }
-
-        public int getCode() {
-            return code;
-        }
-
-        public int getLength() {
-            return length;
-        }
-
-        @Override
-        public String toString() {
-            long p = 1 << length;
-            return Long.toBinaryString(code + p).substring(1)
-                    + ", length=" + length;
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/ISO_8859_1.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +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 java.net.http.internal.hpack;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-//
-// Custom implementation of ISO/IEC 8859-1:1998
-//
-// The rationale behind this is not to deal with CharsetEncoder/CharsetDecoder,
-// basically because it would require wrapping every single CharSequence into a
-// CharBuffer and then copying it back.
-//
-// But why not to give a CharBuffer instead of Appendable? Because I can choose
-// an Appendable (e.g. StringBuilder) that adjusts its length when needed and
-// therefore not to deal with pre-sized CharBuffers or copying.
-//
-// The encoding is simple and well known: 1 byte <-> 1 char
-//
-final class ISO_8859_1 {
-
-    private ISO_8859_1() { }
-
-    public static final class Reader {
-
-        public void read(ByteBuffer source, Appendable destination)
-                throws IOException {
-            for (int i = 0, len = source.remaining(); i < len; i++) {
-                char c = (char) (source.get() & 0xff);
-                try {
-                    destination.append(c);
-                } catch (IOException e) {
-                    throw new IOException(
-                            "Error appending to the destination", e);
-                }
-            }
-        }
-
-        public Reader reset() {
-            return this;
-        }
-    }
-
-    public static final class Writer {
-
-        private CharSequence source;
-        private int pos;
-        private int end;
-
-        public Writer configure(CharSequence source, int start, int end) {
-            this.source = source;
-            this.pos = start;
-            this.end = end;
-            return this;
-        }
-
-        public boolean write(ByteBuffer destination) {
-            for (; pos < end; pos++) {
-                char c = source.charAt(pos);
-                if (c > '\u00FF') {
-                    throw new IllegalArgumentException(
-                            "Illegal ISO-8859-1 char: " + (int) c);
-                }
-                if (destination.hasRemaining()) {
-                    destination.put((byte) c);
-                } else {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        public Writer reset() {
-            source = null;
-            pos = -1;
-            end = -1;
-            return this;
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/IndexNameValueWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-
-abstract class IndexNameValueWriter implements BinaryRepresentationWriter {
-
-    private final int pattern;
-    private final int prefix;
-    private final IntegerWriter intWriter = new IntegerWriter();
-    private final StringWriter nameWriter = new StringWriter();
-    private final StringWriter valueWriter = new StringWriter();
-
-    protected boolean indexedRepresentation;
-
-    private static final int NEW               = 0;
-    private static final int NAME_PART_WRITTEN = 1;
-    private static final int VALUE_WRITTEN     = 2;
-
-    private int state = NEW;
-
-    protected IndexNameValueWriter(int pattern, int prefix) {
-        this.pattern = pattern;
-        this.prefix = prefix;
-    }
-
-    IndexNameValueWriter index(int index) {
-        indexedRepresentation = true;
-        intWriter.configure(index, prefix, pattern);
-        return this;
-    }
-
-    IndexNameValueWriter name(CharSequence name, boolean useHuffman) {
-        indexedRepresentation = false;
-        intWriter.configure(0, prefix, pattern);
-        nameWriter.configure(name, useHuffman);
-        return this;
-    }
-
-    IndexNameValueWriter value(CharSequence value, boolean useHuffman) {
-        valueWriter.configure(value, useHuffman);
-        return this;
-    }
-
-    @Override
-    public boolean write(HeaderTable table, ByteBuffer destination) {
-        if (state < NAME_PART_WRITTEN) {
-            if (indexedRepresentation) {
-                if (!intWriter.write(destination)) {
-                    return false;
-                }
-            } else {
-                if (!intWriter.write(destination) ||
-                        !nameWriter.write(destination)) {
-                    return false;
-                }
-            }
-            state = NAME_PART_WRITTEN;
-        }
-        if (state < VALUE_WRITTEN) {
-            if (!valueWriter.write(destination)) {
-                return false;
-            }
-            state = VALUE_WRITTEN;
-        }
-        return state == VALUE_WRITTEN;
-    }
-
-    @Override
-    public IndexNameValueWriter reset() {
-        intWriter.reset();
-        if (!indexedRepresentation) {
-            nameWriter.reset();
-        }
-        valueWriter.reset();
-        state = NEW;
-        return this;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/IndexedWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-
-final class IndexedWriter implements BinaryRepresentationWriter {
-
-    private final IntegerWriter intWriter = new IntegerWriter();
-
-    IndexedWriter() { }
-
-    IndexedWriter index(int index) {
-        intWriter.configure(index, 7, 0b1000_0000);
-        return this;
-    }
-
-    @Override
-    public boolean write(HeaderTable table, ByteBuffer destination) {
-        return intWriter.write(destination);
-    }
-
-    @Override
-    public BinaryRepresentationWriter reset() {
-        intWriter.reset();
-        return this;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/IntegerReader.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-import static java.lang.String.format;
-
-final class IntegerReader {
-
-    private static final int NEW             = 0;
-    private static final int CONFIGURED      = 1;
-    private static final int FIRST_BYTE_READ = 2;
-    private static final int DONE            = 4;
-
-    private int state = NEW;
-
-    private int N;
-    private int maxValue;
-    private int value;
-    private long r;
-    private long b = 1;
-
-    public IntegerReader configure(int N) {
-        return configure(N, Integer.MAX_VALUE);
-    }
-
-    //
-    // Why is it important to configure 'maxValue' here. After all we can wait
-    // for the integer to be fully read and then check it. Can't we?
-    //
-    // Two reasons.
-    //
-    // 1. Value wraps around long won't be unnoticed.
-    // 2. It can spit out an exception as soon as it becomes clear there's
-    // an overflow. Therefore, no need to wait for the value to be fully read.
-    //
-    public IntegerReader configure(int N, int maxValue) {
-        if (state != NEW) {
-            throw new IllegalStateException("Already configured");
-        }
-        checkPrefix(N);
-        if (maxValue < 0) {
-            throw new IllegalArgumentException(
-                    "maxValue >= 0: maxValue=" + maxValue);
-        }
-        this.maxValue = maxValue;
-        this.N = N;
-        state = CONFIGURED;
-        return this;
-    }
-
-    public boolean read(ByteBuffer input) throws IOException {
-        if (state == NEW) {
-            throw new IllegalStateException("Configure first");
-        }
-        if (state == DONE) {
-            return true;
-        }
-        if (!input.hasRemaining()) {
-            return false;
-        }
-        if (state == CONFIGURED) {
-            int max = (2 << (N - 1)) - 1;
-            int n = input.get() & max;
-            if (n != max) {
-                value = n;
-                state = DONE;
-                return true;
-            } else {
-                r = max;
-            }
-            state = FIRST_BYTE_READ;
-        }
-        if (state == FIRST_BYTE_READ) {
-            // variable-length quantity (VLQ)
-            byte i;
-            do {
-                if (!input.hasRemaining()) {
-                    return false;
-                }
-                i = input.get();
-                long increment = b * (i & 127);
-                if (r + increment > maxValue) {
-                    throw new IOException(format(
-                            "Integer overflow: maxValue=%,d, value=%,d",
-                            maxValue, r + increment));
-                }
-                r += increment;
-                b *= 128;
-            } while ((128 & i) == 128);
-
-            value = (int) r;
-            state = DONE;
-            return true;
-        }
-        throw new InternalError(Arrays.toString(
-                new Object[]{state, N, maxValue, value, r, b}));
-    }
-
-    public int get() throws IllegalStateException {
-        if (state != DONE) {
-            throw new IllegalStateException("Has not been fully read yet");
-        }
-        return value;
-    }
-
-    private static void checkPrefix(int N) {
-        if (N < 1 || N > 8) {
-            throw new IllegalArgumentException("1 <= N <= 8: N= " + N);
-        }
-    }
-
-    public IntegerReader reset() {
-        b = 1;
-        state = NEW;
-        return this;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/IntegerWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-final class IntegerWriter {
-
-    private static final int NEW                = 0;
-    private static final int CONFIGURED         = 1;
-    private static final int FIRST_BYTE_WRITTEN = 2;
-    private static final int DONE               = 4;
-
-    private int state = NEW;
-
-    private int payload;
-    private int N;
-    private int value;
-
-    //
-    //      0   1   2   3   4   5   6   7
-    //    +---+---+---+---+---+---+---+---+
-    //    |   |   |   |   |   |   |   |   |
-    //    +---+---+---+-------------------+
-    //    |<--------->|<----------------->|
-    //       payload           N=5
-    //
-    // payload is the contents of the left-hand side part of the octet;
-    //         it is truncated to fit into 8-N bits, where 1 <= N <= 8;
-    //
-    public IntegerWriter configure(int value, int N, int payload) {
-        if (state != NEW) {
-            throw new IllegalStateException("Already configured");
-        }
-        if (value < 0) {
-            throw new IllegalArgumentException("value >= 0: value=" + value);
-        }
-        checkPrefix(N);
-        this.value = value;
-        this.N = N;
-        this.payload = payload & 0xFF & (0xFFFFFFFF << N);
-        state = CONFIGURED;
-        return this;
-    }
-
-    public boolean write(ByteBuffer output) {
-        if (state == NEW) {
-            throw new IllegalStateException("Configure first");
-        }
-        if (state == DONE) {
-            return true;
-        }
-
-        if (!output.hasRemaining()) {
-            return false;
-        }
-        if (state == CONFIGURED) {
-            int max = (2 << (N - 1)) - 1;
-            if (value < max) {
-                output.put((byte) (payload | value));
-                state = DONE;
-                return true;
-            }
-            output.put((byte) (payload | max));
-            value -= max;
-            state = FIRST_BYTE_WRITTEN;
-        }
-        if (state == FIRST_BYTE_WRITTEN) {
-            while (value >= 128 && output.hasRemaining()) {
-                output.put((byte) (value % 128 + 128));
-                value /= 128;
-            }
-            if (!output.hasRemaining()) {
-                return false;
-            }
-            output.put((byte) value);
-            state = DONE;
-            return true;
-        }
-        throw new InternalError(Arrays.toString(
-                new Object[]{state, payload, N, value}));
-    }
-
-    private static void checkPrefix(int N) {
-        if (N < 1 || N > 8) {
-            throw new IllegalArgumentException("1 <= N <= 8: N= " + N);
-        }
-    }
-
-    public IntegerWriter reset() {
-        state = NEW;
-        return this;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/LiteralNeverIndexedWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +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 java.net.http.internal.hpack;
-
-final class LiteralNeverIndexedWriter extends IndexNameValueWriter {
-
-    LiteralNeverIndexedWriter() {
-        super(0b0001_0000, 4);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/LiteralWithIndexingWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-
-final class LiteralWithIndexingWriter extends IndexNameValueWriter {
-
-    private boolean tableUpdated;
-
-    private CharSequence name;
-    private CharSequence value;
-    private int index;
-
-    LiteralWithIndexingWriter() {
-        super(0b0100_0000, 6);
-    }
-
-    @Override
-    LiteralWithIndexingWriter index(int index) {
-        super.index(index);
-        this.index = index;
-        return this;
-    }
-
-    @Override
-    LiteralWithIndexingWriter name(CharSequence name, boolean useHuffman) {
-        super.name(name, useHuffman);
-        this.name = name;
-        return this;
-    }
-
-    @Override
-    LiteralWithIndexingWriter value(CharSequence value, boolean useHuffman) {
-        super.value(value, useHuffman);
-        this.value = value;
-        return this;
-    }
-
-    @Override
-    public boolean write(HeaderTable table, ByteBuffer destination) {
-        if (!tableUpdated) {
-            CharSequence n;
-            if (indexedRepresentation) {
-                n = table.get(index).name;
-            } else {
-                n = name;
-            }
-            table.put(n, value);
-            tableUpdated = true;
-        }
-        return super.write(table, destination);
-    }
-
-    @Override
-    public IndexNameValueWriter reset() {
-        tableUpdated = false;
-        name = null;
-        value = null;
-        index = -1;
-        return super.reset();
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/LiteralWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +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 java.net.http.internal.hpack;
-
-final class LiteralWriter extends IndexNameValueWriter {
-
-    LiteralWriter() {
-        super(0b0000_0000, 4);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/SizeUpdateWriter.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-
-final class SizeUpdateWriter implements BinaryRepresentationWriter {
-
-    private final IntegerWriter intWriter = new IntegerWriter();
-    private int maxSize;
-    private boolean tableUpdated;
-
-    SizeUpdateWriter() { }
-
-    SizeUpdateWriter maxHeaderTableSize(int size) {
-        intWriter.configure(size, 5, 0b0010_0000);
-        this.maxSize = size;
-        return this;
-    }
-
-    @Override
-    public boolean write(HeaderTable table, ByteBuffer destination) {
-        if (!tableUpdated) {
-            table.setMaxSize(maxSize);
-            tableUpdated = true;
-        }
-        return intWriter.write(destination);
-    }
-
-    @Override
-    public BinaryRepresentationWriter reset() {
-        intWriter.reset();
-        maxSize = -1;
-        tableUpdated = false;
-        return this;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/StringReader.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +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 java.net.http.internal.hpack;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-//
-//          0   1   2   3   4   5   6   7
-//        +---+---+---+---+---+---+---+---+
-//        | H |    String Length (7+)     |
-//        +---+---------------------------+
-//        |  String Data (Length octets)  |
-//        +-------------------------------+
-//
-final class StringReader {
-
-    private static final int NEW             = 0;
-    private static final int FIRST_BYTE_READ = 1;
-    private static final int LENGTH_READ     = 2;
-    private static final int DONE            = 4;
-
-    private final IntegerReader intReader = new IntegerReader();
-    private final Huffman.Reader huffmanReader = new Huffman.Reader();
-    private final ISO_8859_1.Reader plainReader = new ISO_8859_1.Reader();
-
-    private int state = NEW;
-
-    private boolean huffman;
-    private int remainingLength;
-
-    boolean read(ByteBuffer input, Appendable output) throws IOException {
-        if (state == DONE) {
-            return true;
-        }
-        if (!input.hasRemaining()) {
-            return false;
-        }
-        if (state == NEW) {
-            int p = input.position();
-            huffman = (input.get(p) & 0b10000000) != 0;
-            state = FIRST_BYTE_READ;
-            intReader.configure(7);
-        }
-        if (state == FIRST_BYTE_READ) {
-            boolean lengthRead = intReader.read(input);
-            if (!lengthRead) {
-                return false;
-            }
-            remainingLength = intReader.get();
-            state = LENGTH_READ;
-        }
-        if (state == LENGTH_READ) {
-            boolean isLast = input.remaining() >= remainingLength;
-            int oldLimit = input.limit();
-            if (isLast) {
-                input.limit(input.position() + remainingLength);
-            }
-            remainingLength -= Math.min(input.remaining(), remainingLength);
-            if (huffman) {
-                huffmanReader.read(input, output, isLast);
-            } else {
-                plainReader.read(input, output);
-            }
-            if (isLast) {
-                input.limit(oldLimit);
-                state = DONE;
-            }
-            return isLast;
-        }
-        throw new InternalError(Arrays.toString(
-                new Object[]{state, huffman, remainingLength}));
-    }
-
-    boolean isHuffmanEncoded() {
-        if (state < FIRST_BYTE_READ) {
-            throw new IllegalStateException("Has not been fully read yet");
-        }
-        return huffman;
-    }
-
-    void reset() {
-        if (huffman) {
-            huffmanReader.reset();
-        } else {
-            plainReader.reset();
-        }
-        intReader.reset();
-        state = NEW;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/StringWriter.java	Wed Feb 07 15:46:30 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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-//
-//          0   1   2   3   4   5   6   7
-//        +---+---+---+---+---+---+---+---+
-//        | H |    String Length (7+)     |
-//        +---+---------------------------+
-//        |  String Data (Length octets)  |
-//        +-------------------------------+
-//
-// StringWriter does not require a notion of endOfInput (isLast) in 'write'
-// methods due to the nature of string representation in HPACK. Namely, the
-// length of the string is put before string's contents. Therefore the length is
-// always known beforehand.
-//
-// Expected use:
-//
-//     configure write* (reset configure write*)*
-//
-final class StringWriter {
-
-    private static final int NEW            = 0;
-    private static final int CONFIGURED     = 1;
-    private static final int LENGTH_WRITTEN = 2;
-    private static final int DONE           = 4;
-
-    private final IntegerWriter intWriter = new IntegerWriter();
-    private final Huffman.Writer huffmanWriter = new Huffman.Writer();
-    private final ISO_8859_1.Writer plainWriter = new ISO_8859_1.Writer();
-
-    private int state = NEW;
-    private boolean huffman;
-
-    StringWriter configure(CharSequence input, boolean huffman) {
-        return configure(input, 0, input.length(), huffman);
-    }
-
-    StringWriter configure(CharSequence input,
-                           int start,
-                           int end,
-                           boolean huffman) {
-        if (start < 0 || end < 0 || end > input.length() || start > end) {
-            throw new IndexOutOfBoundsException(
-                    String.format("input.length()=%s, start=%s, end=%s",
-                            input.length(), start, end));
-        }
-        if (!huffman) {
-            plainWriter.configure(input, start, end);
-            intWriter.configure(end - start, 7, 0b0000_0000);
-        } else {
-            huffmanWriter.from(input, start, end);
-            intWriter.configure(Huffman.INSTANCE.lengthOf(input, start, end),
-                    7, 0b1000_0000);
-        }
-
-        this.huffman = huffman;
-        state = CONFIGURED;
-        return this;
-    }
-
-    boolean write(ByteBuffer output) {
-        if (state == DONE) {
-            return true;
-        }
-        if (state == NEW) {
-            throw new IllegalStateException("Configure first");
-        }
-        if (!output.hasRemaining()) {
-            return false;
-        }
-        if (state == CONFIGURED) {
-            if (intWriter.write(output)) {
-                state = LENGTH_WRITTEN;
-            } else {
-                return false;
-            }
-        }
-        if (state == LENGTH_WRITTEN) {
-            boolean written = huffman
-                    ? huffmanWriter.write(output)
-                    : plainWriter.write(output);
-            if (written) {
-                state = DONE;
-                return true;
-            } else {
-                return false;
-            }
-        }
-        throw new InternalError(Arrays.toString(new Object[]{state, huffman}));
-    }
-
-    void reset() {
-        intWriter.reset();
-        if (huffman) {
-            huffmanWriter.reset();
-        } else {
-            plainWriter.reset();
-        }
-        state = NEW;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/hpack/package-info.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +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.
- */
-/**
- * HPACK (Header Compression for HTTP/2) implementation conforming to
- * <a href="https://tools.ietf.org/html/rfc7541">RFC&nbsp;7541</a>.
- *
- * <p> Headers can be decoded and encoded by {@link java.net.http.internal.hpack.Decoder}
- * and {@link java.net.http.internal.hpack.Encoder} respectively.
- *
- * <p> Instances of these classes are not safe for use by multiple threads.
- */
-package java.net.http.internal.hpack;
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/BuilderImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +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 java.net.http.internal.websocket;
-
-import java.net.http.HttpClient;
-import java.net.http.WebSocket;
-import java.net.http.WebSocket.Builder;
-import java.net.http.WebSocket.Listener;
-import java.net.http.internal.common.Pair;
-
-import java.net.ProxySelector;
-import java.net.URI;
-import java.time.Duration;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-
-import static java.util.Objects.requireNonNull;
-import static java.net.http.internal.common.Pair.pair;
-
-public final class BuilderImpl implements Builder {
-
-    private final HttpClient client;
-    private URI uri;
-    private Listener listener;
-    private final Optional<ProxySelector> proxySelector;
-    private final Collection<Pair<String, String>> headers;
-    private final Collection<String> subprotocols;
-    private Duration timeout;
-
-    public BuilderImpl(HttpClient client, ProxySelector proxySelector)
-    {
-        this(client, null, null, Optional.ofNullable(proxySelector),
-             new LinkedList<>(), new LinkedList<>(), null);
-    }
-
-    private BuilderImpl(HttpClient client,
-                        URI uri,
-                        Listener listener,
-                        Optional<ProxySelector> proxySelector,
-                        Collection<Pair<String, String>> headers,
-                        Collection<String> subprotocols,
-                        Duration timeout) {
-        this.client = client;
-        this.uri = uri;
-        this.listener = listener;
-        this.proxySelector = proxySelector;
-        // If a proxy selector was supplied by the user, it should be present
-        // on the client and should be the same that what we got as an argument
-        assert !client.proxy().isPresent()
-                || client.proxy().equals(proxySelector);
-        this.headers = headers;
-        this.subprotocols = subprotocols;
-        this.timeout = timeout;
-    }
-
-    @Override
-    public Builder header(String name, String value) {
-        requireNonNull(name, "name");
-        requireNonNull(value, "value");
-        headers.add(pair(name, value));
-        return this;
-    }
-
-    @Override
-    public Builder subprotocols(String mostPreferred, String... lesserPreferred)
-    {
-        requireNonNull(mostPreferred, "mostPreferred");
-        requireNonNull(lesserPreferred, "lesserPreferred");
-        List<String> subprotocols = new LinkedList<>();
-        subprotocols.add(mostPreferred);
-        for (int i = 0; i < lesserPreferred.length; i++) {
-            String p = lesserPreferred[i];
-            requireNonNull(p, "lesserPreferred[" + i + "]");
-            subprotocols.add(p);
-        }
-        this.subprotocols.clear();
-        this.subprotocols.addAll(subprotocols);
-        return this;
-    }
-
-    @Override
-    public Builder connectTimeout(Duration timeout) {
-        this.timeout = requireNonNull(timeout, "timeout");
-        return this;
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> buildAsync(URI uri, Listener listener) {
-        this.uri = requireNonNull(uri, "uri");
-        this.listener = requireNonNull(listener, "listener");
-        // A snapshot of builder inaccessible for further modification
-        // from the outside
-        BuilderImpl copy = immutableCopy();
-        return WebSocketImpl.newInstanceAsync(copy);
-    }
-
-    HttpClient getClient() { return client; }
-
-    URI getUri() { return uri; }
-
-    Listener getListener() { return listener; }
-
-    Collection<Pair<String, String>> getHeaders() { return headers; }
-
-    Collection<String> getSubprotocols() { return subprotocols; }
-
-    Duration getConnectTimeout() { return timeout; }
-
-    Optional<ProxySelector> getProxySelector() { return proxySelector; }
-
-    private BuilderImpl immutableCopy() {
-        @SuppressWarnings({"unchecked", "rawtypes"})
-        BuilderImpl copy = new BuilderImpl(
-                client,
-                uri,
-                listener,
-                proxySelector,
-                List.of(this.headers.toArray(new Pair[0])),
-                List.of(this.subprotocols.toArray(new String[0])),
-                timeout);
-        return copy;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/CheckFailedException.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * 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 java.net.http.internal.websocket;
-
-/*
- * Used as a context-neutral exception which can be wrapped into (for example)
- * a `ProtocolException` or an `IllegalArgumentException` depending on who's
- * doing the check.
- */
-final class CheckFailedException extends RuntimeException {
-
-    private static final long serialVersionUID = 1L;
-
-    CheckFailedException(String message) {
-        super(message);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/FailWebSocketException.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +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 java.net.http.internal.websocket;
-
-import static java.net.http.internal.websocket.StatusCodes.PROTOCOL_ERROR;
-
-/*
- * Used as a marker for protocol issues in the incoming data, so that the
- * implementation could "Fail the WebSocket Connection" with a status code in
- * the Close message that fits the situation the most.
- *
- *     https://tools.ietf.org/html/rfc6455#section-7.1.7
- */
-final class FailWebSocketException extends RuntimeException {
-
-    private static final long serialVersionUID = 1L;
-    private final int statusCode;
-
-    FailWebSocketException(String detail) {
-        this(detail, PROTOCOL_ERROR);
-    }
-
-    FailWebSocketException(String detail, int statusCode) {
-        super(detail);
-        this.statusCode = statusCode;
-    }
-
-    int getStatusCode() {
-        return statusCode;
-    }
-
-    @Override
-    public FailWebSocketException initCause(Throwable cause) {
-        return (FailWebSocketException) super.initCause(cause);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/Frame.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,499 +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 java.net.http.internal.websocket;
-
-import jdk.internal.vm.annotation.Stable;
-
-import java.nio.ByteBuffer;
-
-import static java.net.http.internal.common.Utils.dump;
-import static java.net.http.internal.websocket.Frame.Opcode.ofCode;
-
-/*
- * A collection of utilities for reading, writing, and masking frames.
- */
-final class Frame {
-
-    private Frame() { }
-
-    static final int MAX_HEADER_SIZE_BYTES = 2 + 8 + 4;
-
-    enum Opcode {
-
-        CONTINUATION   (0x0),
-        TEXT           (0x1),
-        BINARY         (0x2),
-        NON_CONTROL_0x3(0x3),
-        NON_CONTROL_0x4(0x4),
-        NON_CONTROL_0x5(0x5),
-        NON_CONTROL_0x6(0x6),
-        NON_CONTROL_0x7(0x7),
-        CLOSE          (0x8),
-        PING           (0x9),
-        PONG           (0xA),
-        CONTROL_0xB    (0xB),
-        CONTROL_0xC    (0xC),
-        CONTROL_0xD    (0xD),
-        CONTROL_0xE    (0xE),
-        CONTROL_0xF    (0xF);
-
-        @Stable
-        private static final Opcode[] opcodes;
-
-        static {
-            Opcode[] values = values();
-            opcodes = new Opcode[values.length];
-            for (Opcode c : values) {
-                opcodes[c.code] = c;
-            }
-        }
-
-        private final byte code;
-
-        Opcode(int code) {
-            this.code = (byte) code;
-        }
-
-        boolean isControl() {
-            return (code & 0x8) != 0;
-        }
-
-        static Opcode ofCode(int code) {
-            return opcodes[code & 0xF];
-        }
-    }
-
-    /*
-     * A utility for masking frame payload data.
-     */
-    static final class Masker {
-
-        // Exploiting ByteBuffer's ability to read/write multi-byte integers
-        private final ByteBuffer acc = ByteBuffer.allocate(8);
-        private final int[] maskBytes = new int[4];
-        private int offset;
-        private long maskLong;
-
-        /*
-         * Reads all remaining bytes from the given input buffer, masks them
-         * with the supplied mask and writes the resulting bytes to the given
-         * output buffer.
-         *
-         * The source and the destination buffers may be the same instance.
-         */
-        static void transferMasking(ByteBuffer src, ByteBuffer dst, int mask) {
-            if (src.remaining() > dst.remaining()) {
-                throw new IllegalArgumentException(dump(src, dst));
-            }
-            new Masker().mask(mask).transferMasking(src, dst);
-        }
-
-        /*
-         * Clears this instance's state and sets the mask.
-         *
-         * The behaviour is as if the mask was set on a newly created instance.
-         */
-        Masker mask(int value) {
-            acc.clear().putInt(value).putInt(value).flip();
-            for (int i = 0; i < maskBytes.length; i++) {
-                maskBytes[i] = acc.get(i);
-            }
-            offset = 0;
-            maskLong = acc.getLong(0);
-            return this;
-        }
-
-        /*
-         * Reads as many remaining bytes as possible from the given input
-         * buffer, masks them with the previously set mask and writes the
-         * resulting bytes to the given output buffer.
-         *
-         * The source and the destination buffers may be the same instance. If
-         * the mask hasn't been previously set it is assumed to be 0.
-         */
-        Masker transferMasking(ByteBuffer src, ByteBuffer dst) {
-            begin(src, dst);
-            loop(src, dst);
-            end(src, dst);
-            return this;
-        }
-
-        /*
-         * Applies up to 3 remaining from the previous pass bytes of the mask.
-         */
-        private void begin(ByteBuffer src, ByteBuffer dst) {
-            if (offset == 0) { // No partially applied mask from the previous invocation
-                return;
-            }
-            int i = src.position(), j = dst.position();
-            final int srcLim = src.limit(), dstLim = dst.limit();
-            for (; offset < 4 && i < srcLim && j < dstLim; i++, j++, offset++)
-            {
-                dst.put(j, (byte) (src.get(i) ^ maskBytes[offset]));
-            }
-            offset &= 3; // Will become 0 if the mask has been fully applied
-            src.position(i);
-            dst.position(j);
-        }
-
-        /*
-         * Gallops one long (mask + mask) at a time.
-         */
-        private void loop(ByteBuffer src, ByteBuffer dst) {
-            int i = src.position();
-            int j = dst.position();
-            final int srcLongLim = src.limit() - 7, dstLongLim = dst.limit() - 7;
-            for (; i < srcLongLim && j < dstLongLim; i += 8, j += 8) {
-                dst.putLong(j, src.getLong(i) ^ maskLong);
-            }
-            if (i > src.limit()) {
-                src.position(i - 8);
-            } else {
-                src.position(i);
-            }
-            if (j > dst.limit()) {
-                dst.position(j - 8);
-            } else {
-                dst.position(j);
-            }
-        }
-
-        /*
-         * Applies up to 7 remaining from the "galloping" phase bytes of the
-         * mask.
-         */
-        private void end(ByteBuffer src, ByteBuffer dst) {
-            assert Math.min(src.remaining(), dst.remaining()) < 8;
-            final int srcLim = src.limit(), dstLim = dst.limit();
-            int i = src.position(), j = dst.position();
-            for (; i < srcLim && j < dstLim;
-                 i++, j++, offset = (offset + 1) & 3) // offset cycles through 0..3
-            {
-                dst.put(j, (byte) (src.get(i) ^ maskBytes[offset]));
-            }
-            src.position(i);
-            dst.position(j);
-        }
-    }
-
-    /*
-     * A builder-style writer of frame headers.
-     *
-     * The writer does not enforce any protocol-level rules, it simply writes a
-     * header structure to the given buffer. The order of calls to intermediate
-     * methods is NOT significant.
-     */
-    static final class HeaderWriter {
-
-        private char firstChar;
-        private long payloadLen;
-        private int maskingKey;
-        private boolean mask;
-
-        HeaderWriter fin(boolean value) {
-            if (value) {
-                firstChar |=  0b10000000_00000000;
-            } else {
-                firstChar &= ~0b10000000_00000000;
-            }
-            return this;
-        }
-
-        HeaderWriter rsv1(boolean value) {
-            if (value) {
-                firstChar |=  0b01000000_00000000;
-            } else {
-                firstChar &= ~0b01000000_00000000;
-            }
-            return this;
-        }
-
-        HeaderWriter rsv2(boolean value) {
-            if (value) {
-                firstChar |=  0b00100000_00000000;
-            } else {
-                firstChar &= ~0b00100000_00000000;
-            }
-            return this;
-        }
-
-        HeaderWriter rsv3(boolean value) {
-            if (value) {
-                firstChar |=  0b00010000_00000000;
-            } else {
-                firstChar &= ~0b00010000_00000000;
-            }
-            return this;
-        }
-
-        HeaderWriter opcode(Opcode value) {
-            firstChar = (char) ((firstChar & 0xF0FF) | (value.code << 8));
-            return this;
-        }
-
-        HeaderWriter payloadLen(long value) {
-            if (value < 0) {
-                throw new IllegalArgumentException("Negative: " + value);
-            }
-            payloadLen = value;
-            firstChar &= 0b11111111_10000000; // Clear previous payload length leftovers
-            if (payloadLen < 126) {
-                firstChar |= payloadLen;
-            } else if (payloadLen < 65536) {
-                firstChar |= 126;
-            } else {
-                firstChar |= 127;
-            }
-            return this;
-        }
-
-        HeaderWriter mask(int value) {
-            firstChar |= 0b00000000_10000000;
-            maskingKey = value;
-            mask = true;
-            return this;
-        }
-
-        HeaderWriter noMask() {
-            firstChar &= ~0b00000000_10000000;
-            mask = false;
-            return this;
-        }
-
-        /*
-         * Writes the header to the given buffer.
-         *
-         * The buffer must have at least MAX_HEADER_SIZE_BYTES remaining. The
-         * buffer's position is incremented by the number of bytes written.
-         */
-        void write(ByteBuffer buffer) {
-            buffer.putChar(firstChar);
-            if (payloadLen >= 126) {
-                if (payloadLen < 65536) {
-                    buffer.putChar((char) payloadLen);
-                } else {
-                    buffer.putLong(payloadLen);
-                }
-            }
-            if (mask) {
-                buffer.putInt(maskingKey);
-            }
-        }
-    }
-
-    /*
-     * A consumer of frame parts.
-     *
-     * Frame.Reader invokes the consumer's methods in the following order:
-     *
-     *     fin rsv1 rsv2 rsv3 opcode mask payloadLength maskingKey? payloadData+ endFrame
-     */
-    interface Consumer {
-
-        void fin(boolean value);
-
-        void rsv1(boolean value);
-
-        void rsv2(boolean value);
-
-        void rsv3(boolean value);
-
-        void opcode(Opcode value);
-
-        void mask(boolean value);
-
-        void payloadLen(long value);
-
-        void maskingKey(int value);
-
-        /*
-         * Called by the Frame.Reader when a part of the (or a complete) payload
-         * is ready to be consumed.
-         *
-         * The sum of numbers of bytes consumed in each invocation of this
-         * method corresponding to the given frame WILL be equal to
-         * 'payloadLen', reported to `void payloadLen(long value)` before that.
-         *
-         * In particular, if `payloadLen` is 0, then there WILL be a single
-         * invocation to this method.
-         *
-         * No unmasking is done.
-         */
-        void payloadData(ByteBuffer data);
-
-        void endFrame();
-    }
-
-    /*
-     * A Reader of frames.
-     *
-     * No protocol-level rules are checked.
-     */
-    static final class Reader {
-
-        private static final int AWAITING_FIRST_BYTE  =  1;
-        private static final int AWAITING_SECOND_BYTE =  2;
-        private static final int READING_16_LENGTH    =  4;
-        private static final int READING_64_LENGTH    =  8;
-        private static final int READING_MASK         = 16;
-        private static final int READING_PAYLOAD      = 32;
-
-        // Exploiting ByteBuffer's ability to read multi-byte integers
-        private final ByteBuffer accumulator = ByteBuffer.allocate(8);
-        private int state = AWAITING_FIRST_BYTE;
-        private boolean mask;
-        private long remainingPayloadLength;
-
-        /*
-         * Reads at most one frame from the given buffer invoking the consumer's
-         * methods corresponding to the frame parts found.
-         *
-         * As much of the frame's payload, if any, is read. The buffer's
-         * position is updated to reflect the number of bytes read.
-         *
-         * Throws FailWebSocketException if detects the frame is malformed.
-         */
-        void readFrame(ByteBuffer input, Consumer consumer) {
-            loop:
-            while (true) {
-                byte b;
-                switch (state) {
-                    case AWAITING_FIRST_BYTE:
-                        if (!input.hasRemaining()) {
-                            break loop;
-                        }
-                        b = input.get();
-                        consumer.fin( (b & 0b10000000) != 0);
-                        consumer.rsv1((b & 0b01000000) != 0);
-                        consumer.rsv2((b & 0b00100000) != 0);
-                        consumer.rsv3((b & 0b00010000) != 0);
-                        consumer.opcode(ofCode(b));
-                        state = AWAITING_SECOND_BYTE;
-                        continue loop;
-                    case AWAITING_SECOND_BYTE:
-                        if (!input.hasRemaining()) {
-                            break loop;
-                        }
-                        b = input.get();
-                        consumer.mask(mask = (b & 0b10000000) != 0);
-                        byte p1 = (byte) (b & 0b01111111);
-                        if (p1 < 126) {
-                            assert p1 >= 0 : p1;
-                            consumer.payloadLen(remainingPayloadLength = p1);
-                            state = mask ? READING_MASK : READING_PAYLOAD;
-                        } else if (p1 < 127) {
-                            state = READING_16_LENGTH;
-                        } else {
-                            state = READING_64_LENGTH;
-                        }
-                        continue loop;
-                    case READING_16_LENGTH:
-                        if (!input.hasRemaining()) {
-                            break loop;
-                        }
-                        b = input.get();
-                        if (accumulator.put(b).position() < 2) {
-                            continue loop;
-                        }
-                        remainingPayloadLength = accumulator.flip().getChar();
-                        if (remainingPayloadLength < 126) {
-                            throw notMinimalEncoding(remainingPayloadLength);
-                        }
-                        consumer.payloadLen(remainingPayloadLength);
-                        accumulator.clear();
-                        state = mask ? READING_MASK : READING_PAYLOAD;
-                        continue loop;
-                    case READING_64_LENGTH:
-                        if (!input.hasRemaining()) {
-                            break loop;
-                        }
-                        b = input.get();
-                        if (accumulator.put(b).position() < 8) {
-                            continue loop;
-                        }
-                        remainingPayloadLength = accumulator.flip().getLong();
-                        if (remainingPayloadLength < 0) {
-                            throw negativePayload(remainingPayloadLength);
-                        } else if (remainingPayloadLength < 65536) {
-                            throw notMinimalEncoding(remainingPayloadLength);
-                        }
-                        consumer.payloadLen(remainingPayloadLength);
-                        accumulator.clear();
-                        state = mask ? READING_MASK : READING_PAYLOAD;
-                        continue loop;
-                    case READING_MASK:
-                        if (!input.hasRemaining()) {
-                            break loop;
-                        }
-                        b = input.get();
-                        if (accumulator.put(b).position() != 4) {
-                            continue loop;
-                        }
-                        consumer.maskingKey(accumulator.flip().getInt());
-                        accumulator.clear();
-                        state = READING_PAYLOAD;
-                        continue loop;
-                    case READING_PAYLOAD:
-                        // This state does not require any bytes to be available
-                        // in the input buffer in order to proceed
-                        int deliverable = (int) Math.min(remainingPayloadLength,
-                                                         input.remaining());
-                        int oldLimit = input.limit();
-                        input.limit(input.position() + deliverable);
-                        if (deliverable != 0 || remainingPayloadLength == 0) {
-                            consumer.payloadData(input);
-                        }
-                        int consumed = deliverable - input.remaining();
-                        if (consumed < 0) {
-                            // Consumer cannot consume more than there was available
-                            throw new InternalError();
-                        }
-                        input.limit(oldLimit);
-                        remainingPayloadLength -= consumed;
-                        if (remainingPayloadLength == 0) {
-                            consumer.endFrame();
-                            state = AWAITING_FIRST_BYTE;
-                        }
-                        break loop;
-                    default:
-                        throw new InternalError(String.valueOf(state));
-                }
-            }
-        }
-
-        private static FailWebSocketException negativePayload(long payloadLength)
-        {
-            return new FailWebSocketException(
-                    "Negative payload length: " + payloadLength);
-        }
-
-        private static FailWebSocketException notMinimalEncoding(long payloadLength)
-        {
-            return new FailWebSocketException(
-                    "Not minimally-encoded payload length:" + payloadLength);
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/FrameConsumer.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,288 +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 java.net.http.internal.websocket;
-
-import java.net.http.WebSocket.MessagePart;
-import java.net.http.internal.websocket.Frame.Opcode;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Objects.requireNonNull;
-import static java.net.http.internal.common.Utils.dump;
-import static java.net.http.internal.websocket.StatusCodes.NO_STATUS_CODE;
-import static java.net.http.internal.websocket.StatusCodes.isLegalToReceiveFromServer;
-
-/*
- * Consumes frame parts and notifies a message consumer, when there is
- * sufficient data to produce a message, or part thereof.
- *
- * Data consumed but not yet translated is accumulated until it's sufficient to
- * form a message.
- */
-/* Non-final for testing purposes only */
-class FrameConsumer implements Frame.Consumer {
-
-    private final static boolean DEBUG = false;
-    private final MessageStreamConsumer output;
-    private final UTF8AccumulatingDecoder decoder = new UTF8AccumulatingDecoder();
-    private boolean fin;
-    private Opcode opcode, originatingOpcode;
-    private MessagePart part = MessagePart.WHOLE;
-    private long payloadLen;
-    private long unconsumedPayloadLen;
-    private ByteBuffer binaryData;
-
-    FrameConsumer(MessageStreamConsumer output) {
-        this.output = requireNonNull(output);
-    }
-
-    /* Exposed for testing purposes only */
-    MessageStreamConsumer getOutput() {
-        return output;
-    }
-
-    @Override
-    public void fin(boolean value) {
-        if (DEBUG) {
-            System.out.printf("Reading fin: %s%n", value);
-        }
-        fin = value;
-    }
-
-    @Override
-    public void rsv1(boolean value) {
-        if (DEBUG) {
-            System.out.printf("Reading rsv1: %s%n", value);
-        }
-        if (value) {
-            throw new FailWebSocketException("Unexpected rsv1 bit");
-        }
-    }
-
-    @Override
-    public void rsv2(boolean value) {
-        if (DEBUG) {
-            System.out.printf("Reading rsv2: %s%n", value);
-        }
-        if (value) {
-            throw new FailWebSocketException("Unexpected rsv2 bit");
-        }
-    }
-
-    @Override
-    public void rsv3(boolean value) {
-        if (DEBUG) {
-            System.out.printf("Reading rsv3: %s%n", value);
-        }
-        if (value) {
-            throw new FailWebSocketException("Unexpected rsv3 bit");
-        }
-    }
-
-    @Override
-    public void opcode(Opcode v) {
-        if (DEBUG) {
-            System.out.printf("Reading opcode: %s%n", v);
-        }
-        if (v == Opcode.PING || v == Opcode.PONG || v == Opcode.CLOSE) {
-            if (!fin) {
-                throw new FailWebSocketException("Fragmented control frame  " + v);
-            }
-            opcode = v;
-        } else if (v == Opcode.TEXT || v == Opcode.BINARY) {
-            if (originatingOpcode != null) {
-                throw new FailWebSocketException(
-                        format("Unexpected frame %s (fin=%s)", v, fin));
-            }
-            opcode = v;
-            if (!fin) {
-                originatingOpcode = v;
-            }
-        } else if (v == Opcode.CONTINUATION) {
-            if (originatingOpcode == null) {
-                throw new FailWebSocketException(
-                        format("Unexpected frame %s (fin=%s)", v, fin));
-            }
-            opcode = v;
-        } else {
-            throw new FailWebSocketException("Unexpected opcode " + v);
-        }
-    }
-
-    @Override
-    public void mask(boolean value) {
-        if (DEBUG) {
-            System.out.printf("Reading mask: %s%n", value);
-        }
-        if (value) {
-            throw new FailWebSocketException("Masked frame received");
-        }
-    }
-
-    @Override
-    public void payloadLen(long value) {
-        if (DEBUG) {
-            System.out.printf("Reading payloadLen: %s%n", value);
-        }
-        if (opcode.isControl()) {
-            if (value > 125) {
-                throw new FailWebSocketException(
-                        format("%s's payload length %s", opcode, value));
-            }
-            assert Opcode.CLOSE.isControl();
-            if (opcode == Opcode.CLOSE && value == 1) {
-                throw new FailWebSocketException("Incomplete status code");
-            }
-        }
-        payloadLen = value;
-        unconsumedPayloadLen = value;
-    }
-
-    @Override
-    public void maskingKey(int value) {
-        // `FrameConsumer.mask(boolean)` is where a masked frame is detected and
-        // reported on; `FrameConsumer.mask(boolean)` MUST be invoked before
-        // this method;
-        // So this method (`maskingKey`) is not supposed to be invoked while
-        // reading a frame that has came from the server. If this method is
-        // invoked, then it's an error in implementation, thus InternalError
-        throw new InternalError();
-    }
-
-    @Override
-    public void payloadData(ByteBuffer data) {
-        if (DEBUG) {
-            System.out.printf("Reading payloadData: %s%n", data);
-        }
-        unconsumedPayloadLen -= data.remaining();
-        boolean isLast = unconsumedPayloadLen == 0;
-        if (opcode.isControl()) {
-            if (binaryData != null) { // An intermediate or the last chunk
-                binaryData.put(data);
-            } else if (!isLast) { // The first chunk
-                int remaining = data.remaining();
-                // It shouldn't be 125, otherwise the next chunk will be of size
-                // 0, which is not what Reader promises to deliver (eager
-                // reading)
-                assert remaining < 125 : dump(remaining);
-                binaryData = ByteBuffer.allocate(125).put(data);
-            } else { // The only chunk
-                binaryData = ByteBuffer.allocate(data.remaining()).put(data);
-            }
-        } else {
-            part = determinePart(isLast);
-            boolean text = opcode == Opcode.TEXT || originatingOpcode == Opcode.TEXT;
-            if (!text) {
-                output.onBinary(data.slice(), part);
-                data.position(data.limit()); // Consume
-            } else {
-                boolean binaryNonEmpty = data.hasRemaining();
-                CharBuffer textData;
-                try {
-                    textData = decoder.decode(data, part == MessagePart.WHOLE || part == MessagePart.LAST);
-                } catch (CharacterCodingException e) {
-                    throw new FailWebSocketException(
-                            "Invalid UTF-8 in frame " + opcode, StatusCodes.NOT_CONSISTENT)
-                            .initCause(e);
-                }
-                if (!(binaryNonEmpty && !textData.hasRemaining())) {
-                    // If there's a binary data, that result in no text, then we
-                    // don't deliver anything
-                    output.onText(textData, part);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void endFrame() {
-        if (DEBUG) {
-            System.out.println("End frame");
-        }
-        if (opcode.isControl()) {
-            binaryData.flip();
-        }
-        switch (opcode) {
-            case CLOSE:
-                char statusCode = NO_STATUS_CODE;
-                String reason = "";
-                if (payloadLen != 0) {
-                    int len = binaryData.remaining();
-                    assert 2 <= len && len <= 125 : dump(len, payloadLen);
-                    statusCode = binaryData.getChar();
-                    if (!isLegalToReceiveFromServer(statusCode)) {
-                        throw new FailWebSocketException(
-                                "Illegal status code: " + statusCode);
-                    }
-                    try {
-                        reason = UTF_8.newDecoder().decode(binaryData).toString();
-                    } catch (CharacterCodingException e) {
-                        throw new FailWebSocketException("Illegal close reason")
-                                .initCause(e);
-                    }
-                }
-                output.onClose(statusCode, reason);
-                break;
-            case PING:
-                output.onPing(binaryData);
-                binaryData = null;
-                break;
-            case PONG:
-                output.onPong(binaryData);
-                binaryData = null;
-                break;
-            default:
-                assert opcode == Opcode.TEXT || opcode == Opcode.BINARY
-                        || opcode == Opcode.CONTINUATION : dump(opcode);
-                if (fin) {
-                    // It is always the last chunk:
-                    // either TEXT(FIN=TRUE)/BINARY(FIN=TRUE) or CONT(FIN=TRUE)
-                    originatingOpcode = null;
-                }
-                break;
-        }
-        payloadLen = 0;
-        opcode = null;
-    }
-
-    private MessagePart determinePart(boolean isLast) {
-        boolean lastChunk = fin && isLast;
-        switch (part) {
-            case LAST:
-            case WHOLE:
-                return lastChunk ? MessagePart.WHOLE : MessagePart.FIRST;
-            case FIRST:
-            case PART:
-                return lastChunk ? MessagePart.LAST : MessagePart.PART;
-            default:
-                throw new InternalError(String.valueOf(part));
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/MessageStreamConsumer.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +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 java.net.http.internal.websocket;
-
-import java.net.http.WebSocket.MessagePart;
-
-import java.nio.ByteBuffer;
-
-/*
- * A callback for consuming messages and related events on the stream.
- */
-interface MessageStreamConsumer {
-
-    void onText(CharSequence data, MessagePart part);
-
-    void onBinary(ByteBuffer data, MessagePart part);
-
-    void onPing(ByteBuffer data);
-
-    void onPong(ByteBuffer data);
-
-    void onClose(int statusCode, CharSequence reason);
-
-    /*
-     * Indicates the end of stream has been reached and there will be no further
-     * messages.
-     */
-    void onComplete();
-
-    void onError(Throwable e);
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/OpeningHandshake.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,392 +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 java.net.http.internal.websocket;
-
-import java.net.http.HttpClient;
-import java.net.http.HttpClient.Version;
-import java.net.http.HttpHeaders;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpResponse.BodyHandler;
-import java.net.http.WebSocketHandshakeException;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.Pair;
-import java.net.http.internal.common.Utils;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLPermission;
-import java.nio.charset.StandardCharsets;
-import java.security.AccessController;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivilegedAction;
-import java.security.SecureRandom;
-import java.time.Duration;
-import java.util.Base64;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.CompletableFuture;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.lang.String.format;
-import static java.net.http.internal.common.Utils.isValidName;
-import static java.net.http.internal.common.Utils.permissionForProxy;
-import static java.net.http.internal.common.Utils.stringOf;
-
-public class OpeningHandshake {
-
-    private static final String HEADER_CONNECTION = "Connection";
-    private static final String HEADER_UPGRADE    = "Upgrade";
-    private static final String HEADER_ACCEPT     = "Sec-WebSocket-Accept";
-    private static final String HEADER_EXTENSIONS = "Sec-WebSocket-Extensions";
-    private static final String HEADER_KEY        = "Sec-WebSocket-Key";
-    private static final String HEADER_PROTOCOL   = "Sec-WebSocket-Protocol";
-    private static final String HEADER_VERSION    = "Sec-WebSocket-Version";
-    private static final String VERSION           = "13";  // WebSocket's lucky number
-
-    private static final Set<String> ILLEGAL_HEADERS;
-
-    static {
-        ILLEGAL_HEADERS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
-        ILLEGAL_HEADERS.addAll(List.of(HEADER_ACCEPT,
-                                       HEADER_EXTENSIONS,
-                                       HEADER_KEY,
-                                       HEADER_PROTOCOL,
-                                       HEADER_VERSION));
-    }
-
-    private static final SecureRandom random = new SecureRandom();
-
-    private final MessageDigest sha1;
-    private final HttpClient client;
-
-    {
-        try {
-            sha1 = MessageDigest.getInstance("SHA-1");
-        } catch (NoSuchAlgorithmException e) {
-            // Shouldn't happen: SHA-1 must be available in every Java platform
-            // implementation
-            throw new InternalError("Minimum requirements", e);
-        }
-    }
-
-    private final HttpRequest request;
-    private final Collection<String> subprotocols;
-    private final String nonce;
-
-    public OpeningHandshake(BuilderImpl b) {
-        checkURI(b.getUri());
-        Proxy proxy = proxyFor(b.getProxySelector(), b.getUri());
-        checkPermissions(b, proxy);
-        this.client = b.getClient();
-        URI httpURI = createRequestURI(b.getUri());
-        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(httpURI);
-        Duration connectTimeout = b.getConnectTimeout();
-        if (connectTimeout != null) {
-            requestBuilder.timeout(connectTimeout);
-        }
-        for (Pair<String, String> p : b.getHeaders()) {
-            if (ILLEGAL_HEADERS.contains(p.first)) {
-                throw illegal("Illegal header: " + p.first);
-            }
-            requestBuilder.header(p.first, p.second);
-        }
-        this.subprotocols = createRequestSubprotocols(b.getSubprotocols());
-        if (!this.subprotocols.isEmpty()) {
-            String p = this.subprotocols.stream().collect(Collectors.joining(", "));
-            requestBuilder.header(HEADER_PROTOCOL, p);
-        }
-        requestBuilder.header(HEADER_VERSION, VERSION);
-        this.nonce = createNonce();
-        requestBuilder.header(HEADER_KEY, this.nonce);
-        // Setting request version to HTTP/1.1 forcibly, since it's not possible
-        // to upgrade from HTTP/2 to WebSocket (as of August 2016):
-        //
-        //     https://tools.ietf.org/html/draft-hirano-httpbis-websocket-over-http2-00
-        this.request = requestBuilder.version(Version.HTTP_1_1).GET().build();
-        WebSocketRequest r = (WebSocketRequest) this.request;
-        r.isWebSocket(true);
-        r.setSystemHeader(HEADER_UPGRADE, "websocket");
-        r.setSystemHeader(HEADER_CONNECTION, "Upgrade");
-        r.setProxy(proxy);
-    }
-
-    private static Collection<String> createRequestSubprotocols(
-            Collection<String> subprotocols)
-    {
-        LinkedHashSet<String> sp = new LinkedHashSet<>(subprotocols.size(), 1);
-        for (String s : subprotocols) {
-            if (s.trim().isEmpty() || !isValidName(s)) {
-                throw illegal("Bad subprotocol syntax: " + s);
-            }
-            if (!sp.add(s)) {
-                throw illegal("Duplicating subprotocol: " + s);
-            }
-        }
-        return Collections.unmodifiableCollection(sp);
-    }
-
-    /*
-     * Checks the given URI for being a WebSocket URI and translates it into a
-     * target HTTP URI for the Opening Handshake.
-     *
-     * https://tools.ietf.org/html/rfc6455#section-3
-     */
-    static URI createRequestURI(URI uri) {
-        String s = uri.getScheme();
-        assert "ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s);
-        String scheme = "ws".equalsIgnoreCase(s) ? "http" : "https";
-        try {
-            return new URI(scheme,
-                           uri.getUserInfo(),
-                           uri.getHost(),
-                           uri.getPort(),
-                           uri.getPath(),
-                           uri.getQuery(),
-                           null); // No fragment
-        } catch (URISyntaxException e) {
-            // Shouldn't happen: URI invariant
-            throw new InternalError(e);
-        }
-    }
-
-    public CompletableFuture<Result> send() {
-        PrivilegedAction<CompletableFuture<Result>> pa = () ->
-                client.sendAsync(this.request, BodyHandler.discard())
-                      .thenCompose(this::resultFrom);
-        return AccessController.doPrivileged(pa);
-    }
-
-    /*
-     * The result of the opening handshake.
-     */
-    static final class Result {
-
-        final String subprotocol;
-        final TransportFactory transport;
-
-        private Result(String subprotocol, TransportFactory transport) {
-            this.subprotocol = subprotocol;
-            this.transport = transport;
-        }
-    }
-
-    private CompletableFuture<Result> resultFrom(HttpResponse<?> response) {
-        // Do we need a special treatment for SSLHandshakeException?
-        // Namely, invoking
-        //
-        //     Listener.onClose(StatusCodes.TLS_HANDSHAKE_FAILURE, "")
-        //
-        // See https://tools.ietf.org/html/rfc6455#section-7.4.1
-        Result result = null;
-        Exception exception = null;
-        try {
-            result = handleResponse(response);
-        } catch (IOException e) {
-            exception = e;
-        } catch (Exception e) {
-            exception = new WebSocketHandshakeException(response).initCause(e);
-        }
-        if (exception == null) {
-            return MinimalFuture.completedFuture(result);
-        }
-        try {
-            ((RawChannel.Provider) response).rawChannel().close();
-        } catch (IOException e) {
-            exception.addSuppressed(e);
-        }
-        return MinimalFuture.failedFuture(exception);
-    }
-
-    private Result handleResponse(HttpResponse<?> response) throws IOException {
-        // By this point all redirects, authentications, etc. (if any) MUST have
-        // been done by the HttpClient used by the WebSocket; so only 101 is
-        // expected
-        int c = response.statusCode();
-        if (c != 101) {
-            throw checkFailed("Unexpected HTTP response status code " + c);
-        }
-        HttpHeaders headers = response.headers();
-        String upgrade = requireSingle(headers, HEADER_UPGRADE);
-        if (!upgrade.equalsIgnoreCase("websocket")) {
-            throw checkFailed("Bad response field: " + HEADER_UPGRADE);
-        }
-        String connection = requireSingle(headers, HEADER_CONNECTION);
-        if (!connection.equalsIgnoreCase("Upgrade")) {
-            throw checkFailed("Bad response field: " + HEADER_CONNECTION);
-        }
-        Optional<String> version = requireAtMostOne(headers, HEADER_VERSION);
-        if (version.isPresent() && !version.get().equals(VERSION)) {
-            throw checkFailed("Bad response field: " + HEADER_VERSION);
-        }
-        requireAbsent(headers, HEADER_EXTENSIONS);
-        String x = this.nonce + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-        this.sha1.update(x.getBytes(StandardCharsets.ISO_8859_1));
-        String expected = Base64.getEncoder().encodeToString(this.sha1.digest());
-        String actual = requireSingle(headers, HEADER_ACCEPT);
-        if (!actual.trim().equals(expected)) {
-            throw checkFailed("Bad " + HEADER_ACCEPT);
-        }
-        String subprotocol = checkAndReturnSubprotocol(headers);
-        RawChannel channel = ((RawChannel.Provider) response).rawChannel();
-        return new Result(subprotocol, new TransportFactoryImpl(channel));
-    }
-
-    private String checkAndReturnSubprotocol(HttpHeaders responseHeaders)
-            throws CheckFailedException
-    {
-        Optional<String> opt = responseHeaders.firstValue(HEADER_PROTOCOL);
-        if (!opt.isPresent()) {
-            // If there is no such header in the response, then the server
-            // doesn't want to use any subprotocol
-            return "";
-        }
-        String s = requireSingle(responseHeaders, HEADER_PROTOCOL);
-        // An empty string as a subprotocol's name is not allowed by the spec
-        // and the check below will detect such responses too
-        if (this.subprotocols.contains(s)) {
-            return s;
-        } else {
-            throw checkFailed("Unexpected subprotocol: " + s);
-        }
-    }
-
-    private static void requireAbsent(HttpHeaders responseHeaders,
-                                      String headerName)
-    {
-        List<String> values = responseHeaders.allValues(headerName);
-        if (!values.isEmpty()) {
-            throw checkFailed(format("Response field '%s' present: %s",
-                                     headerName,
-                                     stringOf(values)));
-        }
-    }
-
-    private static Optional<String> requireAtMostOne(HttpHeaders responseHeaders,
-                                                     String headerName)
-    {
-        List<String> values = responseHeaders.allValues(headerName);
-        if (values.size() > 1) {
-            throw checkFailed(format("Response field '%s' multivalued: %s",
-                                     headerName,
-                                     stringOf(values)));
-        }
-        return values.stream().findFirst();
-    }
-
-    private static String requireSingle(HttpHeaders responseHeaders,
-                                        String headerName)
-    {
-        List<String> values = responseHeaders.allValues(headerName);
-        if (values.isEmpty()) {
-            throw checkFailed("Response field missing: " + headerName);
-        } else if (values.size() > 1) {
-            throw checkFailed(format("Response field '%s' multivalued: %s",
-                                     headerName,
-                                     stringOf(values)));
-        }
-        return values.get(0);
-    }
-
-    private static String createNonce() {
-        byte[] bytes = new byte[16];
-        OpeningHandshake.random.nextBytes(bytes);
-        return Base64.getEncoder().encodeToString(bytes);
-    }
-
-    private static CheckFailedException checkFailed(String message) {
-        throw new CheckFailedException(message);
-    }
-
-    private static URI checkURI(URI uri) {
-        String scheme = uri.getScheme();
-        if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
-            throw illegal("invalid URI scheme: " + scheme);
-        if (uri.getHost() == null)
-            throw illegal("URI must contain a host: " + uri);
-        if (uri.getFragment() != null)
-            throw illegal("URI must not contain a fragment: " + uri);
-        return uri;
-    }
-
-    private static IllegalArgumentException illegal(String message) {
-        return new IllegalArgumentException(message);
-    }
-
-    /**
-     * Returns the proxy for the given URI when sent through the given client,
-     * or {@code null} if none is required or applicable.
-     */
-    private static Proxy proxyFor(Optional<ProxySelector> selector, URI uri) {
-        if (!selector.isPresent()) {
-            return null;
-        }
-        URI requestURI = createRequestURI(uri); // Based on the HTTP scheme
-        List<Proxy> pl = selector.get().select(requestURI);
-        if (pl.isEmpty()) {
-            return null;
-        }
-        Proxy proxy = pl.get(0);
-        if (proxy.type() != Proxy.Type.HTTP) {
-            return null;
-        }
-        return proxy;
-    }
-
-    /**
-     * Performs the necessary security permissions checks to connect ( possibly
-     * through a proxy ) to the builders WebSocket URI.
-     *
-     * @throws SecurityException if the security manager denies access
-     */
-    static void checkPermissions(BuilderImpl b, Proxy proxy) {
-        SecurityManager sm = System.getSecurityManager();
-        if (sm == null) {
-            return;
-        }
-        Stream<String> headers = b.getHeaders().stream().map(p -> p.first).distinct();
-        URLPermission perm1 = Utils.permissionForServer(b.getUri(), "", headers);
-        sm.checkPermission(perm1);
-        if (proxy == null) {
-            return;
-        }
-        URLPermission perm2 = permissionForProxy((InetSocketAddress) proxy.address());
-        if (perm2 != null) {
-            sm.checkPermission(perm2);
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/OutgoingMessage.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,296 +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 java.net.http.internal.websocket;
-
-import java.net.http.internal.websocket.Frame.Opcode;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CoderResult;
-import java.security.SecureRandom;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Objects.requireNonNull;
-import static java.net.http.internal.common.Utils.EMPTY_BYTEBUFFER;
-import static java.net.http.internal.websocket.Frame.MAX_HEADER_SIZE_BYTES;
-import static java.net.http.internal.websocket.Frame.Opcode.BINARY;
-import static java.net.http.internal.websocket.Frame.Opcode.CLOSE;
-import static java.net.http.internal.websocket.Frame.Opcode.CONTINUATION;
-import static java.net.http.internal.websocket.Frame.Opcode.PING;
-import static java.net.http.internal.websocket.Frame.Opcode.PONG;
-import static java.net.http.internal.websocket.Frame.Opcode.TEXT;
-
-/*
- * A stateful object that represents a WebSocket message being sent to the
- * channel.
- *
- * Data provided to the constructors is copied. Otherwise we would have to deal
- * with mutability, security, masking/unmasking, readonly status, etc. So
- * copying greatly simplifies the implementation.
- *
- * In the case of memory-sensitive environments an alternative implementation
- * could use an internal pool of buffers though at the cost of extra complexity
- * and possible performance degradation.
- */
-abstract class OutgoingMessage {
-
-    // Share per WebSocket?
-    private static final SecureRandom maskingKeys = new SecureRandom();
-
-    protected ByteBuffer[] frame;
-    protected int offset;
-
-    /*
-     * Performs contextualization. This method is not a part of the constructor
-     * so it would be possible to defer the work it does until the most
-     * convenient moment (up to the point where sentTo is invoked).
-     */
-    protected boolean contextualize(Context context) {
-        // masking and charset decoding should be performed here rather than in
-        // the constructor (as of today)
-        if (context.isCloseSent()) {
-            throw new IllegalStateException("Close sent");
-        }
-        return true;
-    }
-
-    protected boolean sendTo(RawChannel channel) throws IOException {
-        while ((offset = nextUnwrittenIndex()) != -1) {
-            long n = channel.write(frame, offset, frame.length - offset);
-            if (n == 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private int nextUnwrittenIndex() {
-        for (int i = offset; i < frame.length; i++) {
-            if (frame[i].hasRemaining()) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    static final class Text extends OutgoingMessage {
-
-        private final ByteBuffer payload;
-        private final boolean isLast;
-
-        Text(CharSequence characters, boolean isLast) {
-            CharsetEncoder encoder = UTF_8.newEncoder(); // Share per WebSocket?
-            try {
-                payload = encoder.encode(CharBuffer.wrap(characters));
-            } catch (CharacterCodingException e) {
-                throw new IllegalArgumentException(
-                        "Malformed UTF-8 text message");
-            }
-            this.isLast = isLast;
-        }
-
-        @Override
-        protected boolean contextualize(Context context) {
-            super.contextualize(context);
-            if (context.isPreviousBinary() && !context.isPreviousLast()) {
-                throw new IllegalStateException("Unexpected text message");
-            }
-            frame = getDataMessageBuffers(
-                    TEXT, context.isPreviousLast(), isLast, payload, payload);
-            context.setPreviousBinary(false);
-            context.setPreviousText(true);
-            context.setPreviousLast(isLast);
-            return true;
-        }
-    }
-
-    static final class Binary extends OutgoingMessage {
-
-        private final ByteBuffer payload;
-        private final boolean isLast;
-
-        Binary(ByteBuffer payload, boolean isLast) {
-            this.payload = requireNonNull(payload);
-            this.isLast = isLast;
-        }
-
-        @Override
-        protected boolean contextualize(Context context) {
-            super.contextualize(context);
-            if (context.isPreviousText() && !context.isPreviousLast()) {
-                throw new IllegalStateException("Unexpected binary message");
-            }
-            ByteBuffer newBuffer = ByteBuffer.allocate(payload.remaining());
-            frame = getDataMessageBuffers(
-                    BINARY, context.isPreviousLast(), isLast, payload, newBuffer);
-            context.setPreviousText(false);
-            context.setPreviousBinary(true);
-            context.setPreviousLast(isLast);
-            return true;
-        }
-    }
-
-    static final class Ping extends OutgoingMessage {
-
-        Ping(ByteBuffer payload) {
-            frame = getControlMessageBuffers(PING, payload);
-        }
-    }
-
-    static final class Pong extends OutgoingMessage {
-
-        Pong(ByteBuffer payload) {
-            frame = getControlMessageBuffers(PONG, payload);
-        }
-    }
-
-    static final class Close extends OutgoingMessage {
-
-        Close() {
-            frame = getControlMessageBuffers(CLOSE, EMPTY_BYTEBUFFER);
-        }
-
-        Close(int statusCode, CharSequence reason) {
-            ByteBuffer payload = ByteBuffer.allocate(125)
-                    .putChar((char) statusCode);
-            CoderResult result = UTF_8.newEncoder()
-                    .encode(CharBuffer.wrap(reason),
-                            payload,
-                            true);
-            if (result.isOverflow()) {
-                throw new IllegalArgumentException("Long reason");
-            } else if (result.isError()) {
-                try {
-                    result.throwException();
-                } catch (CharacterCodingException e) {
-                    throw new IllegalArgumentException(
-                            "Malformed UTF-8 reason", e);
-                }
-            }
-            payload.flip();
-            frame = getControlMessageBuffers(CLOSE, payload);
-        }
-
-        @Override
-        protected boolean contextualize(Context context) {
-            if (context.isCloseSent()) {
-                return false;
-            } else {
-                context.setCloseSent();
-                return true;
-            }
-        }
-    }
-
-    private static ByteBuffer[] getControlMessageBuffers(Opcode opcode,
-                                                         ByteBuffer payload) {
-        assert opcode.isControl() : opcode;
-        int remaining = payload.remaining();
-        if (remaining > 125) {
-            throw new IllegalArgumentException
-                    ("Long message: " + remaining);
-        }
-        ByteBuffer frame = ByteBuffer.allocate(MAX_HEADER_SIZE_BYTES + remaining);
-        int mask = maskingKeys.nextInt();
-        new Frame.HeaderWriter()
-                .fin(true)
-                .opcode(opcode)
-                .payloadLen(remaining)
-                .mask(mask)
-                .write(frame);
-        Frame.Masker.transferMasking(payload, frame, mask);
-        frame.flip();
-        return new ByteBuffer[]{frame};
-    }
-
-    private static ByteBuffer[] getDataMessageBuffers(Opcode type,
-                                                      boolean isPreviousLast,
-                                                      boolean isLast,
-                                                      ByteBuffer payloadSrc,
-                                                      ByteBuffer payloadDst) {
-        assert !type.isControl() && type != CONTINUATION : type;
-        ByteBuffer header = ByteBuffer.allocate(MAX_HEADER_SIZE_BYTES);
-        int mask = maskingKeys.nextInt();
-        new Frame.HeaderWriter()
-                .fin(isLast)
-                .opcode(isPreviousLast ? type : CONTINUATION)
-                .payloadLen(payloadDst.remaining())
-                .mask(mask)
-                .write(header);
-        header.flip();
-        Frame.Masker.transferMasking(payloadSrc, payloadDst, mask);
-        payloadDst.flip();
-        return new ByteBuffer[]{header, payloadDst};
-    }
-
-    /*
-     * An instance of this class is passed sequentially between messages, so
-     * every message in a sequence can check the context it is in and update it
-     * if necessary.
-     */
-    public static class Context {
-
-        boolean previousLast = true;
-        boolean previousBinary;
-        boolean previousText;
-        boolean closeSent;
-
-        private boolean isPreviousText() {
-            return this.previousText;
-        }
-
-        private void setPreviousText(boolean value) {
-            this.previousText = value;
-        }
-
-        private boolean isPreviousBinary() {
-            return this.previousBinary;
-        }
-
-        private void setPreviousBinary(boolean value) {
-            this.previousBinary = value;
-        }
-
-        private boolean isPreviousLast() {
-            return this.previousLast;
-        }
-
-        private void setPreviousLast(boolean value) {
-            this.previousLast = value;
-        }
-
-        private boolean isCloseSent() {
-            return closeSent;
-        }
-
-        private void setCloseSent() {
-            closeSent = true;
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/RawChannel.java	Wed Feb 07 15:46:30 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 java.net.http.internal.websocket;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
-
-/*
- * I/O abstraction used to implement WebSocket.
- *
- * @since 9
- */
-public interface RawChannel extends Closeable {
-
-    interface Provider {
-
-        RawChannel rawChannel() throws IOException;
-    }
-
-    interface RawEvent {
-
-        /*
-         * Returns the selector op flags this event is interested in.
-         */
-        int interestOps();
-
-        /*
-         * Called when event occurs.
-         */
-        void handle();
-    }
-
-    /*
-     * Registers given event whose callback will be called once only (i.e.
-     * register new event for each callback).
-     *
-     * Memory consistency effects: actions in a thread calling registerEvent
-     * happen-before any subsequent actions in the thread calling event.handle
-     */
-    void registerEvent(RawEvent event) throws IOException;
-
-    /**
-     * Hands over the initial bytes. Once the bytes have been returned they are
-     * no longer available and the method will throw an {@link
-     * IllegalStateException} on each subsequent invocation.
-     *
-     * @return the initial bytes
-     * @throws IllegalStateException
-     *         if the method has been already invoked
-     */
-    ByteBuffer initialByteBuffer() throws IllegalStateException;
-
-    /*
-     * Returns a ByteBuffer with the data read or null if EOF is reached. Has no
-     * remaining bytes if no data available at the moment.
-     */
-    ByteBuffer read() throws IOException;
-
-    /*
-     * Writes a sequence of bytes to this channel from a subsequence of the
-     * given buffers.
-     */
-    long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
-
-    /**
-     * Shutdown the connection for reading without closing the channel.
-     *
-     * <p> Once shutdown for reading then further reads on the channel will
-     * return {@code null}, the end-of-stream indication. If the input side of
-     * the connection is already shutdown then invoking this method has no
-     * effect.
-     *
-     * @throws ClosedChannelException
-     *         If this channel is closed
-     * @throws IOException
-     *         If some other I/O error occurs
-     */
-    void shutdownInput() throws IOException;
-
-    /**
-     * Shutdown the connection for writing without closing the channel.
-     *
-     * <p> Once shutdown for writing then further attempts to write to the
-     * channel will throw {@link ClosedChannelException}. If the output side of
-     * the connection is already shutdown then invoking this method has no
-     * effect.
-     *
-     * @throws ClosedChannelException
-     *         If this channel is closed
-     * @throws IOException
-     *         If some other I/O error occurs
-     */
-    void shutdownOutput() throws IOException;
-
-    /**
-     * Closes this channel.
-     *
-     * @throws IOException
-     *         If an I/O error occurs
-     */
-    @Override
-    void close() throws IOException;
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/StatusCodes.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/*
- * 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 java.net.http.internal.websocket;
-
-/*
- * Utilities for WebSocket status codes.
- *
- *     1. https://tools.ietf.org/html/rfc6455#section-7.4
- *     2. http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
- */
-final class StatusCodes {
-
-    static final int PROTOCOL_ERROR    = 1002;
-    static final int NO_STATUS_CODE    = 1005;
-    static final int CLOSED_ABNORMALLY = 1006;
-    static final int NOT_CONSISTENT    = 1007;
-
-    private StatusCodes() { }
-
-    static boolean isLegalToSendFromClient(int code) {
-        if (!isLegal(code)) {
-            return false;
-        }
-        // Codes from unreserved range
-        if (code > 4999) {
-            return false;
-        }
-        // Codes below are not allowed to be sent using a WebSocket client API
-        switch (code) {
-            case PROTOCOL_ERROR:
-            case NOT_CONSISTENT:
-            case 1003:
-            case 1009:
-            case 1010:
-            case 1012:  // code sent by servers
-            case 1013:  // code sent by servers
-            case 1014:  // code sent by servers
-                return false;
-            default:
-                return true;
-        }
-    }
-
-    static boolean isLegalToReceiveFromServer(int code) {
-        if (!isLegal(code)) {
-            return false;
-        }
-        return code != 1010;  // code sent by clients
-    }
-
-    private static boolean isLegal(int code) {
-        // 2-byte unsigned integer excluding first 1000 numbers from the range
-        // [0, 999] which are never used
-        if (code < 1000 || code > 65535) {
-            return false;
-        }
-        // Codes from the range below has no known meaning under the WebSocket
-        // specification (i.e. unassigned/undefined)
-        if ((code >= 1016 && code <= 2999) || code == 1004) {
-            return false;
-        }
-        // Codes below cannot appear on the wire. It's always an error either
-        // to send a frame with such a code or to receive one.
-        switch (code) {
-            case NO_STATUS_CODE:
-            case CLOSED_ABNORMALLY:
-            case 1015:
-                return false;
-            default:
-                return true;
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/Transport.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +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 java.net.http.internal.websocket;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.CompletableFuture;
-
-/*
- * Transport needs some way to asynchronously notify the send operation has been
- * completed. It can have several different designs each of which has its own
- * pros and cons:
- *
- *     (1) void sendMessage(..., Callback)
- *     (2) CompletableFuture<T> sendMessage(...)
- *     (3) CompletableFuture<T> sendMessage(..., Callback)
- *     (4) boolean sendMessage(..., Callback) throws IOException
- *     ...
- *
- * If Transport's users use CFs, (1) forces these users to create CFs and pass
- * them to the callback. If any additional (dependant) action needs to be
- * attached to the returned CF, this means an extra object (CF) must be created
- * in (2). (3) and (4) solves both issues, however (4) does not abstract out
- * when exactly the operation has been performed. So the handling code needs to
- * be repeated twice. And that leads to 2 different code paths (more bugs).
- * Unless designed for this, the user should not assume any specific order of
- * completion in (3) (e.g. callback first and then the returned CF).
- *
- * The only parametrization of Transport<T> used is Transport<WebSocket>. The
- * type parameter T was introduced solely to avoid circular dependency between
- * Transport and WebSocket. After all, instances of T are used solely to
- * complete CompletableFutures. Transport doesn't care about the exact type of
- * T.
- *
- * This way the Transport is fully in charge of creating CompletableFutures.
- * On the one hand, Transport may use it to cache/reuse CompletableFutures. On
- * the other hand, the class that uses Transport, may benefit by not converting
- * from CompletableFuture<K> returned from Transport, to CompletableFuture<V>
- * needed by the said class.
- */
-public interface Transport<T> {
-
-    CompletableFuture<T> sendText(CharSequence message, boolean isLast);
-
-    CompletableFuture<T> sendBinary(ByteBuffer message, boolean isLast);
-
-    CompletableFuture<T> sendPing(ByteBuffer message);
-
-    CompletableFuture<T> sendPong(ByteBuffer message);
-
-    CompletableFuture<T> sendClose(int statusCode, String reason);
-
-    void request(long n);
-
-    /*
-     * Why is this method needed? Since Receiver operates through callbacks
-     * this method allows to abstract out what constitutes as a message being
-     * received (i.e. to decide outside this type when exactly one should
-     * decrement the demand).
-     */
-    void acknowledgeReception();
-
-    void closeOutput() throws IOException;
-
-    void closeInput() throws IOException;
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/TransportFactory.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-package java.net.http.internal.websocket;
-
-import java.util.function.Supplier;
-
-@FunctionalInterface
-public interface TransportFactory {
-
-    <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                     MessageStreamConsumer consumer);
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/TransportFactoryImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +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 java.net.http.internal.websocket;
-
-import java.util.function.Supplier;
-
-public class TransportFactoryImpl implements TransportFactory {
-
-    private final RawChannel channel;
-
-    public TransportFactoryImpl(RawChannel channel) {
-        this.channel = channel;
-    }
-
-    @Override
-    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                            MessageStreamConsumer consumer) {
-        return new TransportImpl<T>(sendResultSupplier, consumer, channel);
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/TransportImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,383 +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 java.net.http.internal.websocket;
-
-import java.net.http.internal.common.Demand;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.Pair;
-import java.net.http.internal.common.SequentialScheduler;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.util.Queue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-import static java.util.Objects.requireNonNull;
-import static java.net.http.internal.common.MinimalFuture.failedFuture;
-import static java.net.http.internal.common.Pair.pair;
-
-public class TransportImpl<T> implements Transport<T> {
-
-    /* This flag is used solely for assertions */
-    private final AtomicBoolean busy = new AtomicBoolean();
-    private OutgoingMessage message;
-    private Consumer<Exception> completionHandler;
-    private final RawChannel channel;
-    private final RawChannel.RawEvent writeEvent = createWriteEvent();
-    private final SequentialScheduler sendScheduler = new SequentialScheduler(new SendTask());
-    private final Queue<Pair<OutgoingMessage, CompletableFuture<T>>>
-            queue = new ConcurrentLinkedQueue<>();
-    private final OutgoingMessage.Context context = new OutgoingMessage.Context();
-    private final Supplier<T> resultSupplier;
-
-    private final MessageStreamConsumer messageConsumer;
-    private final FrameConsumer frameConsumer;
-    private final Frame.Reader reader = new Frame.Reader();
-    private final RawChannel.RawEvent readEvent = createReadEvent();
-    private final Demand demand = new Demand();
-    private final SequentialScheduler receiveScheduler;
-
-    private ByteBuffer data;
-    private volatile int state;
-
-    private static final int UNREGISTERED = 0;
-    private static final int AVAILABLE    = 1;
-    private static final int WAITING      = 2;
-
-    private final Object lock = new Object();
-    private boolean inputClosed;
-    private boolean outputClosed;
-
-    public TransportImpl(Supplier<T> sendResultSupplier,
-                         MessageStreamConsumer consumer,
-                         RawChannel channel) {
-        this.resultSupplier = sendResultSupplier;
-        this.messageConsumer = consumer;
-        this.channel = channel;
-        this.frameConsumer = new FrameConsumer(this.messageConsumer);
-        this.data = channel.initialByteBuffer();
-        // To ensure the initial non-final `data` will be visible
-        // (happens-before) when `readEvent.handle()` invokes `receiveScheduler`
-        // the following assignment is done last:
-        receiveScheduler = new SequentialScheduler(new ReceiveTask());
-    }
-
-    /**
-     * The supplied handler may be invoked in the calling thread.
-     * A {@code StackOverflowError} may thus occur if there's a possibility
-     * that this method is called again by the supplied handler.
-     */
-    public void send(OutgoingMessage message,
-                     Consumer<Exception> completionHandler) {
-        requireNonNull(message);
-        requireNonNull(completionHandler);
-        if (!busy.compareAndSet(false, true)) {
-            throw new IllegalStateException();
-        }
-        send0(message, completionHandler);
-    }
-
-    private RawChannel.RawEvent createWriteEvent() {
-        return new RawChannel.RawEvent() {
-
-            @Override
-            public int interestOps() {
-                return SelectionKey.OP_WRITE;
-            }
-
-            @Override
-            public void handle() {
-                // registerEvent(e) happens-before subsequent e.handle(), so
-                // we're fine reading the stored message and the completionHandler
-                send0(message, completionHandler);
-            }
-        };
-    }
-
-    private void send0(OutgoingMessage message, Consumer<Exception> handler) {
-        boolean b = busy.get();
-        assert b; // Please don't inline this, as busy.get() has memory
-                  // visibility effects and we don't want the program behaviour
-                  // to depend on whether the assertions are turned on
-                  // or turned off
-        try {
-            boolean sent = message.sendTo(channel);
-            if (sent) {
-                busy.set(false);
-                handler.accept(null);
-            } else {
-                // The message has not been fully sent, the transmitter needs to
-                // remember the message until it can continue with sending it
-                this.message = message;
-                this.completionHandler = handler;
-                try {
-                    channel.registerEvent(writeEvent);
-                } catch (IOException e) {
-                    this.message = null;
-                    this.completionHandler = null;
-                    busy.set(false);
-                    handler.accept(e);
-                }
-            }
-        } catch (IOException e) {
-            busy.set(false);
-            handler.accept(e);
-        }
-    }
-
-    public CompletableFuture<T> sendText(CharSequence message,
-                                         boolean isLast) {
-        OutgoingMessage.Text m;
-        try {
-            m = new OutgoingMessage.Text(message, isLast);
-        } catch (IllegalArgumentException e) {
-            return failedFuture(e);
-        }
-        return enqueue(m);
-    }
-
-    public CompletableFuture<T> sendBinary(ByteBuffer message,
-                                           boolean isLast) {
-        return enqueue(new OutgoingMessage.Binary(message, isLast));
-    }
-
-    public CompletableFuture<T> sendPing(ByteBuffer message) {
-        OutgoingMessage.Ping m;
-        try {
-            m = new OutgoingMessage.Ping(message);
-        } catch (IllegalArgumentException e) {
-            return failedFuture(e);
-        }
-        return enqueue(m);
-    }
-
-    public CompletableFuture<T> sendPong(ByteBuffer message) {
-        OutgoingMessage.Pong m;
-        try {
-            m = new OutgoingMessage.Pong(message);
-        } catch (IllegalArgumentException e) {
-            return failedFuture(e);
-        }
-        return enqueue(m);
-    }
-
-    public CompletableFuture<T> sendClose(int statusCode, String reason) {
-        OutgoingMessage.Close m;
-        try {
-            m = new OutgoingMessage.Close(statusCode, reason);
-        } catch (IllegalArgumentException e) {
-            return failedFuture(e);
-        }
-        return enqueue(m);
-    }
-
-    private CompletableFuture<T> enqueue(OutgoingMessage m) {
-        CompletableFuture<T> cf = new MinimalFuture<>();
-        boolean added = queue.add(pair(m, cf));
-        if (!added) {
-            // The queue is supposed to be unbounded
-            throw new InternalError();
-        }
-        sendScheduler.runOrSchedule();
-        return cf;
-    }
-
-    /*
-     * This is a message sending task. It pulls messages from the queue one by
-     * one and sends them. It may be run in different threads, but never
-     * concurrently.
-     */
-    private class SendTask implements SequentialScheduler.RestartableTask {
-
-        @Override
-        public void run(SequentialScheduler.DeferredCompleter taskCompleter) {
-            Pair<OutgoingMessage, CompletableFuture<T>> p = queue.poll();
-            if (p == null) {
-                taskCompleter.complete();
-                return;
-            }
-            OutgoingMessage message = p.first;
-            CompletableFuture<T> cf = p.second;
-            try {
-                if (!message.contextualize(context)) { // Do not send the message
-                    cf.complete(resultSupplier.get());
-                    repeat(taskCompleter);
-                    return;
-                }
-                Consumer<Exception> h = e -> {
-                    if (e == null) {
-                        cf.complete(resultSupplier.get());
-                    } else {
-                        cf.completeExceptionally(e);
-                    }
-                    repeat(taskCompleter);
-                };
-                send(message, h);
-            } catch (Throwable t) {
-                cf.completeExceptionally(t);
-                repeat(taskCompleter);
-            }
-        }
-
-        private void repeat(SequentialScheduler.DeferredCompleter taskCompleter) {
-            taskCompleter.complete();
-            // More than a single message may have been enqueued while
-            // the task has been busy with the current message, but
-            // there is only a single signal recorded
-            sendScheduler.runOrSchedule();
-        }
-    }
-
-    private RawChannel.RawEvent createReadEvent() {
-        return new RawChannel.RawEvent() {
-
-            @Override
-            public int interestOps() {
-                return SelectionKey.OP_READ;
-            }
-
-            @Override
-            public void handle() {
-                state = AVAILABLE;
-                receiveScheduler.runOrSchedule();
-            }
-        };
-    }
-
-    @Override
-    public void request(long n) {
-        if (demand.increase(n)) {
-            receiveScheduler.runOrSchedule();
-        }
-    }
-
-    @Override
-    public void acknowledgeReception() {
-        boolean decremented = demand.tryDecrement();
-        if (!decremented) {
-            throw new InternalError();
-        }
-    }
-
-    private class ReceiveTask extends SequentialScheduler.CompleteRestartableTask {
-
-        @Override
-        public void run() {
-            while (!receiveScheduler.isStopped()) {
-                if (data.hasRemaining()) {
-                    if (!demand.isFulfilled()) {
-                        try {
-                            int oldPos = data.position();
-                            reader.readFrame(data, frameConsumer);
-                            int newPos = data.position();
-                            assert oldPos != newPos : data; // reader always consumes bytes
-                        } catch (Throwable e) {
-                            receiveScheduler.stop();
-                            messageConsumer.onError(e);
-                        }
-                        continue;
-                    }
-                    break;
-                }
-                switch (state) {
-                    case WAITING:
-                        return;
-                    case UNREGISTERED:
-                        try {
-                            state = WAITING;
-                            channel.registerEvent(readEvent);
-                        } catch (Throwable e) {
-                            receiveScheduler.stop();
-                            messageConsumer.onError(e);
-                        }
-                        return;
-                    case AVAILABLE:
-                        try {
-                            data = channel.read();
-                        } catch (Throwable e) {
-                            receiveScheduler.stop();
-                            messageConsumer.onError(e);
-                            return;
-                        }
-                        if (data == null) { // EOF
-                            receiveScheduler.stop();
-                            messageConsumer.onComplete();
-                            return;
-                        } else if (!data.hasRemaining()) {
-                            // No data at the moment Pretty much a "goto",
-                            // reusing the existing code path for registration
-                            state = UNREGISTERED;
-                        }
-                        continue;
-                    default:
-                        throw new InternalError(String.valueOf(state));
-                }
-            }
-        }
-    }
-
-    /*
-     * Permanently stops reading from the channel and delivering messages
-     * regardless of the current demand and data availability.
-     */
-    @Override
-    public void closeInput() throws IOException {
-        synchronized (lock) {
-            if (!inputClosed) {
-                inputClosed = true;
-                try {
-                    receiveScheduler.stop();
-                    channel.shutdownInput();
-                } finally {
-                    if (outputClosed) {
-                        channel.close();
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public void closeOutput() throws IOException {
-        synchronized (lock) {
-            if (!outputClosed) {
-                outputClosed = true;
-                try {
-                    channel.shutdownOutput();
-                } finally {
-                    if (inputClosed) {
-                        channel.close();
-                    }
-                }
-            }
-        }
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/UTF8AccumulatingDecoder.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +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 java.net.http.internal.websocket;
-
-import java.net.http.internal.common.Log;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
-import java.nio.charset.CodingErrorAction;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.net.http.internal.common.Utils.EMPTY_BYTEBUFFER;
-
-final class UTF8AccumulatingDecoder {
-
-    private final CharsetDecoder decoder = UTF_8.newDecoder();
-
-    {
-        decoder.onMalformedInput(CodingErrorAction.REPORT);
-        decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
-    }
-
-    private ByteBuffer leftovers = EMPTY_BYTEBUFFER;
-
-    CharBuffer decode(ByteBuffer in, boolean endOfInput)
-            throws CharacterCodingException
-    {
-        ByteBuffer b;
-        int rem = leftovers.remaining();
-        if (rem != 0) {
-            // We won't need this wasteful allocation & copying when JDK-8155222
-            // has been resolved
-            b = ByteBuffer.allocate(rem + in.remaining());
-            b.put(leftovers).put(in).flip();
-        } else {
-            b = in;
-        }
-        CharBuffer out = CharBuffer.allocate(b.remaining());
-        CoderResult r = decoder.decode(b, out, endOfInput);
-        if (r.isError()) {
-            r.throwException();
-        }
-        if (b.hasRemaining()) {
-            leftovers = ByteBuffer.allocate(b.remaining()).put(b).flip();
-        } else {
-            leftovers = EMPTY_BYTEBUFFER;
-        }
-        // Since it's UTF-8, the assumption is leftovers.remaining() < 4
-        // (i.e. small). Otherwise a shared buffer should be used
-        if (!(leftovers.remaining() < 4)) {
-            Log.logError("The size of decoding leftovers is greater than expected: {0}",
-                         leftovers.remaining());
-        }
-        b.position(b.limit()); // As if we always read to the end
-        // Decoder promises that in the case of endOfInput == true:
-        // "...any remaining undecoded input will be treated as being
-        // malformed"
-        assert !(endOfInput && leftovers.hasRemaining()) : endOfInput + ", " + leftovers;
-        if (endOfInput) {
-            r = decoder.flush(out);
-            decoder.reset();
-            if (r.isOverflow()) {
-                // FIXME: for now I know flush() does nothing. But the
-                // implementation of UTF8 decoder might change. And if now
-                // flush() is a no-op, it is not guaranteed to remain so in
-                // the future
-                throw new InternalError("Not yet implemented");
-            }
-        }
-        return out.flip();
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/WebSocketImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,533 +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 java.net.http.internal.websocket;
-
-import java.net.http.WebSocket;
-import java.net.http.internal.common.Demand;
-import java.net.http.internal.common.Log;
-import java.net.http.internal.common.MinimalFuture;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.Utils;
-import java.net.http.internal.websocket.OpeningHandshake.Result;
-
-import java.io.IOException;
-import java.lang.ref.Reference;
-import java.net.ProtocolException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Function;
-
-import static java.util.Objects.requireNonNull;
-import static java.net.http.internal.common.MinimalFuture.failedFuture;
-import static java.net.http.internal.websocket.StatusCodes.CLOSED_ABNORMALLY;
-import static java.net.http.internal.websocket.StatusCodes.NO_STATUS_CODE;
-import static java.net.http.internal.websocket.StatusCodes.isLegalToSendFromClient;
-import static java.net.http.internal.websocket.WebSocketImpl.State.BINARY;
-import static java.net.http.internal.websocket.WebSocketImpl.State.CLOSE;
-import static java.net.http.internal.websocket.WebSocketImpl.State.ERROR;
-import static java.net.http.internal.websocket.WebSocketImpl.State.IDLE;
-import static java.net.http.internal.websocket.WebSocketImpl.State.OPEN;
-import static java.net.http.internal.websocket.WebSocketImpl.State.PING;
-import static java.net.http.internal.websocket.WebSocketImpl.State.PONG;
-import static java.net.http.internal.websocket.WebSocketImpl.State.TEXT;
-import static java.net.http.internal.websocket.WebSocketImpl.State.WAITING;
-
-/*
- * A WebSocket client.
- */
-public final class WebSocketImpl implements WebSocket {
-
-    enum State {
-        OPEN,
-        IDLE,
-        WAITING,
-        TEXT,
-        BINARY,
-        PING,
-        PONG,
-        CLOSE,
-        ERROR;
-    }
-
-    private volatile boolean inputClosed;
-    private volatile boolean outputClosed;
-
-    private final AtomicReference<State> state = new AtomicReference<>(OPEN);
-
-    /* Components of calls to Listener's methods */
-    private MessagePart part;
-    private ByteBuffer binaryData;
-    private CharSequence text;
-    private int statusCode;
-    private String reason;
-    private final AtomicReference<Throwable> error = new AtomicReference<>();
-
-    private final URI uri;
-    private final String subprotocol;
-    private final Listener listener;
-
-    private final AtomicBoolean outstandingSend = new AtomicBoolean();
-    private final Transport<WebSocket> transport;
-    private final SequentialScheduler receiveScheduler = new SequentialScheduler(new ReceiveTask());
-    private final Demand demand = new Demand();
-
-    public static CompletableFuture<WebSocket> newInstanceAsync(BuilderImpl b) {
-        Function<Result, WebSocket> newWebSocket = r -> {
-            WebSocket ws = newInstance(b.getUri(),
-                                       r.subprotocol,
-                                       b.getListener(),
-                                       r.transport);
-            // Make sure we don't release the builder until this lambda
-            // has been executed. The builder has a strong reference to
-            // the HttpClientFacade, and we want to keep that live until
-            // after the raw channel is created and passed to WebSocketImpl.
-            Reference.reachabilityFence(b);
-            return ws;
-        };
-        OpeningHandshake h;
-        try {
-            h = new OpeningHandshake(b);
-        } catch (Throwable e) {
-            return failedFuture(e);
-        }
-        return h.send().thenApply(newWebSocket);
-    }
-
-    /* Exposed for testing purposes */
-    static WebSocketImpl newInstance(URI uri,
-                                     String subprotocol,
-                                     Listener listener,
-                                     TransportFactory transport) {
-        WebSocketImpl ws = new WebSocketImpl(uri, subprotocol, listener, transport);
-        // This initialisation is outside of the constructor for the sake of
-        // safe publication of WebSocketImpl.this
-        ws.signalOpen();
-        return ws;
-    }
-
-    private WebSocketImpl(URI uri,
-                          String subprotocol,
-                          Listener listener,
-                          TransportFactory transportFactory) {
-        this.uri = requireNonNull(uri);
-        this.subprotocol = requireNonNull(subprotocol);
-        this.listener = requireNonNull(listener);
-        this.transport = transportFactory.createTransport(
-                () -> WebSocketImpl.this, // What about escape of WebSocketImpl.this?
-                new SignallingMessageConsumer());
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendText(CharSequence message,
-                                                 boolean isLast) {
-        Objects.requireNonNull(message);
-        if (!outstandingSend.compareAndSet(false, true)) {
-            return failedFuture(new IllegalStateException("Send pending"));
-        }
-        CompletableFuture<WebSocket> cf = transport.sendText(message, isLast);
-        return cf.whenComplete((r, e) -> outstandingSend.set(false));
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendBinary(ByteBuffer message,
-                                                   boolean isLast) {
-        Objects.requireNonNull(message);
-        if (!outstandingSend.compareAndSet(false, true)) {
-            return failedFuture(new IllegalStateException("Send pending"));
-        }
-        CompletableFuture<WebSocket> cf = transport.sendBinary(message, isLast);
-        // Optimize?
-        //     if (cf.isDone()) {
-        //         outstandingSend.set(false);
-        //     } else {
-        //         cf.whenComplete((r, e) -> outstandingSend.set(false));
-        //     }
-        return cf.whenComplete((r, e) -> outstandingSend.set(false));
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendPing(ByteBuffer message) {
-        return transport.sendPing(message);
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendPong(ByteBuffer message) {
-        return transport.sendPong(message);
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendClose(int statusCode, String reason) {
-        Objects.requireNonNull(reason);
-        if (!isLegalToSendFromClient(statusCode)) {
-            return failedFuture(new IllegalArgumentException("statusCode"));
-        }
-        return sendClose0(statusCode, reason);
-    }
-
-    /*
-     * Sends a Close message, then shuts down the output since no more
-     * messages are expected to be sent after this.
-     */
-    private CompletableFuture<WebSocket> sendClose0(int statusCode, String reason ) {
-        outputClosed = true;
-        return transport.sendClose(statusCode, reason)
-                .whenComplete((result, error) -> {
-                    try {
-                        transport.closeOutput();
-                    } catch (IOException e) {
-                        Log.logError(e);
-                    }
-                    Throwable cause = Utils.getCompletionCause(error);
-                    if (cause instanceof TimeoutException) {
-                        try {
-                            transport.closeInput();
-                        } catch (IOException e) {
-                            Log.logError(e);
-                        }
-                    }
-                });
-    }
-
-    @Override
-    public void request(long n) {
-        if (demand.increase(n)) {
-            receiveScheduler.runOrSchedule();
-        }
-    }
-
-    @Override
-    public String getSubprotocol() {
-        return subprotocol;
-    }
-
-    @Override
-    public boolean isOutputClosed() {
-        return outputClosed;
-    }
-
-    @Override
-    public boolean isInputClosed() {
-        return inputClosed;
-    }
-
-    @Override
-    public void abort() {
-        inputClosed = true;
-        outputClosed = true;
-        receiveScheduler.stop();
-        close();
-    }
-
-    @Override
-    public String toString() {
-        return super.toString()
-                + "[uri=" + uri
-                + (!subprotocol.isEmpty() ? ", subprotocol=" + subprotocol : "")
-                + "]";
-    }
-
-    /*
-     * The assumptions about order is as follows:
-     *
-     *     - state is never changed more than twice inside the `run` method:
-     *       x --(1)--> IDLE --(2)--> y (otherwise we're loosing events, or
-     *       overwriting parts of messages creating a mess since there's no
-     *       queueing)
-     *     - OPEN is always the first state
-     *     - no messages are requested/delivered before onOpen is called (this
-     *       is implemented by making WebSocket instance accessible first in
-     *       onOpen)
-     *     - after the state has been observed as CLOSE/ERROR, the scheduler
-     *       is stopped
-     */
-    private class ReceiveTask extends SequentialScheduler.CompleteRestartableTask {
-
-        // Transport only asked here and nowhere else because we must make sure
-        // onOpen is invoked first and no messages become pending before onOpen
-        // finishes
-
-        @Override
-        public void run() {
-            while (true) {
-                State s = state.get();
-                try {
-                    switch (s) {
-                        case OPEN:
-                            processOpen();
-                            tryChangeState(OPEN, IDLE);
-                            break;
-                        case TEXT:
-                            processText();
-                            tryChangeState(TEXT, IDLE);
-                            break;
-                        case BINARY:
-                            processBinary();
-                            tryChangeState(BINARY, IDLE);
-                            break;
-                        case PING:
-                            processPing();
-                            tryChangeState(PING, IDLE);
-                            break;
-                        case PONG:
-                            processPong();
-                            tryChangeState(PONG, IDLE);
-                            break;
-                        case CLOSE:
-                            processClose();
-                            return;
-                        case ERROR:
-                            processError();
-                            return;
-                        case IDLE:
-                            if (demand.tryDecrement()
-                                    && tryChangeState(IDLE, WAITING)) {
-                                transport.request(1);
-                            }
-                            return;
-                        case WAITING:
-                            // For debugging spurious signalling: when there was a
-                            // signal, but apparently nothing has changed
-                            return;
-                        default:
-                            throw new InternalError(String.valueOf(s));
-                    }
-                } catch (Throwable t) {
-                    signalError(t);
-                }
-            }
-        }
-
-        private void processError() throws IOException {
-            transport.closeInput();
-            receiveScheduler.stop();
-            Throwable err = error.get();
-            if (err instanceof FailWebSocketException) {
-                int code1 = ((FailWebSocketException) err).getStatusCode();
-                err = new ProtocolException().initCause(err);
-                sendClose0(code1, "")
-                        .whenComplete(
-                                (r, e) -> {
-                                    if (e != null) {
-                                        Log.logError(e);
-                                    }
-                                });
-            }
-            listener.onError(WebSocketImpl.this, err);
-        }
-
-        private void processClose() throws IOException {
-            transport.closeInput();
-            receiveScheduler.stop();
-            CompletionStage<?> readyToClose;
-            readyToClose = listener.onClose(WebSocketImpl.this, statusCode, reason);
-            if (readyToClose == null) {
-                readyToClose = MinimalFuture.completedFuture(null);
-            }
-            int code;
-            if (statusCode == NO_STATUS_CODE || statusCode == CLOSED_ABNORMALLY) {
-                code = NORMAL_CLOSURE;
-            } else {
-                code = statusCode;
-            }
-            readyToClose.whenComplete((r, e) -> {
-                sendClose0(code, "")
-                        .whenComplete((r1, e1) -> {
-                            if (e1 != null) {
-                                Log.logError(e1);
-                            }
-                        });
-            });
-        }
-
-        private void processPong() {
-            listener.onPong(WebSocketImpl.this, binaryData);
-        }
-
-        private void processPing() {
-            // Let's make a full copy of this tiny data. What we want here
-            // is to rule out a possibility the shared data we send might be
-            // corrupted by processing in the listener.
-            ByteBuffer slice = binaryData.slice();
-            ByteBuffer copy = ByteBuffer.allocate(binaryData.remaining())
-                    .put(binaryData)
-                    .flip();
-            // Non-exclusive send;
-            CompletableFuture<WebSocket> pongSent = transport.sendPong(copy);
-            pongSent.whenComplete(
-                    (r, e) -> {
-                        if (e != null) {
-                            signalError(Utils.getCompletionCause(e));
-                        }
-                    }
-            );
-            listener.onPing(WebSocketImpl.this, slice);
-        }
-
-        private void processBinary() {
-            listener.onBinary(WebSocketImpl.this, binaryData, part);
-        }
-
-        private void processText() {
-            listener.onText(WebSocketImpl.this, text, part);
-        }
-
-        private void processOpen() {
-            listener.onOpen(WebSocketImpl.this);
-        }
-    }
-
-    private void signalOpen() {
-        receiveScheduler.runOrSchedule();
-    }
-
-    private void signalError(Throwable error) {
-        inputClosed = true;
-        outputClosed = true;
-        if (!this.error.compareAndSet(null, error) || !trySetState(ERROR)) {
-            Log.logError(error);
-        } else {
-            close();
-        }
-    }
-
-    private void close() {
-        try {
-            try {
-                transport.closeInput();
-            } finally {
-                transport.closeOutput();
-            }
-        } catch (Throwable t) {
-            Log.logError(t);
-        }
-    }
-
-    /*
-     * Signals a Close event (might not correspond to anything happened on the
-     * channel, i.e. might be synthetic).
-     */
-    private void signalClose(int statusCode, String reason) {
-        inputClosed = true;
-        this.statusCode = statusCode;
-        this.reason = reason;
-        if (!trySetState(CLOSE)) {
-            Log.logTrace("Close: {0}, ''{1}''", statusCode, reason);
-        } else {
-            try {
-                transport.closeInput();
-            } catch (Throwable t) {
-                Log.logError(t);
-            }
-        }
-    }
-
-    private class SignallingMessageConsumer implements MessageStreamConsumer {
-
-        @Override
-        public void onText(CharSequence data, MessagePart part) {
-            transport.acknowledgeReception();
-            text = data;
-            WebSocketImpl.this.part = part;
-            tryChangeState(WAITING, TEXT);
-        }
-
-        @Override
-        public void onBinary(ByteBuffer data, MessagePart part) {
-            transport.acknowledgeReception();
-            binaryData = data;
-            WebSocketImpl.this.part = part;
-            tryChangeState(WAITING, BINARY);
-        }
-
-        @Override
-        public void onPing(ByteBuffer data) {
-            transport.acknowledgeReception();
-            binaryData = data;
-            tryChangeState(WAITING, PING);
-        }
-
-        @Override
-        public void onPong(ByteBuffer data) {
-            transport.acknowledgeReception();
-            binaryData = data;
-            tryChangeState(WAITING, PONG);
-        }
-
-        @Override
-        public void onClose(int statusCode, CharSequence reason) {
-            transport.acknowledgeReception();
-            signalClose(statusCode, reason.toString());
-        }
-
-        @Override
-        public void onComplete() {
-            transport.acknowledgeReception();
-            signalClose(CLOSED_ABNORMALLY, "");
-        }
-
-        @Override
-        public void onError(Throwable error) {
-            signalError(error);
-        }
-    }
-
-    private boolean trySetState(State newState) {
-        while (true) {
-            State currentState = state.get();
-            if (currentState == ERROR || currentState == CLOSE) {
-                return false;
-            } else if (state.compareAndSet(currentState, newState)) {
-                receiveScheduler.runOrSchedule();
-                return true;
-            }
-        }
-    }
-
-    private boolean tryChangeState(State expectedState, State newState) {
-        State witness = state.compareAndExchange(expectedState, newState);
-        if (witness == expectedState) {
-            receiveScheduler.runOrSchedule();
-            return true;
-        }
-        // This should be the only reason for inability to change the state from
-        // IDLE to WAITING: the state has changed to terminal
-        if (witness != ERROR && witness != CLOSE) {
-            throw new InternalError();
-        }
-        return false;
-    }
-
-    /* Exposed for testing purposes */
-    protected final Transport<WebSocket> transport() {
-        return transport;
-    }
-}
--- a/src/java.net.http/share/classes/java/net/http/internal/websocket/WebSocketRequest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/*
- * 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 java.net.http.internal.websocket;
-
-import java.net.Proxy;
-
-/*
- * https://tools.ietf.org/html/rfc6455#section-4.1
- */
-public interface WebSocketRequest {
-
-    /*
-     * If set to `true` and a proxy is used, instructs the implementation that
-     * a TCP tunnel must be opened.
-     */
-    void isWebSocket(boolean flag);
-
-    /*
-     * Needed for setting "Connection" and "Upgrade" headers as required by the
-     * WebSocket specification.
-     */
-    void setSystemHeader(String name, String value);
-
-    /*
-     * Sets the proxy for this request.
-     */
-    void setProxy(Proxy proxy);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.SSLTube;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/AbstractSubscription.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.util.concurrent.Flow;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/AsyncEvent.java	Wed Feb 07 21:45:37 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.internal.net.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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLConnection.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.SSLTube;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpHeaders;
+import jdk.internal.net.http.common.SSLTube;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/AsyncTriggerEvent.java	Wed Feb 07 21:45:37 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.internal.net.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; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpHeaders;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/BufferingSubscriber.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,315 @@
+/*
+ * 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.internal.net.http;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Objects;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.net.http.HttpResponse.BodySubscriber;
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.Utils;
+
+/**
+ * A buffering BodySubscriber. When subscribed, accumulates ( buffers ) a given
+ * amount ( in bytes ) of a publisher's data before pushing it to a downstream
+ * subscriber.
+ */
+public class BufferingSubscriber<T> implements BodySubscriber<T>
+{
+    /** The downstream consumer of the data. */
+    private final BodySubscriber<T> downstreamSubscriber;
+    /** The amount of data to be accumulate before pushing downstream. */
+    private final int bufferSize;
+
+    /** The subscription, created lazily. */
+    private volatile Flow.Subscription subscription;
+    /** The downstream subscription, created lazily. */
+    private volatile DownstreamSubscription downstreamSubscription;
+
+    /** Must be held when accessing the internal buffers. */
+    private final Object buffersLock = new Object();
+    /** The internal buffers holding the buffered data. */
+    private ArrayList<ByteBuffer> internalBuffers;
+    /** The actual accumulated remaining bytes in internalBuffers. */
+    private int accumulatedBytes;
+
+    /** Holds the Throwable from upstream's onError. */
+    private volatile Throwable throwable;
+
+    /** State of the buffering subscriber:
+     *  1) [UNSUBSCRIBED] when initially created
+     *  2) [ACTIVE] when subscribed and can receive data
+     *  3) [ERROR | CANCELLED | COMPLETE] (terminal state)
+     */
+    static final int UNSUBSCRIBED = 0x01;
+    static final int ACTIVE       = 0x02;
+    static final int ERROR        = 0x04;
+    static final int CANCELLED    = 0x08;
+    static final int COMPLETE     = 0x10;
+
+    private volatile int state;
+
+    public BufferingSubscriber(BodySubscriber<T> downstreamSubscriber,
+                               int bufferSize) {
+        this.downstreamSubscriber = Objects.requireNonNull(downstreamSubscriber);
+        this.bufferSize = bufferSize;
+        synchronized (buffersLock) {
+            internalBuffers = new ArrayList<>();
+        }
+        state = UNSUBSCRIBED;
+    }
+
+    /** Returns the number of bytes remaining in the given buffers. */
+    private static final long remaining(List<ByteBuffer> buffers) {
+        return buffers.stream().mapToLong(ByteBuffer::remaining).sum();
+    }
+
+    /**
+     * Tells whether, or not, there is at least a sufficient number of bytes
+     * accumulated in the internal buffers. If the subscriber is COMPLETE, and
+     * has some buffered data, then there is always enough ( to pass downstream ).
+     */
+    private final boolean hasEnoughAccumulatedBytes() {
+        assert Thread.holdsLock(buffersLock);
+        return accumulatedBytes >= bufferSize
+                || (state == COMPLETE && accumulatedBytes > 0);
+    }
+
+    /**
+     * Returns a new, unmodifiable, List<ByteBuffer> containing exactly the
+     * amount of data as required before pushing downstream. The amount of data
+     * may be less than required ( bufferSize ), in the case where the subscriber
+     * is COMPLETE.
+     */
+    private List<ByteBuffer> fromInternalBuffers() {
+        assert Thread.holdsLock(buffersLock);
+        int leftToFill = bufferSize;
+        int state = this.state;
+        assert (state == ACTIVE || state == CANCELLED)
+                ? accumulatedBytes >= leftToFill : true;
+        List<ByteBuffer> dsts = new ArrayList<>();
+
+        ListIterator<ByteBuffer> itr = internalBuffers.listIterator();
+        while (itr.hasNext()) {
+            ByteBuffer b = itr.next();
+            if (b.remaining() <= leftToFill) {
+                itr.remove();
+                if (b.position() != 0)
+                    b = b.slice();  // ensure position = 0 when propagated
+                dsts.add(b);
+                leftToFill -= b.remaining();
+                accumulatedBytes -= b.remaining();
+                if (leftToFill == 0)
+                    break;
+            } else {
+                int prevLimit = b.limit();
+                b.limit(b.position() + leftToFill);
+                ByteBuffer slice = b.slice();
+                dsts.add(slice);
+                b.limit(prevLimit);
+                b.position(b.position() + leftToFill);
+                accumulatedBytes -= leftToFill;
+                leftToFill = 0;
+                break;
+            }
+        }
+        assert (state == ACTIVE || state == CANCELLED)
+                ? leftToFill == 0 : state == COMPLETE;
+        assert (state == ACTIVE || state == CANCELLED)
+                ? remaining(dsts) == bufferSize : state == COMPLETE;
+        assert accumulatedBytes >= 0;
+        assert dsts.stream().noneMatch(b -> b.position() != 0);
+        return Collections.unmodifiableList(dsts);
+    }
+
+    /** Subscription that is passed to the downstream subscriber. */
+    private class DownstreamSubscription implements Flow.Subscription {
+        private final AtomicBoolean cancelled = new AtomicBoolean(); // false
+        private final Demand demand = new Demand();
+        private volatile boolean illegalArg;
+
+        @Override
+        public void request(long n) {
+            if (cancelled.get() || illegalArg) {
+                return;
+            }
+            if (n <= 0L) {
+                // pass the "bad" value upstream so the Publisher can deal with
+                // it appropriately, i.e. invoke onError
+                illegalArg = true;
+                subscription.request(n);
+                return;
+            }
+
+            demand.increase(n);
+
+            pushDemanded();
+        }
+
+        private final SequentialScheduler pushDemandedScheduler =
+                new SequentialScheduler(new PushDemandedTask());
+
+        void pushDemanded() {
+            if (cancelled.get())
+                return;
+            pushDemandedScheduler.runOrSchedule();
+        }
+
+        class PushDemandedTask extends SequentialScheduler.CompleteRestartableTask {
+            @Override
+            public void run() {
+                try {
+                    Throwable t = throwable;
+                    if (t != null) {
+                        pushDemandedScheduler.stop(); // stop the demand scheduler
+                        downstreamSubscriber.onError(t);
+                        return;
+                    }
+
+                    while (true) {
+                        List<ByteBuffer> item;
+                        synchronized (buffersLock) {
+                            if (cancelled.get())
+                                return;
+                            if (!hasEnoughAccumulatedBytes())
+                                break;
+                            if (!demand.tryDecrement())
+                                break;
+                            item = fromInternalBuffers();
+                        }
+                        assert item != null;
+
+                        downstreamSubscriber.onNext(item);
+                    }
+                    if (cancelled.get())
+                        return;
+
+                    // complete only if all data consumed
+                    boolean complete;
+                    synchronized (buffersLock) {
+                        complete = state == COMPLETE && internalBuffers.isEmpty();
+                    }
+                    if (complete) {
+                        assert internalBuffers.isEmpty();
+                        pushDemandedScheduler.stop(); // stop the demand scheduler
+                        downstreamSubscriber.onComplete();
+                        return;
+                    }
+                } catch (Throwable t) {
+                    cancel();  // cancel if there is any error
+                    throw t;
+                }
+
+                boolean requestMore = false;
+                synchronized (buffersLock) {
+                    if (!hasEnoughAccumulatedBytes() && !demand.isFulfilled()) {
+                        // request more upstream data
+                        requestMore = true;
+                    }
+                }
+                if (requestMore)
+                    subscription.request(1);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            if (cancelled.compareAndExchange(false, true))
+                return;  // already cancelled
+
+            state = CANCELLED;  // set CANCELLED state of upstream subscriber
+            subscription.cancel();  // cancel upstream subscription
+            pushDemandedScheduler.stop(); // stop the demand scheduler
+        }
+    }
+
+    @Override
+    public void onSubscribe(Flow.Subscription subscription) {
+        Objects.requireNonNull(subscription);
+        if (this.subscription != null) {
+            subscription.cancel();
+            return;
+        }
+
+        int s = this.state;
+        assert s == UNSUBSCRIBED;
+        state = ACTIVE;
+        this.subscription = subscription;
+        downstreamSubscription = new DownstreamSubscription();
+        downstreamSubscriber.onSubscribe(downstreamSubscription);
+    }
+
+    @Override
+    public void onNext(List<ByteBuffer> item) {
+        Objects.requireNonNull(item);
+
+        int s = state;
+        if (s == CANCELLED)
+            return;
+
+        if (s != ACTIVE)
+            throw new InternalError("onNext on inactive subscriber");
+
+        synchronized (buffersLock) {
+            internalBuffers.addAll(item);
+            accumulatedBytes += remaining(item);
+        }
+
+        downstreamSubscription.pushDemanded();
+    }
+
+    @Override
+    public void onError(Throwable incomingThrowable) {
+        Objects.requireNonNull(incomingThrowable);
+        int s = state;
+        assert s == ACTIVE : "Expected ACTIVE, got:" + s;
+        state = ERROR;
+        Throwable t = this.throwable;
+        assert t == null : "Expected null, got:" + t;
+        this.throwable = incomingThrowable;
+        downstreamSubscription.pushDemanded();
+    }
+
+    @Override
+    public void onComplete() {
+        int s = state;
+        assert s == ACTIVE : "Expected ACTIVE, got:" + s;
+        state = COMPLETE;
+        downstreamSubscription.pushDemanded();
+    }
+
+    @Override
+    public CompletionStage<T> getBody() {
+        return downstreamSubscriber.getBody();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ConnectionPool.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/CookieFilter.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.net.http.HttpHeaders;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/Exchange.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpResponse;
+import java.net.http.HttpTimeoutException;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.common.Log;
+
+import static jdk.internal.net.http.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.replace(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/java.net.http/share/classes/jdk/internal/net/http/ExchangeImpl.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpResponse;
+import jdk.internal.net.http.common.MinimalFuture;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/FilterFactory.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.util.LinkedList;
+import java.util.List;
+
+class FilterFactory {
+
+    final LinkedList<Class<? extends HeaderFilter>> filterClasses = new LinkedList<>();
+
+    public void addFilter(Class<? extends HeaderFilter> type) {
+        filterClasses.add(type);
+    }
+
+    List<HeaderFilter> getFilterChain() {
+        List<HeaderFilter> l = new LinkedList<>();
+        for (Class<? extends HeaderFilter> clazz : filterClasses) {
+            try {
+                // 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/java.net.http/share/classes/jdk/internal/net/http/HeaderFilter.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.io.IOException;
+
+/**
+ * A header filter that can examine or modify, typically system headers for
+ * requests before they are sent, and responses before they are returned to the
+ * user. Some ability to resend requests is provided.
+ */
+interface HeaderFilter {
+
+    void request(HttpRequestImpl r, 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/java.net.http/share/classes/jdk/internal/net/http/HeaderParser.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+/* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
+ * sensibly:
+ * From a String like: 'timeout=15, max=5'
+ * create an array of Strings:
+ * { {"timeout", "15"},
+ *   {"max", "5"}
+ * }
+ * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
+ * create one like (no quotes in literal):
+ * { {"basic", null},
+ *   {"realm", "FuzzFace"}
+ *   {"foo", "Biz Bar Baz"}
+ * }
+ * keys are converted to lower case, vals are left as is....
+ */
+class HeaderParser {
+
+    /* table of key/val pairs */
+    String raw;
+    String[][] tab;
+    int nkeys;
+    int asize = 10; // initial size of array is 10
+
+    public HeaderParser(String raw) {
+        this.raw = raw;
+        tab = new String[asize][2];
+        parse();
+    }
+
+//    private HeaderParser () { }
+
+//    /**
+//     * Creates a new HeaderParser from this, whose keys (and corresponding
+//     * values) range from "start" to "end-1"
+//     */
+//    public HeaderParser subsequence(int start, int end) {
+//        if (start == 0 && end == nkeys) {
+//            return this;
+//        }
+//        if (start < 0 || start >= end || end > nkeys) {
+//            throw new IllegalArgumentException("invalid start or end");
+//        }
+//        HeaderParser n = new HeaderParser();
+//        n.tab = new String [asize][2];
+//        n.asize = asize;
+//        System.arraycopy (tab, start, n.tab, 0, (end-start));
+//        n.nkeys= (end-start);
+//        return n;
+//    }
+
+    private void parse() {
+
+        if (raw != null) {
+            raw = raw.trim();
+            char[] ca = raw.toCharArray();
+            int beg = 0, end = 0, i = 0;
+            boolean inKey = true;
+            boolean inQuote = false;
+            int len = ca.length;
+            while (end < len) {
+                char c = ca[end];
+                if ((c == '=') && !inQuote) { // end of a key
+                    tab[i][0] = new String(ca, beg, end-beg).toLowerCase(Locale.US);
+                    inKey = false;
+                    end++;
+                    beg = end;
+                } else if (c == '\"') {
+                    if (inQuote) {
+                        tab[i++][1]= new String(ca, beg, end-beg);
+                        inQuote=false;
+                        do {
+                            end++;
+                        } while (end < len && (ca[end] == ' ' || ca[end] == ','));
+                        inKey=true;
+                        beg=end;
+                    } else {
+                        inQuote=true;
+                        end++;
+                        beg=end;
+                    }
+                } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
+                    if (inQuote) {
+                        end++;
+                        continue;
+                    } else if (inKey) {
+                        tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase(Locale.US);
+                    } else {
+                        tab[i++][1] = (new String(ca, beg, end-beg));
+                    }
+                    while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
+                        end++;
+                    }
+                    inKey = true;
+                    beg = end;
+                } else {
+                    end++;
+                }
+                if (i == asize) {
+                    asize = asize * 2;
+                    String[][] ntab = new String[asize][2];
+                    System.arraycopy (tab, 0, ntab, 0, tab.length);
+                    tab = ntab;
+                }
+            }
+            // get last key/val, if any
+            if (--end > beg) {
+                if (!inKey) {
+                    if (ca[end] == '\"') {
+                        tab[i++][1] = (new String(ca, beg, end-beg));
+                    } else {
+                        tab[i++][1] = (new String(ca, beg, end-beg+1));
+                    }
+                } else {
+                    tab[i++][0] = (new String(ca, beg, end-beg+1)).toLowerCase();
+                }
+            } else if (end == beg) {
+                if (!inKey) {
+                    if (ca[end] == '\"') {
+                        tab[i++][1] = String.valueOf(ca[end-1]);
+                    } else {
+                        tab[i++][1] = String.valueOf(ca[end]);
+                    }
+                } else {
+                    tab[i++][0] = String.valueOf(ca[end]).toLowerCase();
+                }
+            }
+            nkeys=i;
+        }
+    }
+
+    public String findKey(int i) {
+        if (i < 0 || i > asize) {
+            return null;
+        }
+        return tab[i][0];
+    }
+
+    public String findValue(int i) {
+        if (i < 0 || i > asize) {
+            return null;
+        }
+        return tab[i][1];
+    }
+
+    public String findValue(String key) {
+        return findValue(key, null);
+    }
+
+    public String findValue(String k, String Default) {
+        if (k == null) {
+            return Default;
+        }
+        k = k.toLowerCase(Locale.US);
+        for (int i = 0; i < asize; ++i) {
+            if (tab[i][0] == null) {
+                return Default;
+            } else if (k.equals(tab[i][0])) {
+                return tab[i][1];
+            }
+        }
+        return Default;
+    }
+
+    class ParserIterator implements Iterator<String> {
+        int index;
+        boolean returnsValue; // or key
+
+        ParserIterator (boolean returnValue) {
+            returnsValue = returnValue;
+        }
+        @Override
+        public boolean hasNext () {
+            return index<nkeys;
+        }
+        @Override
+        public String next () {
+            if (index >= nkeys) {
+                throw new NoSuchElementException();
+            }
+            return tab[index++][returnsValue?1:0];
+        }
+    }
+
+    public Iterator<String> keys () {
+        return new ParserIterator (false);
+    }
+
+//    public Iterator<String> values () {
+//        return new ParserIterator (true);
+//    }
+
+    @Override
+    public String toString () {
+        Iterator<String> k = keys();
+        StringBuilder sb = new StringBuilder();
+        sb.append("{size=").append(asize).append(" nkeys=").append(nkeys)
+                .append(' ');
+        for (int i=0; k.hasNext(); i++) {
+            String key = k.next();
+            String val = findValue (i);
+            if (val != null && "".equals (val)) {
+                val = null;
+            }
+            sb.append(" {").append(key).append(val == null ? "" : "," + val)
+                    .append('}');
+            if (k.hasNext()) {
+                sb.append (',');
+            }
+        }
+        sb.append (" }");
+        return sb.toString();
+    }
+
+//    public int findInt(String k, int Default) {
+//        try {
+//            return Integer.parseInt(findValue(k, String.valueOf(Default)));
+//        } catch (Throwable t) {
+//            return Default;
+//        }
+//    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.FlowTube.TubeSubscriber;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.ConnectionExpiredException;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.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.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Utils;
+import static java.net.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/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.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/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import jdk.internal.net.http.Http1Exchange.Http1BodySubscriber;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,525 @@
+/*
+ * 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.internal.net.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 java.net.http.HttpHeaders;
+import java.net.http.HttpResponse;
+import jdk.internal.net.http.ResponseContent.BodyParser;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Utils;
+import static java.net.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(), 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 CompletableFuture<U> cf = new MinimalFuture<>();
+
+        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);
+                }
+            }
+        });
+        p.getBody().whenComplete((U u, Throwable t) -> {
+            if (t == null)
+                cf.complete(u);
+            else
+                cf.completeExceptionally(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/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.Log;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.frame.SettingsFrame;
+import static jdk.internal.net.http.frame.SettingsFrame.INITIAL_WINDOW_SIZE;
+import static jdk.internal.net.http.frame.SettingsFrame.ENABLE_PUSH;
+import static jdk.internal.net.http.frame.SettingsFrame.HEADER_TABLE_SIZE;
+import static jdk.internal.net.http.frame.SettingsFrame.MAX_CONCURRENT_STREAMS;
+import static jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import jdk.internal.net.http.HttpConnection.HttpPublisher;
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.FlowTube.TubeSubscriber;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.frame.ContinuationFrame;
+import jdk.internal.net.http.frame.DataFrame;
+import jdk.internal.net.http.frame.ErrorFrame;
+import jdk.internal.net.http.frame.FramesDecoder;
+import jdk.internal.net.http.frame.FramesEncoder;
+import jdk.internal.net.http.frame.GoAwayFrame;
+import jdk.internal.net.http.frame.HeaderFrame;
+import jdk.internal.net.http.frame.HeadersFrame;
+import jdk.internal.net.http.frame.Http2Frame;
+import jdk.internal.net.http.frame.MalformedFrame;
+import jdk.internal.net.http.frame.OutgoingHeaders;
+import jdk.internal.net.http.frame.PingFrame;
+import jdk.internal.net.http.frame.PushPromiseFrame;
+import jdk.internal.net.http.frame.ResetFrame;
+import jdk.internal.net.http.frame.SettingsFrame;
+import jdk.internal.net.http.frame.WindowUpdateFrame;
+import jdk.internal.net.http.hpack.Encoder;
+import jdk.internal.net.http.hpack.Decoder;
+import jdk.internal.net.http.hpack.DecodingCallback;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/HttpClientBuilderImpl.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/HttpClientFacade.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+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 java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.net.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/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,1023 @@
+/*
+ * 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.net.http.WebSocket;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.Pair;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.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);
+                if (key != null) {
+                    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
+                try {
+                    chan.register(selector, interestOps, this);
+                } catch (CancelledKeyException x) {
+                    abortPending(x);
+                }
+            }
+        }
+
+        /**
+         * 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/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpHeaders;
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.SequentialScheduler.DeferredCompleter;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.Utils;
+import static java.net.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/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.Optional;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublisher;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.Utils;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.common.Utils.isValidName;
+import static jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+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 java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.websocket.WebSocketRequest;
+
+import static jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/HttpResponseImpl.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/ImmutableHeaders.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.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/java.net.http/share/classes/jdk/internal/net/http/LineSubscriberAdapter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import jdk.internal.net.http.common.Demand;
+import java.net.http.HttpResponse.BodySubscriber;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.SequentialScheduler;
+
+/** An adapter between {@code BodySubscriber} and {@code Flow.Subscriber<String>}. */
+public final class LineSubscriberAdapter<S extends Subscriber<? super String>,R>
+        implements BodySubscriber<R> {
+    private final CompletableFuture<R> cf = new MinimalFuture<>();
+    private final S subscriber;
+    private final Function<S, R> finisher;
+    private final Charset charset;
+    private final String eol;
+    private volatile LineSubscription downstream;
+
+    private LineSubscriberAdapter(S subscriber,
+                                  Function<S, R> finisher,
+                                  Charset charset,
+                                  String eol) {
+        if (eol != null && eol.isEmpty())
+            throw new IllegalArgumentException("empty line separator");
+        this.subscriber = Objects.requireNonNull(subscriber);
+        this.finisher = Objects.requireNonNull(finisher);
+        this.charset = Objects.requireNonNull(charset);
+        this.eol = eol;
+    }
+
+    @Override
+    public void onSubscribe(Subscription subscription) {
+        downstream = LineSubscription.create(subscription,
+                                             charset,
+                                             eol,
+                                             subscriber,
+                                             cf);
+        subscriber.onSubscribe(downstream);
+    }
+
+    @Override
+    public void onNext(List<ByteBuffer> item) {
+        try {
+            downstream.submit(item);
+        } catch (Throwable t) {
+            onError(t);
+        }
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        try {
+            downstream.signalError(throwable);
+        } finally {
+            cf.completeExceptionally(throwable);
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        try {
+            downstream.signalComplete();
+        } finally {
+            cf.complete(finisher.apply(subscriber));
+        }
+    }
+
+    @Override
+    public CompletionStage<R> getBody() {
+        return cf;
+    }
+
+    public static <S extends Subscriber<? super String>, R> LineSubscriberAdapter<S, R>
+    create(S subscriber, Function<S, R> finisher, Charset charset, String eol)
+    {
+        if (eol != null && eol.isEmpty())
+            throw new IllegalArgumentException("empty line separator");
+        return new LineSubscriberAdapter<>(Objects.requireNonNull(subscriber),
+                Objects.requireNonNull(finisher),
+                Objects.requireNonNull(charset),
+                eol);
+    }
+
+    static final class LineSubscription implements Flow.Subscription {
+        final Flow.Subscription upstreamSubscription;
+        final CharsetDecoder decoder;
+        final String newline;
+        final Demand downstreamDemand;
+        final ConcurrentLinkedDeque<ByteBuffer> queue;
+        final SequentialScheduler scheduler;
+        final Flow.Subscriber<? super String> upstream;
+        final CompletableFuture<?> cf;
+        private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        private final AtomicLong demanded = new AtomicLong();
+        private volatile boolean completed;
+        private volatile boolean cancelled;
+
+        private final char[] chars = new char[1024];
+        private final ByteBuffer leftover = ByteBuffer.wrap(new byte[64]);
+        private final CharBuffer buffer = CharBuffer.wrap(chars);
+        private final StringBuilder builder = new StringBuilder();
+        private int lineCount;
+        private String nextLine;
+
+        private LineSubscription(Flow.Subscription s,
+                                 CharsetDecoder dec,
+                                 String separator,
+                                 Flow.Subscriber<? super String> subscriber,
+                                 CompletableFuture<?> completion) {
+            downstreamDemand = new Demand();
+            queue = new ConcurrentLinkedDeque<>();
+            upstreamSubscription = Objects.requireNonNull(s);
+            decoder = Objects.requireNonNull(dec);
+            newline = separator;
+            upstream = Objects.requireNonNull(subscriber);
+            cf = Objects.requireNonNull(completion);
+            scheduler = SequentialScheduler.synchronizedScheduler(this::loop);
+        }
+
+        @Override
+        public void request(long n) {
+            if (cancelled) return;
+            if (downstreamDemand.increase(n)) {
+                scheduler.runOrSchedule();
+            }
+        }
+
+        @Override
+        public void cancel() {
+            cancelled = true;
+            upstreamSubscription.cancel();
+        }
+
+        public void submit(List<ByteBuffer> list) {
+            queue.addAll(list);
+            demanded.decrementAndGet();
+            scheduler.runOrSchedule();
+        }
+
+        public void signalComplete() {
+            completed = true;
+            scheduler.runOrSchedule();
+        }
+
+        public void signalError(Throwable error) {
+            if (errorRef.compareAndSet(null,
+                    Objects.requireNonNull(error))) {
+                scheduler.runOrSchedule();
+            }
+        }
+
+        // This method looks at whether some bytes where left over (in leftover)
+        // from decoding the previous buffer when the previous buffer was in
+        // underflow. If so, it takes bytes one by one from the new buffer 'in'
+        // and combines them with the leftover bytes until 'in' is exhausted or a
+        // character was produced in 'out', resolving the previous underflow.
+        // Returns true if the buffer is still in underflow, false otherwise.
+        // However, in both situation some chars might have been produced in 'out'.
+        private boolean isUnderFlow(ByteBuffer in, CharBuffer out, boolean endOfInput)
+                throws CharacterCodingException {
+            int limit = leftover.position();
+            if (limit == 0) {
+                // no leftover
+                return false;
+            } else {
+                CoderResult res = null;
+                while (in.hasRemaining()) {
+                    leftover.position(limit);
+                    leftover.limit(++limit);
+                    leftover.put(in.get());
+                    leftover.position(0);
+                    res = decoder.decode(leftover, out,
+                            endOfInput && !in.hasRemaining());
+                    int remaining = leftover.remaining();
+                    if (remaining > 0) {
+                        assert leftover.position() == 0;
+                        leftover.position(remaining);
+                    } else {
+                        leftover.position(0);
+                    }
+                    leftover.limit(leftover.capacity());
+                    if (res.isUnderflow() && remaining > 0 && in.hasRemaining()) {
+                        continue;
+                    }
+                    if (res.isError()) {
+                        res.throwException();
+                    }
+                    assert !res.isOverflow();
+                    return false;
+                }
+                return !endOfInput;
+            }
+        }
+
+        // extract characters from start to end and remove them from
+        // the StringBuilder
+        private static String take(StringBuilder b, int start, int end) {
+            assert start == 0;
+            String line;
+            if (end == start) return "";
+            line = b.substring(start, end);
+            b.delete(start, end);
+            return line;
+        }
+
+        // finds end of line, returns -1 if not found, or the position after
+        // the line delimiter if found, removing the delimiter in the process.
+        private static int endOfLine(StringBuilder b, String eol, boolean endOfInput) {
+            int len = b.length();
+            if (eol != null) { // delimiter explicitly specified
+                int i = b.indexOf(eol);
+                if (i >= 0) {
+                    // remove the delimiter and returns the position
+                    // of the char after it.
+                    b.delete(i, i + eol.length());
+                    return i;
+                }
+            } else { // no delimiter specified, behaves as BufferedReader::readLine
+                boolean crfound = false;
+                for (int i = 0; i < len; i++) {
+                    char c = b.charAt(i);
+                    if (c == '\n') {
+                        // '\n' or '\r\n' found.
+                        // remove the delimiter and returns the position
+                        // of the char after it.
+                        b.delete(crfound ? i - 1 : i, i + 1);
+                        return crfound ? i - 1 : i;
+                    } else if (crfound) {
+                        // previous char was '\r', c != '\n'
+                        assert i != 0;
+                        // remove the delimiter and returns the position
+                        // of the char after it.
+                        b.delete(i - 1, i);
+                        return i - 1;
+                    }
+                    crfound = c == '\r';
+                }
+                if (crfound && endOfInput) {
+                    // remove the delimiter and returns the position
+                    // of the char after it.
+                    b.delete(len - 1, len);
+                    return len - 1;
+                }
+            }
+            return endOfInput && len > 0 ? len : -1;
+        }
+
+        // Looks at whether the StringBuilder contains a line.
+        // Returns null if more character are needed.
+        private static String nextLine(StringBuilder b, String eol, boolean endOfInput) {
+            int next = endOfLine(b, eol, endOfInput);
+            return (next > -1) ? take(b, 0, next) : null;
+        }
+
+        // Attempts to read the next line. Returns the next line if
+        // the delimiter was found, null otherwise. The delimiters are
+        // consumed.
+        private String nextLine()
+                throws CharacterCodingException {
+            assert nextLine == null;
+            LINES:
+            while (nextLine == null) {
+                boolean endOfInput = completed && queue.isEmpty();
+                nextLine = nextLine(builder, newline,
+                        endOfInput && leftover.position() == 0);
+                if (nextLine != null) return nextLine;
+                ByteBuffer b;
+                BUFFERS:
+                while ((b = queue.peek()) != null) {
+                    if (!b.hasRemaining()) {
+                        queue.poll();
+                        continue BUFFERS;
+                    }
+                    BYTES:
+                    while (b.hasRemaining()) {
+                        buffer.position(0);
+                        buffer.limit(buffer.capacity());
+                        boolean endofInput = completed && queue.size() <= 1;
+                        if (isUnderFlow(b, buffer, endofInput)) {
+                            assert !b.hasRemaining();
+                            if (buffer.position() > 0) {
+                                buffer.flip();
+                                builder.append(buffer);
+                            }
+                            continue BUFFERS;
+                        }
+                        CoderResult res = decoder.decode(b, buffer, endofInput);
+                        if (res.isError()) res.throwException();
+                        if (buffer.position() > 0) {
+                            buffer.flip();
+                            builder.append(buffer);
+                            continue LINES;
+                        }
+                        if (res.isUnderflow() && b.hasRemaining()) {
+                            //System.out.println("underflow: adding " + b.remaining() + " bytes");
+                            leftover.put(b);
+                            assert !b.hasRemaining();
+                            continue BUFFERS;
+                        }
+                    }
+                }
+
+                assert queue.isEmpty();
+                if (endOfInput) {
+                    // Time to cleanup: there may be some undecoded leftover bytes
+                    // We need to flush them out.
+                    // The decoder has been configured to replace malformed/unmappable
+                    // chars with some replacement, in order to behave like
+                    // InputStreamReader.
+                    leftover.flip();
+                    buffer.position(0);
+                    buffer.limit(buffer.capacity());
+
+                    // decode() must be called just before flush, even if there
+                    // is nothing to decode. We must do this even if leftover
+                    // has no remaining bytes.
+                    CoderResult res = decoder.decode(leftover, buffer, endOfInput);
+                    if (buffer.position() > 0) {
+                        buffer.flip();
+                        builder.append(buffer);
+                    }
+                    if (res.isError()) res.throwException();
+
+                    // Now call decoder.flush()
+                    buffer.position(0);
+                    buffer.limit(buffer.capacity());
+                    res = decoder.flush(buffer);
+                    if (buffer.position() > 0) {
+                        buffer.flip();
+                        builder.append(buffer);
+                    }
+                    if (res.isError()) res.throwException();
+
+                    // It's possible that we reach here twice - just for the
+                    // purpose of checking that no bytes were left over, so
+                    // we reset leftover/decoder to make the function reentrant.
+                    leftover.position(0);
+                    leftover.limit(leftover.capacity());
+                    decoder.reset();
+
+                    // if some chars were produced then this call will
+                    // return them.
+                    return nextLine = nextLine(builder, newline, endOfInput);
+                }
+                return null;
+            }
+            return null;
+        }
+
+        // The main sequential scheduler loop.
+        private void loop() {
+            try {
+                while (!cancelled) {
+                    Throwable error = errorRef.get();
+                    if (error != null) {
+                        cancelled = true;
+                        scheduler.stop();
+                        upstream.onError(error);
+                        cf.completeExceptionally(error);
+                        return;
+                    }
+                    if (nextLine == null) nextLine = nextLine();
+                    if (nextLine == null) {
+                        if (completed) {
+                            scheduler.stop();
+                            if (leftover.position() != 0) {
+                                // Underflow: not all bytes could be
+                                // decoded, but no more bytes will be coming.
+                                // This should not happen as we should already
+                                // have got a MalformedInputException, or
+                                // replaced the unmappable chars.
+                                errorRef.compareAndSet(null,
+                                        new IllegalStateException(
+                                                "premature end of input ("
+                                                        + leftover.position()
+                                                        + " undecoded bytes)"));
+                                continue;
+                            } else {
+                                upstream.onComplete();
+                            }
+                            return;
+                        } else if (demanded.get() == 0
+                                && !downstreamDemand.isFulfilled()) {
+                            long incr = Math.max(1, downstreamDemand.get());
+                            demanded.addAndGet(incr);
+                            upstreamSubscription.request(incr);
+                            continue;
+                        } else return;
+                    }
+                    assert nextLine != null;
+                    assert newline != null && !nextLine.endsWith(newline)
+                            || !nextLine.endsWith("\n") || !nextLine.endsWith("\r");
+                    if (downstreamDemand.tryDecrement()) {
+                        String forward = nextLine;
+                        nextLine = null;
+                        upstream.onNext(forward);
+                    } else return; // no demand: come back later
+                }
+            } catch (Throwable t) {
+                try {
+                    upstreamSubscription.cancel();
+                } finally {
+                    signalError(t);
+                }
+            }
+        }
+
+        static LineSubscription create(Flow.Subscription s,
+                                       Charset charset,
+                                       String lineSeparator,
+                                       Flow.Subscriber<? super String> upstream,
+                                       CompletableFuture<?> cf) {
+            return new LineSubscription(Objects.requireNonNull(s),
+                    Objects.requireNonNull(charset).newDecoder()
+                            // use the same decoder configuration than
+                            // java.io.InputStreamReader
+                            .onMalformedInput(CodingErrorAction.REPLACE)
+                            .onUnmappableCharacter(CodingErrorAction.REPLACE),
+                    lineSeparator,
+                    Objects.requireNonNull(upstream),
+                    Objects.requireNonNull(cf));
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.net.http.HttpTimeoutException;
+import jdk.internal.net.http.UntrustedBodyHandler;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.ConnectionExpiredException;
+import jdk.internal.net.http.common.Utils;
+import static jdk.internal.net.http.common.MinimalFuture.completedFuture;
+import static jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/PlainProxyConnection.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.net.InetSocketAddress;
+
+class PlainProxyConnection extends PlainHttpConnection {
+
+    PlainProxyConnection(InetSocketAddress proxy, HttpClientImpl client) {
+        super(proxy, client);
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return new ConnectionPool.CacheKey(null, address);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PlainTunnelingConnection.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpHeaders;
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.MinimalFuture;
+import static java.net.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);
+                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/java.net.http/share/classes/jdk/internal/net/http/PrivilegedExecutor.java	Wed Feb 07 21:45:37 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.internal.net.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));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ProxyAuthenticationRequired.java	Wed Feb 07 21:45:37 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.internal.net.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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PullPublisher.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.util.Iterator;
+import java.util.concurrent.Flow;
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/PushGroup.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/RawChannelImpl.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,377 @@
+/*
+ * 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.internal.net.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 java.net.http.HttpRequest.BodyPublisher;
+import jdk.internal.net.http.common.Utils;
+
+public final class RequestPublishers {
+
+    private 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/java.net.http/share/classes/jdk/internal/net/http/Response.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.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/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http;
+
+import java.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 java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodySubscriber;
+import jdk.internal.net.http.ResponseSubscribers.PathSubscriber;
+import static jdk.internal.net.http.common.Utils.unchecked;
+
+public final class ResponseBodyHandlers {
+
+    private 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/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpHeaders;
+import java.net.http.HttpResponse;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,650 @@
+/*
+ * 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.internal.net.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.net.http.HttpResponse.BodySubscriber;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Utils;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class ResponseSubscribers {
+
+    public static class ConsumerSubscriber implements BodySubscriber<Void> {
+        private final Consumer<Optional<byte[]>> consumer;
+        private Flow.Subscription subscription;
+        private final CompletableFuture<Void> result = new MinimalFuture<>();
+        private final AtomicBoolean subscribed = new AtomicBoolean();
+
+        public ConsumerSubscriber(Consumer<Optional<byte[]>> consumer) {
+            this.consumer = Objects.requireNonNull(consumer);
+        }
+
+        @Override
+        public CompletionStage<Void> getBody() {
+            return result;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (!subscribed.compareAndSet(false, true)) {
+                subscription.cancel();
+            } else {
+                this.subscription = subscription;
+                subscription.request(1);
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> items) {
+            for (ByteBuffer item : items) {
+                byte[] buf = new byte[item.remaining()];
+                item.get(buf);
+                consumer.accept(Optional.of(buf));
+            }
+            subscription.request(1);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            result.completeExceptionally(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            consumer.accept(Optional.empty());
+            result.complete(null);
+        }
+
+    }
+
+    public static class PathSubscriber implements BodySubscriber<Path> {
+
+        private final Path file;
+        private final CompletableFuture<Path> result = new MinimalFuture<>();
+
+        private volatile Flow.Subscription subscription;
+        private volatile FileChannel out;
+        private volatile AccessControlContext acc;
+        private final OpenOption[] options;
+
+        public PathSubscriber(Path file, OpenOption... options) {
+            this.file = file;
+            this.options = options;
+        }
+
+        public void setAccessControlContext(AccessControlContext acc) {
+            this.acc = acc;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (System.getSecurityManager() != null && acc == null)
+                throw new InternalError(
+                        "Unexpected null acc when security manager has been installed");
+
+            this.subscription = subscription;
+            try {
+                PrivilegedExceptionAction<FileChannel> pa =
+                        () -> FileChannel.open(file, options);
+                out = AccessController.doPrivileged(pa, acc);
+            } catch (PrivilegedActionException pae) {
+                Throwable t = pae.getCause() != null ? pae.getCause() : pae;
+                result.completeExceptionally(t);
+                subscription.cancel();
+                return;
+            }
+            subscription.request(1);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> items) {
+            try {
+                out.write(items.toArray(Utils.EMPTY_BB_ARRAY));
+            } catch (IOException ex) {
+                Utils.close(out);
+                subscription.cancel();
+                result.completeExceptionally(ex);
+            }
+            subscription.request(1);
+        }
+
+        @Override
+        public void onError(Throwable e) {
+            result.completeExceptionally(e);
+            Utils.close(out);
+        }
+
+        @Override
+        public void onComplete() {
+            Utils.close(out);
+            result.complete(file);
+        }
+
+        @Override
+        public CompletionStage<Path> getBody() {
+            return result;
+        }
+    }
+
+    public static class ByteArraySubscriber<T> implements BodySubscriber<T> {
+        private final Function<byte[], T> finisher;
+        private final CompletableFuture<T> result = new MinimalFuture<>();
+        private final List<ByteBuffer> received = new ArrayList<>();
+
+        private volatile Flow.Subscription subscription;
+
+        public ByteArraySubscriber(Function<byte[],T> finisher) {
+            this.finisher = finisher;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (this.subscription != null) {
+                subscription.cancel();
+                return;
+            }
+            this.subscription = subscription;
+            // We can handle whatever you've got
+            subscription.request(Long.MAX_VALUE);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> items) {
+            // incoming buffers are allocated by http client internally,
+            // and won't be used anywhere except this place.
+            // So it's free simply to store them for further processing.
+            assert Utils.hasRemaining(items);
+            received.addAll(items);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            received.clear();
+            result.completeExceptionally(throwable);
+        }
+
+        static private byte[] join(List<ByteBuffer> bytes) {
+            int size = Utils.remaining(bytes, Integer.MAX_VALUE);
+            byte[] res = new byte[size];
+            int from = 0;
+            for (ByteBuffer b : bytes) {
+                int l = b.remaining();
+                b.get(res, from, l);
+                from += l;
+            }
+            return res;
+        }
+
+        @Override
+        public void onComplete() {
+            try {
+                result.complete(finisher.apply(join(received)));
+                received.clear();
+            } catch (IllegalArgumentException e) {
+                result.completeExceptionally(e);
+            }
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            return result;
+        }
+    }
+
+    /**
+     * An InputStream built on top of the Flow API.
+     */
+    public static class HttpResponseInputStream extends InputStream
+        implements BodySubscriber<InputStream>
+    {
+        final static boolean DEBUG = Utils.DEBUG;
+        final static int MAX_BUFFERS_IN_QUEUE = 1;  // lock-step with the producer
+
+        // An immutable ByteBuffer sentinel to mark that the last byte was received.
+        private static final ByteBuffer LAST_BUFFER = ByteBuffer.wrap(new byte[0]);
+        private static final List<ByteBuffer> LAST_LIST = List.of(LAST_BUFFER);
+        private static final System.Logger DEBUG_LOGGER =
+                Utils.getDebugLogger("HttpResponseInputStream"::toString, DEBUG);
+
+        // A queue of yet unprocessed ByteBuffers received from the flow API.
+        private final BlockingQueue<List<ByteBuffer>> buffers;
+        private volatile Flow.Subscription subscription;
+        private volatile boolean closed;
+        private volatile Throwable failed;
+        private volatile Iterator<ByteBuffer> currentListItr;
+        private volatile ByteBuffer currentBuffer;
+        private final AtomicBoolean subscribed = new AtomicBoolean();
+
+        public HttpResponseInputStream() {
+            this(MAX_BUFFERS_IN_QUEUE);
+        }
+
+        HttpResponseInputStream(int maxBuffers) {
+            int capacity = (maxBuffers <= 0 ? MAX_BUFFERS_IN_QUEUE : maxBuffers);
+            // 1 additional slot needed for LAST_LIST added by onComplete
+            this.buffers = new ArrayBlockingQueue<>(capacity + 1);
+        }
+
+        @Override
+        public CompletionStage<InputStream> getBody() {
+            // Returns the stream immediately, before the
+            // response body is received.
+            // This makes it possible for sendAsync().get().body()
+            // to complete before the response body is received.
+            return CompletableFuture.completedStage(this);
+        }
+
+        // Returns the current byte buffer to read from.
+        // If the current buffer has no remaining data, this method will take the
+        // next buffer from the buffers queue, possibly blocking until
+        // a new buffer is made available through the Flow API, or the
+        // end of the flow has been reached.
+        private ByteBuffer current() throws IOException {
+            while (currentBuffer == null || !currentBuffer.hasRemaining()) {
+                // Check whether the stream is closed or exhausted
+                if (closed || failed != null) {
+                    throw new IOException("closed", failed);
+                }
+                if (currentBuffer == LAST_BUFFER) break;
+
+                try {
+                    if (currentListItr == null || !currentListItr.hasNext()) {
+                        // Take a new list of buffers from the queue, blocking
+                        // if none is available yet...
+
+                        DEBUG_LOGGER.log(Level.DEBUG, "Taking list of Buffers");
+                        List<ByteBuffer> lb = buffers.take();
+                        currentListItr = lb.iterator();
+                        DEBUG_LOGGER.log(Level.DEBUG, "List of Buffers Taken");
+
+                        // Check whether an exception was encountered upstream
+                        if (closed || failed != null)
+                            throw new IOException("closed", failed);
+
+                        // Check whether we're done.
+                        if (lb == LAST_LIST) {
+                            currentListItr = null;
+                            currentBuffer = LAST_BUFFER;
+                            break;
+                        }
+
+                        // Request another upstream item ( list of buffers )
+                        Flow.Subscription s = subscription;
+                        if (s != null) {
+                            DEBUG_LOGGER.log(Level.DEBUG, "Increased demand by 1");
+                            s.request(1);
+                        }
+                        assert currentListItr != null;
+                        if (lb.isEmpty()) continue;
+                    }
+                    assert currentListItr != null;
+                    assert currentListItr.hasNext();
+                    DEBUG_LOGGER.log(Level.DEBUG, "Next Buffer");
+                    currentBuffer = currentListItr.next();
+                } catch (InterruptedException ex) {
+                    // continue
+                }
+            }
+            assert currentBuffer == LAST_BUFFER || currentBuffer.hasRemaining();
+            return currentBuffer;
+        }
+
+        @Override
+        public int read(byte[] bytes, int off, int len) throws IOException {
+            // get the buffer to read from, possibly blocking if
+            // none is available
+            ByteBuffer buffer;
+            if ((buffer = current()) == LAST_BUFFER) return -1;
+
+            // don't attempt to read more than what is available
+            // in the current buffer.
+            int read = Math.min(buffer.remaining(), len);
+            assert read > 0 && read <= buffer.remaining();
+
+            // buffer.get() will do the boundary check for us.
+            buffer.get(bytes, off, read);
+            return read;
+        }
+
+        @Override
+        public int read() throws IOException {
+            ByteBuffer buffer;
+            if ((buffer = current()) == LAST_BUFFER) return -1;
+            return buffer.get() & 0xFF;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription s) {
+            try {
+                if (!subscribed.compareAndSet(false, true)) {
+                    s.cancel();
+                } else {
+                    // check whether the stream is already closed.
+                    // if so, we should cancel the subscription
+                    // immediately.
+                    boolean closed;
+                    synchronized (this) {
+                        closed = this.closed;
+                        if (!closed) {
+                            this.subscription = s;
+                        }
+                    }
+                    if (closed) {
+                        s.cancel();
+                        return;
+                    }
+                    assert buffers.remainingCapacity() > 1; // should contain at least 2
+                    DEBUG_LOGGER.log(Level.DEBUG, () -> "onSubscribe: requesting "
+                            + Math.max(1, buffers.remainingCapacity() - 1));
+                    s.request(Math.max(1, buffers.remainingCapacity() - 1));
+                }
+            } catch (Throwable t) {
+                failed = t;
+                try {
+                    close();
+                } catch (IOException x) {
+                    // OK
+                } finally {
+                    onError(t);
+                }
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> t) {
+            Objects.requireNonNull(t);
+            try {
+                DEBUG_LOGGER.log(Level.DEBUG, "next item received");
+                if (!buffers.offer(t)) {
+                    throw new IllegalStateException("queue is full");
+                }
+                DEBUG_LOGGER.log(Level.DEBUG, "item offered");
+            } catch (Throwable ex) {
+                failed = ex;
+                try {
+                    close();
+                } catch (IOException ex1) {
+                    // OK
+                } finally {
+                    onError(ex);
+                }
+            }
+        }
+
+        @Override
+        public void onError(Throwable thrwbl) {
+            subscription = null;
+            failed = Objects.requireNonNull(thrwbl);
+            // The client process that reads the input stream might
+            // be blocked in queue.take().
+            // Tries to offer LAST_LIST to the queue. If the queue is
+            // full we don't care if we can't insert this buffer, as
+            // the client can't be blocked in queue.take() in that case.
+            // Adding LAST_LIST to the queue is harmless, as the client
+            // should find failed != null before handling LAST_LIST.
+            buffers.offer(LAST_LIST);
+        }
+
+        @Override
+        public void onComplete() {
+            subscription = null;
+            onNext(LAST_LIST);
+        }
+
+        @Override
+        public void close() throws IOException {
+            Flow.Subscription s;
+            synchronized (this) {
+                if (closed) return;
+                closed = true;
+                s = subscription;
+                subscription = null;
+            }
+            // s will be null if already completed
+            try {
+                if (s != null) {
+                    s.cancel();
+                }
+            } finally {
+                buffers.offer(LAST_LIST);
+                super.close();
+            }
+        }
+
+    }
+
+    public static BodySubscriber<Stream<String>> createLineStream() {
+        return createLineStream(UTF_8);
+    }
+
+    public static BodySubscriber<Stream<String>> createLineStream(Charset charset) {
+        Objects.requireNonNull(charset);
+        BodySubscriber<InputStream> s = new HttpResponseInputStream();
+        return new MappedSubscriber<InputStream,Stream<String>>(s,
+            (InputStream stream) -> {
+                return new BufferedReader(new InputStreamReader(stream, charset))
+                            .lines().onClose(() -> Utils.close(stream));
+            });
+    }
+
+    /**
+     * Currently this consumes all of the data and ignores it
+     */
+    public static class NullSubscriber<T> implements BodySubscriber<T> {
+
+        private final CompletableFuture<T> cf = new MinimalFuture<>();
+        private final Optional<T> result;
+        private final AtomicBoolean subscribed = new AtomicBoolean();
+
+        public NullSubscriber(Optional<T> result) {
+            this.result = result;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (!subscribed.compareAndSet(false, true)) {
+                subscription.cancel();
+            } else {
+                subscription.request(Long.MAX_VALUE);
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> items) {
+            Objects.requireNonNull(items);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            cf.completeExceptionally(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            if (result.isPresent()) {
+                cf.complete(result.get());
+            } else {
+                cf.complete(null);
+            }
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            return cf;
+        }
+    }
+
+    /** An adapter between {@code BodySubscriber} and {@code Flow.Subscriber}. */
+    public static final class SubscriberAdapter<S extends Subscriber<? super List<ByteBuffer>>,R>
+        implements BodySubscriber<R>
+    {
+        private final CompletableFuture<R> cf = new MinimalFuture<>();
+        private final S subscriber;
+        private final Function<S,R> finisher;
+        private volatile Subscription subscription;
+
+        public SubscriberAdapter(S subscriber, Function<S,R> finisher) {
+            this.subscriber = Objects.requireNonNull(subscriber);
+            this.finisher = Objects.requireNonNull(finisher);
+        }
+
+        @Override
+        public void onSubscribe(Subscription subscription) {
+            Objects.requireNonNull(subscription);
+            if (this.subscription != null) {
+                subscription.cancel();
+            } else {
+                this.subscription = subscription;
+                subscriber.onSubscribe(subscription);
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            Objects.requireNonNull(item);
+            try {
+                subscriber.onNext(item);
+            } catch (Throwable throwable) {
+                subscription.cancel();
+                onError(throwable);
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            Objects.requireNonNull(throwable);
+            try {
+                subscriber.onError(throwable);
+            } finally {
+                cf.completeExceptionally(throwable);
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            try {
+                subscriber.onComplete();
+            } finally {
+                try {
+                    cf.complete(finisher.apply(subscriber));
+                } catch (Throwable throwable) {
+                    cf.completeExceptionally(throwable);
+                }
+            }
+        }
+
+        @Override
+        public CompletionStage<R> getBody() {
+            return cf;
+        }
+    }
+
+    /**
+     * A body subscriber which receives input from an upstream subscriber
+     * and maps that subscriber's body type to a new type. The upstream subscriber
+     * delegates all flow operations directly to this object. The
+     * {@link CompletionStage} returned by {@link #getBody()}} takes the output
+     * of the upstream {@code getBody()} and applies the mapper function to
+     * obtain the new {@code CompletionStage} type.
+     *
+     * Uses an Executor that must be set externally.
+     *
+     * @param <T> the upstream body type
+     * @param <U> this subscriber's body type
+     */
+    public static class MappedSubscriber<T,U> implements BodySubscriber<U> {
+        final BodySubscriber<T> upstream;
+        final Function<T,U> mapper;
+
+        /**
+         *
+         * @param upstream
+         * @param mapper
+         */
+        public MappedSubscriber(BodySubscriber<T> upstream, Function<T,U> mapper) {
+            this.upstream = upstream;
+            this.mapper = mapper;
+        }
+
+        @Override
+        public CompletionStage<U> getBody() {
+            return upstream.getBody()
+                    .thenApply(mapper);
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            upstream.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            upstream.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            upstream.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            upstream.onComplete();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/SSLDelegate.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.Log;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.SequentialScheduler.DeferredCompleter;
+import jdk.internal.net.http.common.SequentialScheduler.RestartableTask;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,1180 @@
+/*
+ * 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodySubscriber;
+import jdk.internal.net.http.common.*;
+import jdk.internal.net.http.frame.*;
+import jdk.internal.net.http.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, executor);
+
+        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, Executor executor) {
+        responseBodyCF = new MinimalFuture<>();
+        // We want to allow the subscriber's getBody() method to block so it
+        // can work with InputStreams. So, we offload execution.
+        executor.execute(() -> {
+            bodySubscriber.getBody().whenComplete((T body, Throwable t) -> {
+                if (t == null)
+                    responseBodyCF.complete(body);
+                else
+                    responseBodyCF.completeExceptionally(t);
+            });
+        });
+
+        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/java.net.http/share/classes/jdk/internal/net/http/TimeoutEvent.java	Wed Feb 07 21:45:37 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.internal.net.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 + "]";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/UntrustedBodyHandler.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.security.AccessControlContext;
+import java.net.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/java.net.http/share/classes/jdk/internal/net/http/WindowController.java	Wed Feb 07 21:45:37 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.internal.net.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.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/WindowUpdateSender.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+import java.lang.System.Logger.Level;
+import jdk.internal.net.http.frame.SettingsFrame;
+import jdk.internal.net.http.frame.WindowUpdateFrame;
+import jdk.internal.net.http.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/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferPool.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,60 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * The class provides reuse of ByteBuffers.
+ * It is supposed that all requested buffers have the same size for a long period of time.
+ * That is why there is no any logic splitting buffers into different buckets (by size). It's unnecessary.
+ *
+ * At the same moment it is allowed to change requested buffers size (all smaller buffers will be discarded).
+ * It may be needed for example, if after rehandshaking netPacketBufferSize was changed.
+ */
+public class ByteBufferPool {
+
+    private final java.util.Queue<ByteBuffer> pool = new ConcurrentLinkedQueue<>();
+
+    public ByteBufferPool() {
+    }
+
+    public ByteBufferReference get(int size) {
+        ByteBuffer buffer;
+        while ((buffer = pool.poll()) != null) {
+            if (buffer.capacity() >= size) {
+                return ByteBufferReference.of(buffer, this);
+            }
+        }
+        return ByteBufferReference.of(ByteBuffer.allocate(size), this);
+    }
+
+    public void release(ByteBuffer buffer) {
+        buffer.clear();
+        pool.offer(buffer);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferReference.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public class ByteBufferReference  implements Supplier<ByteBuffer> {
+
+    private ByteBuffer buffer;
+    private final ByteBufferPool pool;
+
+    public static ByteBufferReference of(ByteBuffer buffer) {
+        return of(buffer, null);
+    }
+
+    public static ByteBufferReference of(ByteBuffer buffer, ByteBufferPool pool) {
+        Objects.requireNonNull(buffer);
+        return new ByteBufferReference(buffer, pool);
+    }
+
+    public static ByteBuffer[] toBuffers(ByteBufferReference... refs) {
+        ByteBuffer[] bufs = new ByteBuffer[refs.length];
+        for (int i = 0; i < refs.length; i++) {
+            bufs[i] = refs[i].get();
+        }
+        return bufs;
+    }
+
+    public static ByteBufferReference[] toReferences(ByteBuffer... buffers) {
+        ByteBufferReference[] refs = new ByteBufferReference[buffers.length];
+        for (int i = 0; i < buffers.length; i++) {
+            refs[i] = of(buffers[i]);
+        }
+        return refs;
+    }
+
+
+    public static void clear(ByteBufferReference[] refs) {
+        for(ByteBufferReference ref : refs) {
+            ref.clear();
+        }
+    }
+
+    private ByteBufferReference(ByteBuffer buffer, ByteBufferPool pool) {
+        this.buffer = buffer;
+        this.pool = pool;
+    }
+
+    @Override
+    public ByteBuffer get() {
+        ByteBuffer buf = this.buffer;
+        assert buf!=null : "getting ByteBuffer after clearance";
+        return buf;
+    }
+
+    public void clear() {
+        ByteBuffer buf = this.buffer;
+        assert buf!=null : "double ByteBuffer clearance";
+        this.buffer = null;
+        if (pool != null) {
+            pool.release(buf);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/ConnectionExpiredException.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,47 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.io.IOException;
+
+/**
+ * Signals that an end of file or end of stream has been reached
+ * unexpectedly before any protocol specific data has been received.
+ */
+public final class ConnectionExpiredException extends IOException {
+    private static final long serialVersionUID = 0;
+
+    /**
+     * Constructs a {@code ConnectionExpiredException} with the specified detail
+     * message and cause.
+     *
+     * @param   s     the detail message
+     * @param   cause the throwable cause
+     */
+    public ConnectionExpiredException(String s, Throwable cause) {
+        super(s, cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/DebugLogger.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,251 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.io.PrintStream;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.lang.System.Logger;
+
+/**
+ * A {@code System.Logger} that forwards all messages to an underlying
+ * {@code System.Logger}, after adding some decoration.
+ * The logger also has the ability to additionally send the logged messages
+ * to System.err or System.out, whether the underlying logger is activated or not.
+ * In addition instance of {@code DebugLogger} support both
+ * {@link String#format(String, Object...)} and
+ * {@link java.text.MessageFormat#format(String, Object...)} formatting.
+ * String-like formatting is enabled by the presence of "%s" or "%d" in the format
+ * string. MessageFormat-like formatting is enabled by the presence of "{0" or "{1".
+ * <p>
+ * See {@link Utils#getDebugLogger(Supplier, boolean)} and
+ * {@link Utils#getHpackLogger(Supplier, boolean)}.
+ */
+class DebugLogger implements Logger {
+    // deliberately not in the same subtree than standard loggers.
+    final static String HTTP_NAME  = "jdk.internal.httpclient.debug";
+    final static String HPACK_NAME = "jdk.internal.httpclient.hpack.debug";
+    final static Logger HTTP = System.getLogger(HTTP_NAME);
+    final static Logger HPACK = System.getLogger(HPACK_NAME);
+    final static long START_NANOS = System.nanoTime();
+
+    private final Supplier<String> dbgTag;
+    private final Level errLevel;
+    private final Level outLevel;
+    private final Logger logger;
+    private final boolean debugOn;
+    private final boolean traceOn;
+
+    /**
+     * Create a logger for debug traces.The logger should only be used
+     * with levels whose severity is {@code <= DEBUG}.
+     *
+     * By default, this logger will forward all messages logged to the supplied
+     * {@code logger}.
+     * But in addition, if the message severity level is {@code >=} to
+     * the provided {@code errLevel} it will print the messages on System.err,
+     * and if the message severity level is {@code >=} to
+     * the provided {@code outLevel} it will also print the messages on System.out.
+     * <p>
+     * The logger will add some decoration to the printed message, in the form of
+     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
+     *
+     * @apiNote To obtain a logger that will always print things on stderr in
+     *          addition to forwarding to the internal logger, use
+     *          {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.ALL);}.
+     *          To obtain a logger that will only forward to the internal logger,
+     *          use {@code new DebugLogger(logger, this::dbgTag, Level.OFF, Level.OFF);}.
+     *
+     * @param logger The internal logger to which messages will be forwarded.
+     *               This should be either {@link #HPACK} or {@link #HTTP};
+     *
+     * @param dbgTag A lambda that returns a string that identifies the caller
+     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
+     * @param outLevel The level above which messages will be also printed on
+     *               System.out (in addition to being forwarded to the internal logger).
+     * @param errLevel The level above which messages will be also printed on
+     *               System.err (in addition to being forwarded to the internal logger).
+     *
+     * @return A logger for HTTP internal debug traces
+     */
+    private DebugLogger(Logger logger,
+                Supplier<String> dbgTag,
+                Level outLevel,
+                Level errLevel) {
+        this.dbgTag = dbgTag;
+        this.errLevel = errLevel;
+        this.outLevel = outLevel;
+        this.logger = Objects.requireNonNull(logger);
+        // support only static configuration.
+        this.debugOn = isEnabled(Level.DEBUG);
+        this.traceOn = isEnabled(Level.TRACE);
+    }
+
+    @Override
+    public String getName() {
+        return logger.getName();
+    }
+
+    private boolean isEnabled(Level level) {
+        if (level == Level.OFF) return false;
+        int severity = level.getSeverity();
+        return severity >= errLevel.getSeverity()
+                || severity >= outLevel.getSeverity()
+                || logger.isLoggable(level);
+    }
+
+    @Override
+    public boolean isLoggable(Level level) {
+        // fast path, we assume these guys never change.
+        // support only static configuration.
+        if (level == Level.DEBUG) return debugOn;
+        if (level == Level.TRACE) return traceOn;
+        return isEnabled(level);
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle unused,
+                    String format, Object... params) {
+        // fast path, we assume these guys never change.
+        // support only static configuration.
+        if (level == Level.DEBUG && !debugOn) return;
+        if (level == Level.TRACE && !traceOn) return;
+
+        int severity = level.getSeverity();
+        if (errLevel != Level.OFF
+                && errLevel.getSeverity() <= severity) {
+            print(System.err, level, format, params, null);
+        }
+        if (outLevel != Level.OFF
+                && outLevel.getSeverity() <= severity) {
+            print(System.out, level, format, params, null);
+        }
+        if (logger.isLoggable(level)) {
+            logger.log(level, unused,
+                    getFormat(new StringBuilder(), format, params).toString(),
+                    params);
+        }
+    }
+
+    @Override
+    public void log(Level level, ResourceBundle unused, String msg,
+                    Throwable thrown) {
+        // fast path, we assume these guys never change.
+        if (level == Level.DEBUG && !debugOn) return;
+        if (level == Level.TRACE && !traceOn) return;
+
+        if (errLevel != Level.OFF
+                && errLevel.getSeverity() <= level.getSeverity()) {
+            print(System.err, level, msg, null, thrown);
+        }
+        if (outLevel != Level.OFF
+                && outLevel.getSeverity() <= level.getSeverity()) {
+            print(System.out, level, msg, null, thrown);
+        }
+        if (logger.isLoggable(level)) {
+            logger.log(level, unused,
+                    getFormat(new StringBuilder(), msg, null).toString(),
+                    thrown);
+        }
+    }
+
+    private void print(PrintStream out, Level level, String msg,
+                       Object[] params, Throwable t) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(level.name()).append(':').append(' ');
+        sb = format(sb, msg, params);
+        if (t != null) sb.append(' ').append(t.toString());
+        out.println(sb.toString());
+        if (t != null) {
+            t.printStackTrace(out);
+        }
+    }
+
+    private StringBuilder decorate(StringBuilder sb, String msg) {
+        String tag = dbgTag == null ? null : dbgTag.get();
+        String res = msg == null ? "" : msg;
+        long elapsed = System.nanoTime() - START_NANOS;
+        long millis = elapsed / 1000_000;
+        long secs   = millis / 1000;
+        sb.append('[').append(Thread.currentThread().getName()).append(']')
+                .append(' ').append('[');
+        if (secs > 0) {
+            sb.append(secs).append('s');
+        }
+        millis = millis % 1000;
+        if (millis > 0) {
+            if (secs > 0) sb.append(' ');
+            sb.append(millis).append("ms");
+        }
+        sb.append(']').append(' ');
+        if (tag != null) {
+            sb.append(tag).append(' ');
+        }
+        sb.append(res);
+        return sb;
+    }
+
+
+    private StringBuilder getFormat(StringBuilder sb, String format, Object[] params) {
+        if (format == null || params == null || params.length == 0) {
+            return decorate(sb, format);
+        } else if (format.contains("{0}") || format.contains("{1}")) {
+            return decorate(sb, format);
+        } else if (format.contains("%s") || format.contains("%d")) {
+            try {
+                return decorate(sb, String.format(format, params));
+            } catch (Throwable t) {
+                return decorate(sb, format);
+            }
+        } else {
+            return decorate(sb, format);
+        }
+    }
+
+    private StringBuilder format(StringBuilder sb, String format, Object[] params) {
+        if (format == null || params == null || params.length == 0) {
+            return decorate(sb, format);
+        } else if (format.contains("{0}") || format.contains("{1}")) {
+            return decorate(sb, java.text.MessageFormat.format(format, params));
+        } else if (format.contains("%s") || format.contains("%d")) {
+            try {
+                return decorate(sb, String.format(format, params));
+            } catch (Throwable t) {
+                return decorate(sb, format);
+            }
+        } else {
+            return decorate(sb, format);
+        }
+    }
+
+    public static DebugLogger createHttpLogger(Supplier<String> dbgTag, Level outLevel, Level errLevel) {
+        return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
+    }
+
+    public static DebugLogger createHpackLogger(Supplier<String> dbgTag, Level outLevel, Level errLevel) {
+        return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Demand.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,116 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Encapsulates operations with demand (Reactive Streams).
+ *
+ * <p> Demand is the aggregated number of elements requested by a Subscriber
+ * which is yet to be delivered (fulfilled) by the Publisher.
+ */
+public final class Demand {
+
+    private final AtomicLong val = new AtomicLong();
+
+    /**
+     * Increases this demand by the specified positive value.
+     *
+     * @param n
+     *         increment {@code > 0}
+     *
+     * @return {@code true} iff prior to this operation this demand was fulfilled
+     */
+    public boolean increase(long n) {
+        if (n <= 0) {
+            throw new IllegalArgumentException(String.valueOf(n));
+        }
+        long prev = val.getAndAccumulate(n, (p, i) -> p + i < 0 ? Long.MAX_VALUE : p + i);
+        return prev == 0;
+    }
+
+    /**
+     * Tries to decrease this demand by the specified positive value.
+     *
+     * <p> The actual value this demand has been decreased by might be less than
+     * {@code n}, including {@code 0} (no decrease at all).
+     *
+     * @param n
+     *         decrement {@code > 0}
+     *
+     * @return a value {@code m} ({@code 0 <= m <= n}) this demand has been
+     *         actually decreased by
+     */
+    public long decreaseAndGet(long n) {
+        if (n <= 0) {
+            throw new IllegalArgumentException(String.valueOf(n));
+        }
+        long p, d;
+        do {
+            d = val.get();
+            p = Math.min(d, n);
+        } while (!val.compareAndSet(d, d - p));
+        return p;
+    }
+
+    /**
+     * Tries to decrease this demand by {@code 1}.
+     *
+     * @return {@code true} iff this demand has been decreased by {@code 1}
+     */
+    public boolean tryDecrement() {
+        return decreaseAndGet(1) == 1;
+    }
+
+    /**
+     * @return {@code true} iff there is no unfulfilled demand
+     */
+    public boolean isFulfilled() {
+        return val.get() == 0;
+    }
+
+    /**
+     * Resets this object to its initial state.
+     */
+    public void reset() {
+        val.set(0);
+    }
+
+    /**
+     * Returns the current value of this demand.
+     *
+     * @return the current value of this demand
+     */
+    public long get() {
+        return val.get();
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(val.get());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/FlowTube.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,185 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow;
+
+/**
+ * FlowTube is an I/O abstraction that allows reading from and writing to a
+ * destination asynchronously.
+ * This is not a {@link Flow.Processor
+ * Flow.Processor&lt;List&lt;ByteBuffer&gt;, List&lt;ByteBuffer&gt;&gt;},
+ * but rather models a publisher source and a subscriber sink in a bidirectional flow.
+ * <p>
+ * The {@code connectFlows} method should be called to connect the bidirectional
+ * flow. A FlowTube supports handing over the same read subscription to different
+ * sequential read subscribers over time. When {@code connectFlows(writePublisher,
+ * readSubscriber} is called, the FlowTube will call {@code dropSubscription} on
+ * its former readSubscriber, and {@code onSubscribe} on its new readSubscriber.
+ */
+public interface FlowTube extends
+       Flow.Publisher<List<ByteBuffer>>,
+       Flow.Subscriber<List<ByteBuffer>> {
+
+    /**
+     * A subscriber for reading from the bidirectional flow.
+     * A TubeSubscriber is a {@code Flow.Subscriber} that can be canceled
+     * by calling {@code dropSubscription()}.
+     * Once {@code dropSubscription()} is called, the {@code TubeSubscriber}
+     * should stop calling any method on its subscription.
+     */
+    static interface TubeSubscriber extends Flow.Subscriber<List<ByteBuffer>> {
+
+        /**
+         * Called when the flow is connected again, and the subscription
+         * is handed over to a new subscriber.
+         * Once {@code dropSubscription()} is called, the {@code TubeSubscriber}
+         * should stop calling any method on its subscription.
+         */
+        default void dropSubscription() { }
+
+    }
+
+    /**
+     * A publisher for writing to the bidirectional flow.
+     */
+    static interface TubePublisher extends Flow.Publisher<List<ByteBuffer>> {
+
+    }
+
+    /**
+     * Connects the bidirectional flows to a write {@code Publisher} and a
+     * read {@code Subscriber}. This method can be called sequentially
+     * several times to switch existing publishers and subscribers to a new
+     * pair of write subscriber and read publisher.
+     * @param writePublisher A new publisher for writing to the bidirectional flow.
+     * @param readSubscriber A new subscriber for reading from the bidirectional
+     *                       flow.
+     */
+    default void connectFlows(TubePublisher writePublisher,
+                              TubeSubscriber readSubscriber) {
+
+        this.subscribe(readSubscriber);
+        writePublisher.subscribe(this);
+    }
+
+    /**
+     * Returns true if this flow was completed, either exceptionally
+     * or normally (EOF reached).
+     * @return true if the flow is finished
+     */
+    boolean isFinished();
+
+
+    /**
+     * Returns {@code s} if {@code s} is a {@code TubeSubscriber}, otherwise
+     * wraps it in a {@code TubeSubscriber}.
+     * Using the wrapper is only appropriate in the case where
+     * {@code dropSubscription} doesn't need to be implemented, and the
+     * {@code TubeSubscriber} is subscribed only once.
+     *
+     * @param s a subscriber for reading.
+     * @return a {@code TubeSubscriber}: either {@code s} if {@code s} is a
+     *           {@code TubeSubscriber}, otherwise a {@code TubeSubscriber}
+     *           wrapper that delegates to {@code s}
+     */
+    static TubeSubscriber asTubeSubscriber(Flow.Subscriber<? super List<ByteBuffer>> s) {
+        if (s instanceof TubeSubscriber) {
+            return (TubeSubscriber) s;
+        }
+        return new AbstractTubeSubscriber.TubeSubscriberWrapper(s);
+    }
+
+    /**
+     * Returns {@code s} if {@code s} is a {@code  TubePublisher}, otherwise
+     * wraps it in a {@code  TubePublisher}.
+     *
+     * @param p a publisher for writing.
+     * @return a {@code TubePublisher}: either {@code s} if {@code s} is a
+     *           {@code  TubePublisher}, otherwise a {@code TubePublisher}
+     *           wrapper that delegates to {@code s}
+     */
+    static TubePublisher asTubePublisher(Flow.Publisher<List<ByteBuffer>> p) {
+        if (p instanceof TubePublisher) {
+            return (TubePublisher) p;
+        }
+        return new AbstractTubePublisher.TubePublisherWrapper(p);
+    }
+
+    /**
+     * Convenience abstract class for {@code TubePublisher} implementations.
+     * It is not required that a {@code TubePublisher} implementation extends
+     * this class.
+     */
+    static abstract class AbstractTubePublisher implements TubePublisher {
+        static final class TubePublisherWrapper extends AbstractTubePublisher {
+            final Flow.Publisher<List<ByteBuffer>> delegate;
+            public TubePublisherWrapper(Flow.Publisher<List<ByteBuffer>> delegate) {
+                this.delegate = delegate;
+            }
+            @Override
+            public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+                delegate.subscribe(subscriber);
+            }
+        }
+    }
+
+    /**
+     * Convenience abstract class for {@code TubeSubscriber} implementations.
+     * It is not required that a {@code TubeSubscriber} implementation extends
+     * this class.
+     */
+    static abstract class AbstractTubeSubscriber implements TubeSubscriber {
+        static final class TubeSubscriberWrapper extends  AbstractTubeSubscriber {
+            final Flow.Subscriber<? super List<ByteBuffer>> delegate;
+            TubeSubscriberWrapper(Flow.Subscriber<? super List<ByteBuffer>> delegate) {
+                this.delegate = delegate;
+            }
+            @Override
+            public void dropSubscription() {}
+            @Override
+            public void onSubscribe(Flow.Subscription subscription) {
+                delegate.onSubscribe(subscription);
+            }
+            @Override
+            public void onNext(List<ByteBuffer> item) {
+                delegate.onNext(item);
+            }
+            @Override
+            public void onError(Throwable throwable) {
+                delegate.onError(throwable);
+            }
+            @Override
+            public void onComplete() {
+                delegate.onComplete();
+            }
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/HttpHeadersImpl.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,73 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.net.http.HttpHeaders;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Implementation of HttpHeaders.
+ */
+public class HttpHeadersImpl extends HttpHeaders {
+
+    private final TreeMap<String,List<String>> headers;
+
+    public HttpHeadersImpl() {
+        headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+    }
+
+    @Override
+    public Map<String, List<String>> map() {
+        return Collections.unmodifiableMap(headers);
+    }
+
+    // package private mutators
+
+    public HttpHeadersImpl deepCopy() {
+        HttpHeadersImpl h1 = new HttpHeadersImpl();
+        for (Map.Entry<String,List<String>> entry : headers.entrySet()) {
+            List<String> valuesCopy = new ArrayList<>(entry.getValue());
+            h1.headers.put(entry.getKey(), valuesCopy);
+        }
+        return h1;
+    }
+
+    public void addHeader(String name, String value) {
+        headers.computeIfAbsent(name, k -> new ArrayList<>(1))
+               .add(value);
+    }
+
+    public void setHeader(String name, String value) {
+        List<String> values = new ArrayList<>(1); // most headers has one value
+        values.add(value);
+        headers.put(name, values);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Log.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,302 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.net.http.HttpHeaders;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import jdk.internal.net.http.frame.DataFrame;
+import jdk.internal.net.http.frame.Http2Frame;
+import jdk.internal.net.http.frame.WindowUpdateFrame;
+
+import javax.net.ssl.SNIServerName;
+import javax.net.ssl.SSLParameters;
+
+/**
+ * -Djava.net.HttpClient.log=
+ *          errors,requests,headers,
+ *          frames[:control:data:window:all..],content,ssl,trace
+ *
+ * Any of errors, requests, headers or content are optional.
+ *
+ * Other handlers may be added. All logging is at level INFO
+ *
+ * Logger name is "jdk.httpclient.HttpClient"
+ */
+// implements System.Logger in order to be skipped when printing the caller's
+// information
+public abstract class Log implements System.Logger {
+
+    static final String logProp = "jdk.httpclient.HttpClient.log";
+
+    public static final int OFF = 0;
+    public static final int ERRORS = 0x1;
+    public static final int REQUESTS = 0x2;
+    public static final int HEADERS = 0x4;
+    public static final int CONTENT = 0x8;
+    public static final int FRAMES = 0x10;
+    public static final int SSL = 0x20;
+    public static final int TRACE = 0x40;
+    static int logging;
+
+    // Frame types: "control", "data", "window", "all"
+    public static final int CONTROL = 1; // all except DATA and WINDOW_UPDATES
+    public static final int DATA = 2;
+    public static final int WINDOW_UPDATES = 4;
+    public static final int ALL = CONTROL| DATA | WINDOW_UPDATES;
+    static int frametypes;
+
+    static final System.Logger logger;
+
+    static {
+        String s = Utils.getNetProperty(logProp);
+        if (s == null) {
+            logging = OFF;
+        } else {
+            String[] vals = s.split(",");
+            for (String val : vals) {
+                switch (val.toLowerCase(Locale.US)) {
+                    case "errors":
+                        logging |= ERRORS;
+                        break;
+                    case "requests":
+                        logging |= REQUESTS;
+                        break;
+                    case "headers":
+                        logging |= HEADERS;
+                        break;
+                    case "content":
+                        logging |= CONTENT;
+                        break;
+                    case "ssl":
+                        logging |= SSL;
+                        break;
+                    case "trace":
+                        logging |= TRACE;
+                        break;
+                    case "all":
+                        logging |= CONTENT|HEADERS|REQUESTS|FRAMES|ERRORS|TRACE|SSL;
+                        break;
+                }
+                if (val.startsWith("frames")) {
+                    logging |= FRAMES;
+                    String[] types = val.split(":");
+                    if (types.length == 1) {
+                        frametypes = CONTROL | DATA | WINDOW_UPDATES;
+                    } else {
+                        for (String type : types) {
+                            switch (type.toLowerCase()) {
+                                case "control":
+                                    frametypes |= CONTROL;
+                                    break;
+                                case "data":
+                                    frametypes |= DATA;
+                                    break;
+                                case "window":
+                                    frametypes |= WINDOW_UPDATES;
+                                    break;
+                                case "all":
+                                    frametypes = ALL;
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (logging != OFF) {
+            logger = System.getLogger("jdk.httpclient.HttpClient");
+        } else {
+            logger = null;
+        }
+    }
+    public static boolean errors() {
+        return (logging & ERRORS) != 0;
+    }
+
+    public static boolean requests() {
+        return (logging & REQUESTS) != 0;
+    }
+
+    public static boolean headers() {
+        return (logging & HEADERS) != 0;
+    }
+
+    public static boolean trace() {
+        return (logging & TRACE) != 0;
+    }
+
+    public static boolean ssl() {
+        return (logging & SSL) != 0;
+    }
+
+    public static boolean frames() {
+        return (logging & FRAMES) != 0;
+    }
+
+    public static void logError(String s, Object... s1) {
+        if (errors()) {
+            logger.log(Level.INFO, "ERROR: " + s, s1);
+        }
+    }
+
+    public static void logError(Throwable t) {
+        if (errors()) {
+            String s = Utils.stackTrace(t);
+            logger.log(Level.INFO, "ERROR: " + s);
+        }
+    }
+
+    public static void logSSL(String s, Object... s1) {
+        if (ssl()) {
+            logger.log(Level.INFO, "SSL: " + s, s1);
+        }
+    }
+
+    public static void logSSL(Supplier<String> msgSupplier) {
+        if (ssl()) {
+            logger.log(Level.INFO, "SSL: " + msgSupplier.get());
+        }
+    }
+
+    public static void logTrace(String s, Object... s1) {
+        if (trace()) {
+            String format = "TRACE: " + s;
+            logger.log(Level.INFO, format, s1);
+        }
+    }
+
+    public static void logRequest(String s, Object... s1) {
+        if (requests()) {
+            logger.log(Level.INFO, "REQUEST: " + s, s1);
+        }
+    }
+
+    public static void logResponse(Supplier<String> supplier) {
+        if (requests()) {
+            logger.log(Level.INFO, "RESPONSE: " + supplier.get());
+        }
+    }
+
+    public static void logHeaders(String s, Object... s1) {
+        if (headers()) {
+            logger.log(Level.INFO, "HEADERS: " + s, s1);
+        }
+    }
+
+    public static boolean loggingFrame(Class<? extends Http2Frame> clazz) {
+        if (frametypes == ALL) {
+            return true;
+        }
+        if (clazz == DataFrame.class) {
+            return (frametypes & DATA) != 0;
+        } else if (clazz == WindowUpdateFrame.class) {
+            return (frametypes & WINDOW_UPDATES) != 0;
+        } else {
+            return (frametypes & CONTROL) != 0;
+        }
+    }
+
+    public static void logFrames(Http2Frame f, String direction) {
+        if (frames() && loggingFrame(f.getClass())) {
+            logger.log(Level.INFO, "FRAME: " + direction + ": " + f.toString());
+        }
+    }
+
+    public static void logParams(SSLParameters p) {
+        if (!Log.ssl()) {
+            return;
+        }
+
+        if (p == null) {
+            Log.logSSL("SSLParameters: Null params");
+            return;
+        }
+
+        final StringBuilder sb = new StringBuilder("SSLParameters:");
+        final List<Object> params = new ArrayList<>();
+        if (p.getCipherSuites() != null) {
+            for (String cipher : p.getCipherSuites()) {
+                sb.append("\n    cipher: {")
+                        .append(params.size()).append("}");
+                params.add(cipher);
+            }
+        }
+
+        // SSLParameters.getApplicationProtocols() can't return null
+        // JDK 8 EXCL START
+        for (String approto : p.getApplicationProtocols()) {
+            sb.append("\n    application protocol: {")
+                    .append(params.size()).append("}");
+            params.add(approto);
+        }
+        // JDK 8 EXCL END
+
+        if (p.getProtocols() != null) {
+            for (String protocol : p.getProtocols()) {
+                sb.append("\n    protocol: {")
+                        .append(params.size()).append("}");
+                params.add(protocol);
+            }
+        }
+
+        if (p.getServerNames() != null) {
+            for (SNIServerName sname : p.getServerNames()) {
+                sb.append("\n    server name: {")
+                        .append(params.size()).append("}");
+                params.add(sname.toString());
+            }
+        }
+        sb.append('\n');
+
+        Log.logSSL(sb.toString(), params.toArray());
+    }
+
+    public static void dumpHeaders(StringBuilder sb, String prefix, HttpHeaders headers) {
+        if (headers != null) {
+            Map<String,List<String>> h = headers.map();
+            Set<Map.Entry<String,List<String>>> entries = h.entrySet();
+            for (Map.Entry<String,List<String>> entry : entries) {
+                String key = entry.getKey();
+                List<String> values = entry.getValue();
+                sb.append(prefix).append(key).append(":");
+                for (String value : values) {
+                    sb.append(' ').append(value);
+                }
+                sb.append('\n');
+            }
+        }
+    }
+
+
+    // not instantiable
+    private Log() {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/MinimalFuture.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,110 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static java.util.Objects.requireNonNull;
+
+/*
+ * A CompletableFuture which does not allow any obtrusion logic.
+ * All methods of CompletionStage return instances of this class.
+ */
+public final class MinimalFuture<T> extends CompletableFuture<T> {
+
+    @FunctionalInterface
+    public interface ExceptionalSupplier<U> {
+        U get() throws Throwable;
+    }
+
+    private final static AtomicLong TOKENS = new AtomicLong();
+    private final long id;
+
+    public static <U> MinimalFuture<U> completedFuture(U value) {
+        MinimalFuture<U> f = new MinimalFuture<>();
+        f.complete(value);
+        return f;
+    }
+
+    public static <U> CompletableFuture<U> failedFuture(Throwable ex) {
+        requireNonNull(ex);
+        MinimalFuture<U> f = new MinimalFuture<>();
+        f.completeExceptionally(ex);
+        return f;
+    }
+
+    public static <U> CompletableFuture<U> supply(ExceptionalSupplier<U> supplier) {
+        CompletableFuture<U> cf = new MinimalFuture<>();
+        try {
+            U value = supplier.get();
+            cf.complete(value);
+        } catch (Throwable t) {
+            cf.completeExceptionally(t);
+        }
+        return cf;
+    }
+
+    public MinimalFuture() {
+        super();
+        this.id = TOKENS.incrementAndGet();
+    }
+
+    @Override
+    public <U> MinimalFuture<U> newIncompleteFuture() {
+        return new MinimalFuture<>();
+    }
+
+    @Override
+    public void obtrudeValue(T value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void obtrudeException(Throwable ex) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " (id=" + id +")";
+    }
+
+    public static <U> MinimalFuture<U> of(CompletionStage<U> stage) {
+        MinimalFuture<U> cf = new MinimalFuture<>();
+        stage.whenComplete((r,t) -> complete(cf, r, t));
+        return cf;
+    }
+
+    private static <U> void complete(CompletableFuture<U> cf, U result, Throwable t) {
+        if (t == null) {
+            cf.complete(result);
+        } else {
+            cf.completeExceptionally(t);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Pair.java	Wed Feb 07 21:45:37 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.internal.net.http.common;
+
+/**
+ * A simple paired value class
+ */
+public final class Pair<T, U> {
+
+    public Pair(T first, U second) {
+        this.second = second;
+        this.first = first;
+    }
+
+    public final T first;
+    public final U second;
+
+    // Because 'pair()' is shorter than 'new Pair<>()'.
+    // Sometimes this difference might be very significant (especially in a
+    // 80-ish characters boundary). Sorry diamond operator.
+    public static <T, U> Pair<T, U> pair(T first, U second) {
+        return new Pair<>(first, second);
+    }
+
+    @Override
+    public String toString() {
+        return "(" + first + ", " + second + ")";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,906 @@
+/*
+ * 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.internal.net.http.common;
+
+import jdk.internal.net.http.common.SubscriberWrapper.SchedulingAction;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+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.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Implements SSL using two SubscriberWrappers.
+ *
+ * <p> Constructor takes two Flow.Subscribers: one that receives the network
+ * data (after it has been encrypted by SSLFlowDelegate) data, and one that
+ * receives the application data (before it has been encrypted by SSLFlowDelegate).
+ *
+ * <p> Methods upstreamReader() and upstreamWriter() return the corresponding
+ * Flow.Subscribers containing Flows for the encrypted/decrypted upstream data.
+ * See diagram below.
+ *
+ * <p> How Flow.Subscribers are used in this class, and where they come from:
+ * <pre>
+ * {@code
+ *
+ *
+ *
+ * --------->  data flow direction
+ *
+ *
+ *                         +------------------+
+ *        upstreamWriter   |                  | downWriter
+ *        ---------------> |                  | ------------>
+ *  obtained from this     |                  | supplied to constructor
+ *                         | SSLFlowDelegate  |
+ *        downReader       |                  | upstreamReader
+ *        <--------------- |                  | <--------------
+ * supplied to constructor |                  | obtained from this
+ *                         +------------------+
+ * }
+ * </pre>
+ */
+public class SSLFlowDelegate {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger debug =
+            Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    final Executor exec;
+    final Reader reader;
+    final Writer writer;
+    final SSLEngine engine;
+    final String tubeName; // hack
+    private final CompletableFuture<Void> cf;
+    final CompletableFuture<String> alpnCF; // completes on initial handshake
+    final static ByteBuffer SENTINEL = Utils.EMPTY_BYTEBUFFER;
+    volatile boolean close_notify_received;
+
+    /**
+     * Creates an SSLFlowDelegate fed from two Flow.Subscribers. Each
+     * Flow.Subscriber requires an associated {@link CompletableFuture}
+     * for errors that need to be signaled from downstream to upstream.
+     */
+    public SSLFlowDelegate(SSLEngine engine,
+                           Executor exec,
+                           Subscriber<? super List<ByteBuffer>> downReader,
+                           Subscriber<? super List<ByteBuffer>> downWriter)
+    {
+        this.tubeName = String.valueOf(downWriter);
+        this.reader = new Reader();
+        this.writer = new Writer();
+        this.engine = engine;
+        this.exec = exec;
+        this.handshakeState = new AtomicInteger(NOT_HANDSHAKING);
+        CompletableFuture<Void> cs = CompletableFuture.allOf(
+                reader.completion(), writer.completion()).thenRun(this::normalStop);
+        this.cf = MinimalFuture.of(cs);
+        this.alpnCF = new MinimalFuture<>();
+
+        // connect the Reader to the downReader and the
+        // Writer to the downWriter.
+        connect(downReader, downWriter);
+
+        //Monitor.add(this::monitor);
+    }
+
+    /**
+     * Returns true if the SSLFlowDelegate has detected a TLS
+     * close_notify from the server.
+     * @return true, if a close_notify was detected.
+     */
+    public boolean closeNotifyReceived() {
+        return close_notify_received;
+    }
+
+    /**
+     * Connects the read sink (downReader) to the SSLFlowDelegate Reader,
+     * and the write sink (downWriter) to the SSLFlowDelegate Writer.
+     * Called from within the constructor. Overwritten by SSLTube.
+     *
+     * @param downReader  The left hand side read sink (typically, the
+     *                    HttpConnection read subscriber).
+     * @param downWriter  The right hand side write sink (typically
+     *                    the SocketTube write subscriber).
+     */
+    void connect(Subscriber<? super List<ByteBuffer>> downReader,
+                 Subscriber<? super List<ByteBuffer>> downWriter) {
+        this.reader.subscribe(downReader);
+        this.writer.subscribe(downWriter);
+    }
+
+   /**
+    * Returns a CompletableFuture<String> which completes after
+    * the initial handshake completes, and which contains the negotiated
+    * alpn.
+    */
+    public CompletableFuture<String> alpn() {
+        return alpnCF;
+    }
+
+    private void setALPN() {
+        // Handshake is finished. So, can retrieve the ALPN now
+        if (alpnCF.isDone())
+            return;
+        String alpn = engine.getApplicationProtocol();
+        debug.log(Level.DEBUG, "setALPN = %s", alpn);
+        alpnCF.complete(alpn);
+    }
+
+    public String monitor() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SSL: HS state: " + states(handshakeState));
+        sb.append(" Engine state: " + engine.getHandshakeStatus().toString());
+        sb.append(" LL : ");
+        synchronized(stateList) {
+            for (String s: stateList) {
+                sb.append(s).append(" ");
+            }
+        }
+        sb.append("\r\n");
+        sb.append("Reader:: ").append(reader.toString());
+        sb.append("\r\n");
+        sb.append("Writer:: ").append(writer.toString());
+        sb.append("\r\n===================================");
+        return sb.toString();
+    }
+
+    protected SchedulingAction enterReadScheduling() {
+        return SchedulingAction.CONTINUE;
+    }
+
+
+    /**
+     * Processing function for incoming data. Pass it thru SSLEngine.unwrap().
+     * Any decrypted buffers returned to be passed downstream.
+     * Status codes:
+     *     NEED_UNWRAP: do nothing. Following incoming data will contain
+     *                  any required handshake data
+     *     NEED_WRAP: call writer.addData() with empty buffer
+     *     NEED_TASK: delegate task to executor
+     *     BUFFER_OVERFLOW: allocate larger output buffer. Repeat unwrap
+     *     BUFFER_UNDERFLOW: keep buffer and wait for more data
+     *     OK: return generated buffers.
+     *
+     * Upstream subscription strategy is to try and keep no more than
+     * TARGET_BUFSIZE bytes in readBuf
+     */
+    class Reader extends SubscriberWrapper {
+        final SequentialScheduler scheduler;
+        static final int TARGET_BUFSIZE = 16 * 1024;
+        volatile ByteBuffer readBuf;
+        volatile boolean completing = false;
+        final Object readBufferLock = new Object();
+        final System.Logger debugr =
+            Utils.getDebugLogger(this::dbgString, DEBUG);
+
+        class ReaderDownstreamPusher implements Runnable {
+            @Override public void run() { processData(); }
+        }
+
+        Reader() {
+            super();
+            scheduler = SequentialScheduler.synchronizedScheduler(
+                                                new ReaderDownstreamPusher());
+            this.readBuf = ByteBuffer.allocate(1024);
+            readBuf.limit(0); // keep in read mode
+        }
+
+        protected SchedulingAction enterScheduling() {
+            return enterReadScheduling();
+        }
+
+        public final String dbgString() {
+            return "SSL Reader(" + tubeName + ")";
+        }
+
+        /**
+         * entry point for buffers delivered from upstream Subscriber
+         */
+        @Override
+        public void incoming(List<ByteBuffer> buffers, boolean complete) {
+            debugr.log(Level.DEBUG, () -> "Adding " + Utils.remaining(buffers)
+                        + " bytes to read buffer");
+            addToReadBuf(buffers, complete);
+            scheduler.runOrSchedule();
+        }
+
+        @Override
+        public String toString() {
+            return "READER: " + super.toString() + " readBuf: " + readBuf.toString()
+                    + " count: " + count.toString();
+        }
+
+        private void reallocReadBuf() {
+            int sz = readBuf.capacity();
+            ByteBuffer newb = ByteBuffer.allocate(sz*2);
+            readBuf.flip();
+            Utils.copy(readBuf, newb);
+            readBuf = newb;
+        }
+
+        @Override
+        protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
+            if (readBuf.remaining() > TARGET_BUFSIZE) {
+                return 0;
+            } else {
+                return super.upstreamWindowUpdate(currentWindow, downstreamQsize);
+            }
+        }
+
+        // readBuf is kept ready for reading outside of this method
+        private void addToReadBuf(List<ByteBuffer> buffers, boolean complete) {
+            synchronized (readBufferLock) {
+                for (ByteBuffer buf : buffers) {
+                    readBuf.compact();
+                    while (readBuf.remaining() < buf.remaining())
+                        reallocReadBuf();
+                    readBuf.put(buf);
+                    readBuf.flip();
+                }
+                if (complete) {
+                    this.completing = complete;
+                }
+            }
+        }
+
+        void schedule() {
+            scheduler.runOrSchedule();
+        }
+
+        void stop() {
+            debugr.log(Level.DEBUG, "stop");
+            scheduler.stop();
+        }
+
+        AtomicInteger count = new AtomicInteger(0);
+
+        // work function where it all happens
+        void processData() {
+            try {
+                debugr.log(Level.DEBUG, () -> "processData: " + readBuf.remaining()
+                           + " bytes to unwrap "
+                           + states(handshakeState)
+                           + ", " + engine.getHandshakeStatus());
+                int len;
+                boolean complete = false;
+                while ((len = readBuf.remaining()) > 0) {
+                    boolean handshaking = false;
+                    try {
+                        EngineResult result;
+                        synchronized (readBufferLock) {
+                            complete = this.completing;
+                            result = unwrapBuffer(readBuf);
+                            debugr.log(Level.DEBUG, "Unwrapped: %s", result.result);
+                        }
+                        if (result.bytesProduced() > 0) {
+                            debugr.log(Level.DEBUG, "sending %d", result.bytesProduced());
+                            count.addAndGet(result.bytesProduced());
+                            outgoing(result.destBuffer, false);
+                        }
+                        if (result.status() == Status.BUFFER_UNDERFLOW) {
+                            debugr.log(Level.DEBUG, "BUFFER_UNDERFLOW");
+                            // not enough data in the read buffer...
+                            requestMore();
+                            synchronized (readBufferLock) {
+                                // check if we have received some data
+                                if (readBuf.remaining() > len) continue;
+                                return;
+                            }
+                        }
+                        if (complete && result.status() == Status.CLOSED) {
+                            debugr.log(Level.DEBUG, "Closed: completing");
+                            outgoing(Utils.EMPTY_BB_LIST, true);
+                            return;
+                        }
+                        if (result.handshaking() && !complete) {
+                            debugr.log(Level.DEBUG, "handshaking");
+                            doHandshake(result, READER);
+                            resumeActivity();
+                            handshaking = true;
+                        } else {
+                            if ((handshakeState.getAndSet(NOT_HANDSHAKING) & ~DOING_TASKS) == HANDSHAKING) {
+                                setALPN();
+                                handshaking = false;
+                                resumeActivity();
+                            }
+                        }
+                    } catch (IOException ex) {
+                        errorCommon(ex);
+                        handleError(ex);
+                    }
+                    if (handshaking && !complete)
+                        return;
+                }
+                if (!complete) {
+                    synchronized (readBufferLock) {
+                        complete = this.completing && !readBuf.hasRemaining();
+                    }
+                }
+                if (complete) {
+                    debugr.log(Level.DEBUG, "completing");
+                    // Complete the alpnCF, if not already complete, regardless of
+                    // whether or not the ALPN is available, there will be no more
+                    // activity.
+                    setALPN();
+                    outgoing(Utils.EMPTY_BB_LIST, true);
+                }
+            } catch (Throwable ex) {
+                errorCommon(ex);
+                handleError(ex);
+            }
+        }
+    }
+
+    /**
+     * Returns a CompletableFuture which completes after all activity
+     * in the delegate is terminated (whether normally or exceptionally).
+     *
+     * @return
+     */
+    public CompletableFuture<Void> completion() {
+        return cf;
+    }
+
+    public interface Monitorable {
+        public String getInfo();
+    }
+
+    public static class Monitor extends Thread {
+        final List<Monitorable> list;
+        static Monitor themon;
+
+        static {
+            themon = new Monitor();
+            themon.start(); // uncomment to enable Monitor
+        }
+
+        Monitor() {
+            super("Monitor");
+            setDaemon(true);
+            list = Collections.synchronizedList(new LinkedList<>());
+        }
+
+        void addTarget(Monitorable o) {
+            list.add(o);
+        }
+
+        public static void add(Monitorable o) {
+            themon.addTarget(o);
+        }
+
+        @Override
+        public void run() {
+            System.out.println("Monitor starting");
+            while (true) {
+                try {Thread.sleep(20*1000); } catch (Exception e) {}
+                synchronized (list) {
+                    for (Monitorable o : list) {
+                        System.out.println(o.getInfo());
+                        System.out.println("-------------------------");
+                    }
+                }
+                System.out.println("--o-o-o-o-o-o-o-o-o-o-o-o-o-o-");
+
+            }
+        }
+    }
+
+    /**
+     * Processing function for outgoing data. Pass it thru SSLEngine.wrap()
+     * Any encrypted buffers generated are passed downstream to be written.
+     * Status codes:
+     *     NEED_UNWRAP: call reader.addData() with empty buffer
+     *     NEED_WRAP: call addData() with empty buffer
+     *     NEED_TASK: delegate task to executor
+     *     BUFFER_OVERFLOW: allocate larger output buffer. Repeat wrap
+     *     BUFFER_UNDERFLOW: shouldn't happen on writing side
+     *     OK: return generated buffers
+     */
+    class Writer extends SubscriberWrapper {
+        final SequentialScheduler scheduler;
+        // queues of buffers received from upstream waiting
+        // to be processed by the SSLEngine
+        final List<ByteBuffer> writeList;
+        final System.Logger debugw =
+            Utils.getDebugLogger(this::dbgString, DEBUG);
+        volatile boolean completing;
+        boolean completed; // only accessed in processData
+
+        class WriterDownstreamPusher extends SequentialScheduler.CompleteRestartableTask {
+            @Override public void run() { processData(); }
+        }
+
+        Writer() {
+            super();
+            writeList = Collections.synchronizedList(new LinkedList<>());
+            scheduler = new SequentialScheduler(new WriterDownstreamPusher());
+        }
+
+        @Override
+        protected void incoming(List<ByteBuffer> buffers, boolean complete) {
+            assert complete ? buffers ==  Utils.EMPTY_BB_LIST : true;
+            assert buffers != Utils.EMPTY_BB_LIST ? complete == false : true;
+            if (complete) {
+                debugw.log(Level.DEBUG, "adding SENTINEL");
+                completing = true;
+                writeList.add(SENTINEL);
+            } else {
+                writeList.addAll(buffers);
+            }
+            debugw.log(Level.DEBUG, () -> "added " + buffers.size()
+                        + " (" + Utils.remaining(buffers)
+                        + " bytes) to the writeList");
+            scheduler.runOrSchedule();
+        }
+
+        public final String dbgString() {
+            return "SSL Writer(" + tubeName + ")";
+        }
+
+        protected void onSubscribe() {
+            doHandshake(EngineResult.INIT, INIT);
+            resumeActivity();
+        }
+
+        void schedule() {
+            scheduler.runOrSchedule();
+        }
+
+        void stop() {
+            debugw.log(Level.DEBUG, "stop");
+            scheduler.stop();
+        }
+
+        @Override
+        public boolean closing() {
+            return closeNotifyReceived();
+        }
+
+        private boolean isCompleting() {
+            return completing;
+        }
+
+        @Override
+        protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
+            if (writeList.size() > 10)
+                return 0;
+            else
+                return super.upstreamWindowUpdate(currentWindow, downstreamQsize);
+        }
+
+        private boolean hsTriggered() {
+            synchronized(writeList) {
+                for (ByteBuffer b : writeList)
+                    if (b == HS_TRIGGER)
+                        return true;
+                return false;
+            }
+        }
+
+        private void processData() {
+            boolean completing = isCompleting();
+
+            try {
+                debugw.log(Level.DEBUG, () -> "processData(" + Utils.remaining(writeList) + ")");
+                while (Utils.remaining(writeList) > 0 || hsTriggered()
+                        || needWrap()) {
+                    ByteBuffer[] outbufs = writeList.toArray(Utils.EMPTY_BB_ARRAY);
+                    EngineResult result = wrapBuffers(outbufs);
+                    debugw.log(Level.DEBUG, "wrapBuffer returned %s", result.result);
+
+                    if (result.status() == Status.CLOSED) {
+                        if (result.bytesProduced() <= 0)
+                            return;
+
+                        if (!completing && !completed) {
+                            completing = this.completing = true;
+                            // There could still be some outgoing data in outbufs.
+                            writeList.add(SENTINEL);
+                        }
+                    }
+
+                    boolean handshaking = false;
+                    if (result.handshaking()) {
+                        debugw.log(Level.DEBUG, "handshaking");
+                        doHandshake(result, WRITER);
+                        handshaking = true;
+                    } else {
+                        if ((handshakeState.getAndSet(NOT_HANDSHAKING) & ~DOING_TASKS) == HANDSHAKING) {
+                            setALPN();
+                            resumeActivity();
+                        }
+                    }
+                    cleanList(writeList); // tidy up the source list
+                    sendResultBytes(result);
+                    if (handshaking && !completing) {
+                        if (writeList.isEmpty() && !result.needUnwrap()) {
+                            writer.addData(HS_TRIGGER);
+                        }
+                        if (needWrap()) continue;
+                        return;
+                    }
+                }
+                if (completing && Utils.remaining(writeList) == 0) {
+                    /*
+                    System.out.println("WRITER DOO 3");
+                    engine.closeOutbound();
+                    EngineResult result = wrapBuffers(Utils.EMPTY_BB_ARRAY);
+                    sendResultBytes(result);
+                    */
+                    if (!completed) {
+                        completed = true;
+                        writeList.clear();
+                        outgoing(Utils.EMPTY_BB_LIST, true);
+                    }
+                    return;
+                }
+                if (writeList.isEmpty() && needWrap()) {
+                    writer.addData(HS_TRIGGER);
+                }
+            } catch (Throwable ex) {
+                errorCommon(ex);
+                handleError(ex);
+            }
+        }
+
+        private boolean needWrap() {
+            return engine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP;
+        }
+
+        private void sendResultBytes(EngineResult result) {
+            if (result.bytesProduced() > 0) {
+                debugw.log(Level.DEBUG, "Sending %d bytes downstream",
+                           result.bytesProduced());
+                outgoing(result.destBuffer, false);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "WRITER: " + super.toString() +
+                    " writeList size " + Integer.toString(writeList.size());
+                    //" writeList: " + writeList.toString();
+        }
+    }
+
+    private void handleError(Throwable t) {
+        debug.log(Level.DEBUG, "handleError", t);
+        cf.completeExceptionally(t);
+        // no-op if already completed
+        alpnCF.completeExceptionally(t);
+        reader.stop();
+        writer.stop();
+    }
+
+    private void normalStop() {
+        reader.stop();
+        writer.stop();
+    }
+
+    private void cleanList(List<ByteBuffer> l) {
+        synchronized (l) {
+            Iterator<ByteBuffer> iter = l.iterator();
+            while (iter.hasNext()) {
+                ByteBuffer b = iter.next();
+                if (!b.hasRemaining() && b != SENTINEL) {
+                    iter.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * States for handshake. We avoid races when accessing/updating the AtomicInt
+     * because updates always schedule an additional call to both the read()
+     * and write() functions.
+     */
+    private static final int NOT_HANDSHAKING = 0;
+    private static final int HANDSHAKING = 1;
+    private static final int INIT = 2;
+    private static final int DOING_TASKS = 4; // bit added to above state
+    private static final ByteBuffer HS_TRIGGER = ByteBuffer.allocate(0);
+
+    private static final int READER = 1;
+    private static final int WRITER = 2;
+
+    private static String states(AtomicInteger state) {
+        int s = state.get();
+        StringBuilder sb = new StringBuilder();
+        int x = s & ~DOING_TASKS;
+        switch (x) {
+            case NOT_HANDSHAKING:
+                sb.append(" NOT_HANDSHAKING ");
+                break;
+            case HANDSHAKING:
+                sb.append(" HANDSHAKING ");
+                break;
+            case INIT:
+                sb.append(" INIT ");
+                break;
+            default:
+                throw new InternalError();
+        }
+        if ((s & DOING_TASKS) > 0)
+            sb.append("|DOING_TASKS");
+        return sb.toString();
+    }
+
+    private void resumeActivity() {
+        reader.schedule();
+        writer.schedule();
+    }
+
+    final AtomicInteger handshakeState;
+    final ConcurrentLinkedQueue<String> stateList = new ConcurrentLinkedQueue<>();
+
+    private void doHandshake(EngineResult r, int caller) {
+        int s = handshakeState.getAndAccumulate(HANDSHAKING, (current, update) -> update | (current & DOING_TASKS));
+        stateList.add(r.handshakeStatus().toString());
+        stateList.add(Integer.toString(caller));
+        switch (r.handshakeStatus()) {
+            case NEED_TASK:
+                if ((s & DOING_TASKS) > 0) // someone else was doing tasks
+                    return;
+                List<Runnable> tasks = obtainTasks();
+                executeTasks(tasks);
+                break;
+            case NEED_WRAP:
+                writer.addData(HS_TRIGGER);
+                break;
+            case NEED_UNWRAP:
+            case NEED_UNWRAP_AGAIN:
+                // do nothing else
+                break;
+            default:
+                throw new InternalError("Unexpected handshake status:"
+                                        + r.handshakeStatus());
+        }
+    }
+
+    private List<Runnable> obtainTasks() {
+        List<Runnable> l = new ArrayList<>();
+        Runnable r;
+        while ((r = engine.getDelegatedTask()) != null) {
+            l.add(r);
+        }
+        return l;
+    }
+
+    private void executeTasks(List<Runnable> tasks) {
+        exec.execute(() -> {
+            try {
+                handshakeState.getAndUpdate((current) -> current | DOING_TASKS);
+                List<Runnable> nextTasks = tasks;
+                do {
+                    nextTasks.forEach(Runnable::run);
+                    if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+                        nextTasks = obtainTasks();
+                    } else {
+                        break;
+                    }
+                } while (true);
+                handshakeState.getAndUpdate((current) -> current & ~DOING_TASKS);
+                writer.addData(HS_TRIGGER);
+                resumeActivity();
+            } catch (Throwable t) {
+                handleError(t);
+            }
+        });
+    }
+
+
+    EngineResult unwrapBuffer(ByteBuffer src) throws IOException {
+        ByteBuffer dst = getAppBuffer();
+        while (true) {
+            SSLEngineResult sslResult = engine.unwrap(src, dst);
+            switch (sslResult.getStatus()) {
+                case BUFFER_OVERFLOW:
+                    // may happen only if app size buffer was changed.
+                    // get it again if app buffer size changed
+                    int appSize = engine.getSession().getApplicationBufferSize();
+                    ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
+                    dst.flip();
+                    b.put(dst);
+                    dst = b;
+                    break;
+                case CLOSED:
+                    return doClosure(new EngineResult(sslResult));
+                case BUFFER_UNDERFLOW:
+                    // handled implicitly by compaction/reallocation of readBuf
+                    return new EngineResult(sslResult);
+                case OK:
+                     dst.flip();
+                     return new EngineResult(sslResult, dst);
+            }
+        }
+    }
+
+    // FIXME: acknowledge a received CLOSE request from peer
+    EngineResult doClosure(EngineResult r) throws IOException {
+        debug.log(Level.DEBUG,
+                "doClosure(%s): %s [isOutboundDone: %s, isInboundDone: %s]",
+                r.result, engine.getHandshakeStatus(),
+                engine.isOutboundDone(), engine.isInboundDone());
+        if (engine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
+            // we have received TLS close_notify and need to send
+            // an acknowledgement back. We're calling doHandshake
+            // to finish the close handshake.
+            if (engine.isInboundDone() && !engine.isOutboundDone()) {
+                debug.log(Level.DEBUG, "doClosure: close_notify received");
+                close_notify_received = true;
+                doHandshake(r, READER);
+            }
+        }
+        return r;
+    }
+
+    /**
+     * Returns the upstream Flow.Subscriber of the reading (incoming) side.
+     * This flow must be given the encrypted data read from upstream (eg socket)
+     * before it is decrypted.
+     */
+    public Flow.Subscriber<List<ByteBuffer>> upstreamReader() {
+        return reader;
+    }
+
+    /**
+     * Returns the upstream Flow.Subscriber of the writing (outgoing) side.
+     * This flow contains the plaintext data before it is encrypted.
+     */
+    public Flow.Subscriber<List<ByteBuffer>> upstreamWriter() {
+        return writer;
+    }
+
+    public boolean resumeReader() {
+        return reader.signalScheduling();
+    }
+
+    public void resetReaderDemand() {
+        reader.resetDownstreamDemand();
+    }
+
+    static class EngineResult {
+        final SSLEngineResult result;
+        final ByteBuffer destBuffer;
+
+        // normal result
+        EngineResult(SSLEngineResult result) {
+            this(result, null);
+        }
+
+        EngineResult(SSLEngineResult result, ByteBuffer destBuffer) {
+            this.result = result;
+            this.destBuffer = destBuffer;
+        }
+
+        // Special result used to trigger handshaking in constructor
+        static EngineResult INIT =
+            new EngineResult(
+                new SSLEngineResult(SSLEngineResult.Status.OK, HandshakeStatus.NEED_WRAP, 0, 0));
+
+        boolean handshaking() {
+            HandshakeStatus s = result.getHandshakeStatus();
+            return s != HandshakeStatus.FINISHED
+                   && s != HandshakeStatus.NOT_HANDSHAKING
+                   && result.getStatus() != Status.CLOSED;
+        }
+
+        boolean needUnwrap() {
+            HandshakeStatus s = result.getHandshakeStatus();
+            return s == HandshakeStatus.NEED_UNWRAP;
+        }
+
+
+        int bytesConsumed() {
+            return result.bytesConsumed();
+        }
+
+        int bytesProduced() {
+            return result.bytesProduced();
+        }
+
+        SSLEngineResult.HandshakeStatus handshakeStatus() {
+            return result.getHandshakeStatus();
+        }
+
+        SSLEngineResult.Status status() {
+            return result.getStatus();
+        }
+    }
+
+    public ByteBuffer getNetBuffer() {
+        return ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
+    }
+
+    private ByteBuffer getAppBuffer() {
+        return ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
+    }
+
+    final String dbgString() {
+        return "SSLFlowDelegate(" + tubeName + ")";
+    }
+
+    @SuppressWarnings("fallthrough")
+    EngineResult wrapBuffers(ByteBuffer[] src) throws SSLException {
+        debug.log(Level.DEBUG, () -> "wrapping "
+                    + Utils.remaining(src) + " bytes");
+        ByteBuffer dst = getNetBuffer();
+        while (true) {
+            SSLEngineResult sslResult = engine.wrap(src, dst);
+            debug.log(Level.DEBUG, () -> "SSLResult: " + sslResult);
+            switch (sslResult.getStatus()) {
+                case BUFFER_OVERFLOW:
+                    // Shouldn't happen. We allocated buffer with packet size
+                    // get it again if net buffer size was changed
+                    debug.log(Level.DEBUG, "BUFFER_OVERFLOW");
+                    int appSize = engine.getSession().getApplicationBufferSize();
+                    ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
+                    dst.flip();
+                    b.put(dst);
+                    dst = b;
+                    break; // try again
+                case CLOSED:
+                    debug.log(Level.DEBUG, "CLOSED");
+                    // fallthrough. There could be some remaining data in dst.
+                    // CLOSED will be handled by the caller.
+                case OK:
+                    dst.flip();
+                    final ByteBuffer dest = dst;
+                    debug.log(Level.DEBUG, () -> "OK => produced: "
+                                           + dest.remaining()
+                                           + " not wrapped: "
+                                           + Utils.remaining(src));
+                    return new EngineResult(sslResult, dest);
+                case BUFFER_UNDERFLOW:
+                    // Shouldn't happen.  Doesn't returns when wrap()
+                    // underflow handled externally
+                    // assert false : "Buffer Underflow";
+                    debug.log(Level.DEBUG, "BUFFER_UNDERFLOW");
+                    return new EngineResult(sslResult);
+                default:
+                    debug.log(Level.DEBUG, "ASSERT");
+                    assert false;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,586 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import jdk.internal.net.http.common.SubscriberWrapper.SchedulingAction;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
+import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED;
+
+/**
+ * An implementation of FlowTube that wraps another FlowTube in an
+ * SSL flow.
+ * <p>
+ * The following diagram shows a typical usage of the SSLTube, where
+ * the SSLTube wraps a SocketTube on the right hand side, and is connected
+ * to an HttpConnection on the left hand side.
+ *
+ * <preformatted>{@code
+ *                  +----------  SSLTube -------------------------+
+ *                  |                                             |
+ *                  |                    +---SSLFlowDelegate---+  |
+ *  HttpConnection  |                    |                     |  |   SocketTube
+ *    read sink  <- SSLSubscriberW.   <- Reader <- upstreamR.() <---- read source
+ *  (a subscriber)  |                    |    \         /      |  |  (a publisher)
+ *                  |                    |     SSLEngine       |  |
+ *  HttpConnection  |                    |    /         \      |  |   SocketTube
+ *  write source -> SSLSubscriptionW. -> upstreamW.() -> Writer ----> write sink
+ *  (a publisher)   |                    |                     |  |  (a subscriber)
+ *                  |                    +---------------------+  |
+ *                  |                                             |
+ *                  +---------------------------------------------+
+ * }</preformatted>
+ */
+public class SSLTube implements FlowTube {
+
+    static final boolean DEBUG = Utils.DEBUG; // revisit: temporary developer's flag.
+    final System.Logger debug =
+            Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    private final FlowTube tube;
+    private final SSLSubscriberWrapper readSubscriber;
+    private final SSLSubscriptionWrapper writeSubscription;
+    private final SSLFlowDelegate sslDelegate;
+    private final SSLEngine engine;
+    private volatile boolean finished;
+
+    public SSLTube(SSLEngine engine, Executor executor, FlowTube tube) {
+        Objects.requireNonNull(engine);
+        Objects.requireNonNull(executor);
+        this.tube = Objects.requireNonNull(tube);
+        writeSubscription = new SSLSubscriptionWrapper();
+        readSubscriber = new SSLSubscriberWrapper();
+        this.engine = engine;
+        sslDelegate = new SSLTubeFlowDelegate(engine,
+                                              executor,
+                                              readSubscriber,
+                                              tube);
+    }
+
+    final class SSLTubeFlowDelegate extends SSLFlowDelegate {
+        SSLTubeFlowDelegate(SSLEngine engine, Executor executor,
+                            SSLSubscriberWrapper readSubscriber,
+                            FlowTube tube) {
+            super(engine, executor, readSubscriber, tube);
+        }
+        protected SchedulingAction enterReadScheduling() {
+            readSubscriber.processPendingSubscriber();
+            return SchedulingAction.CONTINUE;
+        }
+        void connect(Flow.Subscriber<? super List<ByteBuffer>> downReader,
+                     Flow.Subscriber<? super List<ByteBuffer>> downWriter) {
+            assert downWriter == tube;
+            assert downReader == readSubscriber;
+
+            // Connect the read sink first. That's the left-hand side
+            // downstream subscriber from the HttpConnection (or more
+            // accurately, the SSLSubscriberWrapper that will wrap it
+            // when SSLTube::connectFlows is called.
+            reader.subscribe(downReader);
+
+            // Connect the right hand side tube (the socket tube).
+            //
+            // The SSLFlowDelegate.writer publishes ByteBuffer to
+            // the SocketTube for writing on the socket, and the
+            // SSLFlowDelegate::upstreamReader subscribes to the
+            // SocketTube to receive ByteBuffers read from the socket.
+            //
+            // Basically this method is equivalent to:
+            //     // connect the read source:
+            //     //   subscribe the SSLFlowDelegate upstream reader
+            //     //   to the socket tube publisher.
+            //     tube.subscribe(upstreamReader());
+            //     // connect the write sink:
+            //     //   subscribe the socket tube write subscriber
+            //     //   with the SSLFlowDelegate downstream writer.
+            //     writer.subscribe(tube);
+            tube.connectFlows(FlowTube.asTubePublisher(writer),
+                              FlowTube.asTubeSubscriber(upstreamReader()));
+
+            // Finally connect the write source. That's the left
+            // hand side publisher which will push ByteBuffer for
+            // writing and encryption to the SSLFlowDelegate.
+            // The writeSubscription is in fact the SSLSubscriptionWrapper
+            // that will wrap the subscription provided by the
+            // HttpConnection publisher when SSLTube::connectFlows
+            // is called.
+            upstreamWriter().onSubscribe(writeSubscription);
+        }
+    }
+
+    public CompletableFuture<String> getALPN() {
+        return sslDelegate.alpn();
+    }
+
+    @Override
+    public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
+        readSubscriber.dropSubscription();
+        readSubscriber.setDelegate(s);
+        s.onSubscribe(readSubscription);
+    }
+
+    /**
+     * Tells whether, or not, this FlowTube has finished receiving data.
+     *
+     * @return true when one of this FlowTube Subscriber's OnError or onComplete
+     * methods have been invoked
+     */
+    @Override
+    public boolean isFinished() {
+        return finished;
+    }
+
+    private volatile Flow.Subscription readSubscription;
+
+    // The DelegateWrapper wraps a subscribed {@code Flow.Subscriber} and
+    // tracks the subscriber's state. In particular it makes sure that
+    // onComplete/onError are not called before onSubscribed.
+    final static class DelegateWrapper implements FlowTube.TubeSubscriber {
+        private final FlowTube.TubeSubscriber delegate;
+        private final System.Logger debug;
+        volatile boolean subscribedCalled;
+        volatile boolean subscribedDone;
+        volatile boolean completed;
+        volatile Throwable error;
+        DelegateWrapper(Flow.Subscriber<? super List<ByteBuffer>> delegate,
+                        System.Logger debug) {
+            this.delegate = FlowTube.asTubeSubscriber(delegate);
+            this.debug = debug;
+        }
+
+        @Override
+        public void dropSubscription() {
+            if (subscribedCalled && !completed) {
+                delegate.dropSubscription();
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            assert subscribedCalled;
+            delegate.onNext(item);
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            onSubscribe(delegate::onSubscribe, subscription);
+        }
+
+        private void onSubscribe(Consumer<Flow.Subscription> method,
+                                 Flow.Subscription subscription) {
+            subscribedCalled = true;
+            method.accept(subscription);
+            Throwable x;
+            boolean finished;
+            synchronized (this) {
+                subscribedDone = true;
+                x = error;
+                finished = completed;
+            }
+            if (x != null) {
+                debug.log(Level.DEBUG,
+                          "Subscriber completed before subscribe: forwarding %s",
+                          (Object)x);
+                delegate.onError(x);
+            } else if (finished) {
+                debug.log(Level.DEBUG,
+                          "Subscriber completed before subscribe: calling onComplete()");
+                delegate.onComplete();
+            }
+        }
+
+        @Override
+        public void onError(Throwable t) {
+            if (completed) {
+                debug.log(Level.DEBUG,
+                          "Subscriber already completed: ignoring %s",
+                          (Object)t);
+                return;
+            }
+            boolean subscribed;
+            synchronized (this) {
+                if (completed) return;
+                error = t;
+                completed = true;
+                subscribed = subscribedDone;
+            }
+            if (subscribed) {
+                delegate.onError(t);
+            } else {
+                debug.log(Level.DEBUG,
+                          "Subscriber not yet subscribed: stored %s",
+                          (Object)t);
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            if (completed) return;
+            boolean subscribed;
+            synchronized (this) {
+                if (completed) return;
+                completed = true;
+                subscribed = subscribedDone;
+            }
+            if (subscribed) {
+                debug.log(Level.DEBUG, "DelegateWrapper: completing subscriber");
+                delegate.onComplete();
+            } else {
+                debug.log(Level.DEBUG,
+                          "Subscriber not yet subscribed: stored completed=true");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "DelegateWrapper:" + delegate.toString();
+        }
+
+    }
+
+    // Used to read data from the SSLTube.
+    final class SSLSubscriberWrapper implements FlowTube.TubeSubscriber {
+        private AtomicReference<DelegateWrapper> pendingDelegate =
+                new AtomicReference<>();
+        private volatile DelegateWrapper subscribed;
+        private volatile boolean onCompleteReceived;
+        private final AtomicReference<Throwable> errorRef
+                = new AtomicReference<>();
+
+        // setDelegate can be called asynchronously when the SSLTube flow
+        // is connected. At this time the permanent subscriber (this class)
+        // may already be subscribed (readSubscription != null) or not.
+        // 1. If it's already subscribed (readSubscription != null), we
+        //    are going to signal the SSLFlowDelegate reader, and make sure
+        //    onSubscribed is called within the reader flow
+        // 2. If it's not yet subscribed (readSubscription == null), then
+        //    we're going to wait for onSubscribe to be called.
+        //
+        void setDelegate(Flow.Subscriber<? super List<ByteBuffer>> delegate) {
+            debug.log(Level.DEBUG, "SSLSubscriberWrapper (reader) got delegate: %s",
+                      delegate);
+            assert delegate != null;
+            DelegateWrapper delegateWrapper = new DelegateWrapper(delegate, debug);
+            DelegateWrapper previous;
+            Flow.Subscription subscription;
+            boolean handleNow;
+            synchronized (this) {
+                previous = pendingDelegate.getAndSet(delegateWrapper);
+                subscription = readSubscription;
+                handleNow = this.errorRef.get() != null || finished;
+            }
+            if (previous != null) {
+                previous.dropSubscription();
+            }
+            if (subscription == null) {
+                debug.log(Level.DEBUG, "SSLSubscriberWrapper (reader) no subscription yet");
+                return;
+            }
+            if (handleNow || !sslDelegate.resumeReader()) {
+                processPendingSubscriber();
+            }
+        }
+
+        // Can be called outside of the flow if an error has already been
+        // raise. Otherwise, must be called within the SSLFlowDelegate
+        // downstream reader flow.
+        // If there is a subscription, and if there is a pending delegate,
+        // calls dropSubscription() on the previous delegate (if any),
+        // then subscribe the pending delegate.
+        void processPendingSubscriber() {
+            Flow.Subscription subscription;
+            DelegateWrapper delegateWrapper, previous;
+            synchronized (this) {
+                delegateWrapper = pendingDelegate.get();
+                if (delegateWrapper == null) return;
+                subscription = readSubscription;
+                previous = subscribed;
+            }
+            if (subscription == null) {
+                debug.log(Level.DEBUG,
+                         "SSLSubscriberWrapper (reader) %s",
+                         "processPendingSubscriber: no subscription yet");
+                return;
+            }
+            delegateWrapper = pendingDelegate.getAndSet(null);
+            if (delegateWrapper == null) return;
+            if (previous != null) {
+                previous.dropSubscription();
+            }
+            onNewSubscription(delegateWrapper, subscription);
+        }
+
+        @Override
+        public void dropSubscription() {
+            DelegateWrapper subscriberImpl = subscribed;
+            if (subscriberImpl != null) {
+                subscriberImpl.dropSubscription();
+            }
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            debug.log(Level.DEBUG,
+                      "SSLSubscriberWrapper (reader) onSubscribe(%s)",
+                      subscription);
+            onSubscribeImpl(subscription);
+        }
+
+        // called in the reader flow, from onSubscribe.
+        private void onSubscribeImpl(Flow.Subscription subscription) {
+            assert subscription != null;
+            DelegateWrapper subscriberImpl, pending;
+            synchronized (this) {
+                readSubscription = subscription;
+                subscriberImpl = subscribed;
+                pending = pendingDelegate.get();
+            }
+
+            if (subscriberImpl == null && pending == null) {
+                debug.log(Level.DEBUG,
+                      "SSLSubscriberWrapper (reader) onSubscribeImpl: %s",
+                      "no delegate yet");
+                return;
+            }
+
+            if (pending == null) {
+                // There is no pending delegate, but we have a previously
+                // subscribed delegate. This is obviously a re-subscribe.
+                // We are in the downstream reader flow, so we should call
+                // onSubscribe directly.
+                debug.log(Level.DEBUG,
+                      "SSLSubscriberWrapper (reader) onSubscribeImpl: %s",
+                      "resubscribing");
+                onNewSubscription(subscriberImpl, subscription);
+            } else {
+                // We have some pending subscriber: subscribe it now that we have
+                // a subscription. If we already had a previous delegate then
+                // it will get a dropSubscription().
+                debug.log(Level.DEBUG,
+                      "SSLSubscriberWrapper (reader) onSubscribeImpl: %s",
+                      "subscribing pending");
+                processPendingSubscriber();
+            }
+        }
+
+        private void onNewSubscription(DelegateWrapper subscriberImpl,
+                                       Flow.Subscription subscription) {
+            assert subscriberImpl != null;
+            assert subscription != null;
+
+            Throwable failed;
+            boolean completed;
+            // reset any demand that may have been made by the previous
+            // subscriber
+            sslDelegate.resetReaderDemand();
+            // send the subscription to the subscriber.
+            subscriberImpl.onSubscribe(subscription);
+
+            // The following twisted logic is just here that we don't invoke
+            // onError before onSubscribe. It also prevents race conditions
+            // if onError is invoked concurrently with setDelegate.
+            synchronized (this) {
+                failed = this.errorRef.get();
+                completed = finished;
+                subscribed = subscriberImpl;
+            }
+            if (failed != null) {
+                subscriberImpl.onError(failed);
+            } else if (completed) {
+                subscriberImpl.onComplete();
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            subscribed.onNext(item);
+        }
+
+        public void onErrorImpl(Throwable throwable) {
+            // The following twisted logic is just here that we don't invoke
+            // onError before onSubscribe. It also prevents race conditions
+            // if onError is invoked concurrently with setDelegate.
+            // See setDelegate.
+
+            errorRef.compareAndSet(null, throwable);
+            Throwable failed = errorRef.get();
+            finished = true;
+            debug.log(Level.DEBUG, "%s: onErrorImpl: %s", this, throwable);
+            DelegateWrapper subscriberImpl;
+            synchronized (this) {
+                subscriberImpl = subscribed;
+            }
+            if (subscriberImpl != null) {
+                subscriberImpl.onError(failed);
+            } else {
+                debug.log(Level.DEBUG, "%s: delegate null, stored %s", this, failed);
+            }
+            // now if we have any pending subscriber, we should forward
+            // the error to them immediately as the read scheduler will
+            // already be stopped.
+            processPendingSubscriber();
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            assert !finished && !onCompleteReceived;
+            onErrorImpl(throwable);
+        }
+
+        private boolean handshaking() {
+            HandshakeStatus hs = engine.getHandshakeStatus();
+            return !(hs == NOT_HANDSHAKING || hs == FINISHED);
+        }
+
+        private boolean handshakeFailed() {
+            // sslDelegate can be null if we reach here
+            // during the initial handshake, as that happens
+            // within the SSLFlowDelegate constructor.
+            // In that case we will want to raise an exception.
+            return handshaking()
+                    && (sslDelegate == null
+                    || !sslDelegate.closeNotifyReceived());
+        }
+
+        @Override
+        public void onComplete() {
+            assert !finished && !onCompleteReceived;
+            onCompleteReceived = true;
+            DelegateWrapper subscriberImpl;
+            synchronized(this) {
+                subscriberImpl = subscribed;
+            }
+
+            if (handshakeFailed()) {
+                debug.log(Level.DEBUG,
+                        "handshake: %s, inbound done: %s outbound done: %s",
+                        engine.getHandshakeStatus(),
+                        engine.isInboundDone(),
+                        engine.isOutboundDone());
+                onErrorImpl(new SSLHandshakeException(
+                        "Remote host terminated the handshake"));
+            } else if (subscriberImpl != null) {
+                finished = true;
+                subscriberImpl.onComplete();
+            }
+            // now if we have any pending subscriber, we should complete
+            // them immediately as the read scheduler will already be stopped.
+            processPendingSubscriber();
+        }
+    }
+
+    @Override
+    public void connectFlows(TubePublisher writePub,
+                             TubeSubscriber readSub) {
+        debug.log(Level.DEBUG, "connecting flows");
+        readSubscriber.setDelegate(readSub);
+        writePub.subscribe(this);
+    }
+
+    /** Outstanding write demand from the SSL Flow Delegate. */
+    private final Demand writeDemand = new Demand();
+
+    final class SSLSubscriptionWrapper implements Flow.Subscription {
+
+        volatile Flow.Subscription delegate;
+
+        void setSubscription(Flow.Subscription sub) {
+            long demand = writeDemand.get(); // FIXME: isn't it a racy way of passing the demand?
+            delegate = sub;
+            debug.log(Level.DEBUG, "setSubscription: demand=%d", demand);
+            if (demand > 0)
+                sub.request(demand);
+        }
+
+        @Override
+        public void request(long n) {
+            writeDemand.increase(n);
+            debug.log(Level.DEBUG, "request: n=%d", n);
+            Flow.Subscription sub = delegate;
+            if (sub != null && n > 0) {
+                sub.request(n);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            // TODO:  no-op or error?
+        }
+    }
+
+    /* Subscriber - writing side */
+    @Override
+    public void onSubscribe(Flow.Subscription subscription) {
+        Objects.requireNonNull(subscription);
+        Flow.Subscription x = writeSubscription.delegate;
+        if (x != null)
+            x.cancel();
+
+        writeSubscription.setSubscription(subscription);
+    }
+
+    @Override
+    public void onNext(List<ByteBuffer> item) {
+        Objects.requireNonNull(item);
+        boolean decremented = writeDemand.tryDecrement();
+        assert decremented : "Unexpected writeDemand: ";
+        debug.log(Level.DEBUG,
+                "sending %d  buffers to SSL flow delegate", item.size());
+        sslDelegate.upstreamWriter().onNext(item);
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        Objects.requireNonNull(throwable);
+        sslDelegate.upstreamWriter().onError(throwable);
+    }
+
+    @Override
+    public void onComplete() {
+        sslDelegate.upstreamWriter().onComplete();
+    }
+
+    @Override
+    public String toString() {
+        return dbgString();
+    }
+
+    final String dbgString() {
+        return "SSLTube(" + tube + ")";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SequentialScheduler.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,362 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A scheduler of ( repeatable ) tasks that MUST be run sequentially.
+ *
+ * <p> This class can be used as a synchronization aid that assists a number of
+ * parties in running a task in a mutually exclusive fashion.
+ *
+ * <p> To run the task, a party invokes {@code runOrSchedule}. To permanently
+ * prevent the task from subsequent runs, the party invokes {@code stop}.
+ *
+ * <p> The parties can, but do not have to, operate in different threads.
+ *
+ * <p> The task can be either synchronous ( completes when its {@code run}
+ * method returns ), or asynchronous ( completed when its
+ * {@code DeferredCompleter} is explicitly completed ).
+ *
+ * <p> The next run of the task will not begin until the previous run has
+ * finished.
+ *
+ * <p> The task may invoke {@code runOrSchedule} itself, which may be a normal
+ * situation.
+ */
+public final class SequentialScheduler {
+
+    /*
+       Since the task is fixed and known beforehand, no blocking synchronization
+       (locks, queues, etc.) is required. The job can be done solely using
+       nonblocking primitives.
+
+       The machinery below addresses two problems:
+
+         1. Running the task in a sequential order (no concurrent runs):
+
+                begin, end, begin, end...
+
+         2. Avoiding indefinite recursion:
+
+                begin
+                  end
+                    begin
+                      end
+                        ...
+
+       Problem #1 is solved with a finite state machine with 4 states:
+
+           BEGIN, AGAIN, END, and STOP.
+
+       Problem #2 is solved with a "state modifier" OFFLOAD.
+
+       Parties invoke `runOrSchedule()` to signal the task must run. A party
+       that has invoked `runOrSchedule()` either begins the task or exploits the
+       party that is either beginning the task or ending it.
+
+       The party that is trying to end the task either ends it or begins it
+       again.
+
+       To avoid indefinite recursion, before re-running the task the
+       TryEndDeferredCompleter sets the OFFLOAD bit, signalling to its "child"
+       TryEndDeferredCompleter that this ("parent") TryEndDeferredCompleter is
+       available and the "child" must offload the task on to the "parent". Then
+       a race begins. Whichever invocation of TryEndDeferredCompleter.complete
+       manages to unset OFFLOAD bit first does not do the work.
+
+       There is at most 1 thread that is beginning the task and at most 2
+       threads that are trying to end it: "parent" and "child". In case of a
+       synchronous task "parent" and "child" are the same thread.
+     */
+
+    /**
+     * An interface to signal the completion of a {@link RestartableTask}.
+     *
+     * <p> The invocation of {@code complete} completes the task. The invocation
+     * of {@code complete} may restart the task, if an attempt has previously
+     * been made to run the task while it was already running.
+     *
+     * @apiNote {@code DeferredCompleter} is useful when a task is not necessary
+     * complete when its {@code run} method returns, but will complete at a
+     * later time, and maybe in different thread. This type exists for
+     * readability purposes at use-sites only.
+     */
+    public static abstract class DeferredCompleter {
+
+        /** Extensible from this (outer) class ONLY. */
+        private DeferredCompleter() { }
+
+        /** Completes the task. Must be called once, and once only. */
+        public abstract void complete();
+    }
+
+    /**
+     * A restartable task.
+     */
+    @FunctionalInterface
+    public interface RestartableTask {
+
+        /**
+         * The body of the task.
+         *
+         * @param taskCompleter
+         *         A completer that must be invoked once, and only once,
+         *         when this task is logically finished
+         */
+        void run(DeferredCompleter taskCompleter);
+    }
+
+    /**
+     * A simple and self-contained task that completes once its {@code run}
+     * method returns.
+     */
+    public static abstract class CompleteRestartableTask
+        implements RestartableTask
+    {
+        @Override
+        public final void run(DeferredCompleter taskCompleter) {
+            try {
+                run();
+            } finally {
+                taskCompleter.complete();
+            }
+        }
+
+        /** The body of the task. */
+        protected abstract void run();
+    }
+
+    /**
+     * A task that runs its main loop within a synchronized block to provide
+     * memory visibility between runs. Since the main loop can't run concurrently,
+     * the lock shouldn't be contended and no deadlock should ever be possible.
+     */
+    public static final class SynchronizedRestartableTask
+            extends CompleteRestartableTask {
+
+        private final Runnable mainLoop;
+        private final Object lock = new Object();
+
+        public SynchronizedRestartableTask(Runnable mainLoop) {
+            this.mainLoop = mainLoop;
+        }
+
+        @Override
+        protected void run() {
+            synchronized(lock) {
+                mainLoop.run();
+            }
+        }
+    }
+
+    private static final int OFFLOAD =  1;
+    private static final int AGAIN   =  2;
+    private static final int BEGIN   =  4;
+    private static final int STOP    =  8;
+    private static final int END     = 16;
+
+    private final AtomicInteger state = new AtomicInteger(END);
+    private final RestartableTask restartableTask;
+    private final DeferredCompleter completer;
+    private final SchedulableTask schedulableTask;
+
+    /**
+     * An auxiliary task that starts the restartable task:
+     * {@code restartableTask.run(completer)}.
+     */
+    private final class SchedulableTask implements Runnable {
+        @Override
+        public void run() {
+            restartableTask.run(completer);
+        }
+    }
+
+    public SequentialScheduler(RestartableTask restartableTask) {
+        this.restartableTask = requireNonNull(restartableTask);
+        this.completer = new TryEndDeferredCompleter();
+        this.schedulableTask = new SchedulableTask();
+    }
+
+    /**
+     * Runs or schedules the task to be run.
+     *
+     * @implSpec The recursion which is possible here must be bounded:
+     *
+     *  <pre>{@code
+     *     this.runOrSchedule()
+     *         completer.complete()
+     *             this.runOrSchedule()
+     *                 ...
+     * }</pre>
+     *
+     * @implNote The recursion in this implementation has the maximum
+     * depth of 1.
+     */
+    public void runOrSchedule() {
+        runOrSchedule(schedulableTask, null);
+    }
+
+    /**
+     * Executes or schedules the task to be executed in the provided executor.
+     *
+     * <p> This method can be used when potential executing from a calling
+     * thread is not desirable.
+     *
+     * @param executor
+     *         An executor in which to execute the task, if the task needs
+     *         to be executed.
+     *
+     * @apiNote The given executor can be {@code null} in which case calling
+     * {@code runOrSchedule(null)} is strictly equivalent to calling
+     * {@code runOrSchedule()}.
+     */
+    public void runOrSchedule(Executor executor) {
+        runOrSchedule(schedulableTask, executor);
+    }
+
+    private void runOrSchedule(SchedulableTask task, Executor executor) {
+        while (true) {
+            int s = state.get();
+            if (s == END) {
+                if (state.compareAndSet(END, BEGIN)) {
+                    break;
+                }
+            } else if ((s & BEGIN) != 0) {
+                // Tries to change the state to AGAIN, preserving OFFLOAD bit
+                if (state.compareAndSet(s, AGAIN | (s & OFFLOAD))) {
+                    return;
+                }
+            } else if ((s & AGAIN) != 0 || s == STOP) {
+                /* In the case of AGAIN the scheduler does not provide
+                   happens-before relationship between actions prior to
+                   runOrSchedule() and actions that happen in task.run().
+                   The reason is that no volatile write is done in this case,
+                   and the call piggybacks on the call that has actually set
+                   AGAIN state. */
+                return;
+            } else {
+                // Non-existent state, or the one that cannot be offloaded
+                throw new InternalError(String.valueOf(s));
+            }
+        }
+        if (executor == null) {
+            task.run();
+        } else {
+            executor.execute(task);
+        }
+    }
+
+    /** The only concrete {@code DeferredCompleter} implementation. */
+    private class TryEndDeferredCompleter extends DeferredCompleter {
+
+        @Override
+        public void complete() {
+            while (true) {
+                int s;
+                while (((s = state.get()) & OFFLOAD) != 0) {
+                    // Tries to offload ending of the task to the parent
+                    if (state.compareAndSet(s, s & ~OFFLOAD)) {
+                        return;
+                    }
+                }
+                while (true) {
+                    if ((s & OFFLOAD) != 0) {
+                        /* OFFLOAD bit can never be observed here. Otherwise
+                           it would mean there is another invocation of
+                           "complete" that can run the task. */
+                        throw new InternalError(String.valueOf(s));
+                    }
+                    if (s == BEGIN) {
+                        if (state.compareAndSet(BEGIN, END)) {
+                            return;
+                        }
+                    } else if (s == AGAIN) {
+                        if (state.compareAndSet(AGAIN, BEGIN | OFFLOAD)) {
+                            break;
+                        }
+                    } else if (s == STOP) {
+                        return;
+                    } else if (s == END) {
+                        throw new IllegalStateException("Duplicate completion");
+                    } else {
+                        // Non-existent state
+                        throw new InternalError(String.valueOf(s));
+                    }
+                    s = state.get();
+                }
+                restartableTask.run(completer);
+            }
+        }
+    }
+
+    /**
+     * Tells whether, or not, this scheduler has been permanently stopped.
+     *
+     * <p> Should be used from inside the task to poll the status of the
+     * scheduler, pretty much the same way as it is done for threads:
+     * <pre>{@code
+     *     if (!Thread.currentThread().isInterrupted()) {
+     *         ...
+     *     }
+     * }</pre>
+     */
+    public boolean isStopped() {
+        return state.get() == STOP;
+    }
+
+    /**
+     * Stops this scheduler.  Subsequent invocations of {@code runOrSchedule}
+     * are effectively no-ops.
+     *
+     * <p> If the task has already begun, this invocation will not affect it,
+     * unless the task itself uses {@code isStopped()} method to check the state
+     * of the handler.
+     */
+    public void stop() {
+        state.set(STOP);
+    }
+
+    /**
+     * Returns a new {@code SequentialScheduler} that executes the provided
+     * {@code mainLoop} from within a {@link SynchronizedRestartableTask}.
+     *
+     * @apiNote This is equivalent to calling
+     * {@code new SequentialScheduler(new SynchronizedRestartableTask(mainLoop))}
+     * The main loop must not perform any blocking operation.
+     *
+     * @param mainLoop The main loop of the new sequential scheduler
+     * @return a new {@code SequentialScheduler} that executes the provided
+     * {@code mainLoop} from within a {@link SynchronizedRestartableTask}.
+     */
+    public static SequentialScheduler synchronizedScheduler(Runnable mainLoop) {
+        return new SequentialScheduler(new SynchronizedRestartableTask(mainLoop));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriberWrapper.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,461 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.io.Closeable;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A wrapper for a Flow.Subscriber. This wrapper delivers data to the wrapped
+ * Subscriber which is supplied to the constructor. This class takes care of
+ * downstream flow control automatically and upstream flow control automatically
+ * by default.
+ * <p>
+ * Processing is done by implementing the {@link #incoming(List, boolean)} method
+ * which supplies buffers from upstream. This method (or any other method)
+ * can then call the outgoing() method to deliver processed buffers downstream.
+ * <p>
+ * Upstream error signals are delivered downstream directly. Cancellation from
+ * downstream is also propagated upstream immediately.
+ * <p>
+ * Each SubscriberWrapper has a {@link java.util.concurrent.CompletableFuture}{@code <Void>}
+ * which propagates completion/errors from downstream to upstream. Normal completion
+ * can only occur after onComplete() is called, but errors can be propagated upwards
+ * at any time.
+ */
+public abstract class SubscriberWrapper
+    implements FlowTube.TubeSubscriber, Closeable, Flow.Processor<List<ByteBuffer>,List<ByteBuffer>>
+                // TODO: SSLTube Subscriber will never change? Does this really need to be a TS?
+{
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    final System.Logger logger =
+            Utils.getDebugLogger(this::dbgString, DEBUG);
+
+    public enum SchedulingAction { CONTINUE, RETURN, RESCHEDULE }
+
+    volatile Flow.Subscription upstreamSubscription;
+    final SubscriptionBase downstreamSubscription;
+    volatile boolean upstreamCompleted;
+    volatile boolean downstreamCompleted;
+    volatile boolean completionAcknowledged;
+    private volatile Subscriber<? super List<ByteBuffer>> downstreamSubscriber;
+    // processed byte to send to the downstream subscriber.
+    private final ConcurrentLinkedQueue<List<ByteBuffer>> outputQ;
+    private final CompletableFuture<Void> cf;
+    private final SequentialScheduler pushScheduler;
+    private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+    final AtomicLong upstreamWindow = new AtomicLong(0);
+
+    /**
+     * Wraps the given downstream subscriber. For each call to {@link
+     * #onNext(List<ByteBuffer>) } the given filter function is invoked
+     * and the list (if not empty) returned is passed downstream.
+     *
+     * A {@code CompletableFuture} is supplied which can be used to signal an
+     * error from downstream and which terminates the wrapper or which signals
+     * completion of downstream activity which can be propagated upstream. Error
+     * completion can be signaled at any time, but normal completion must not be
+     * signaled before onComplete() is called.
+     */
+    public SubscriberWrapper()
+    {
+        this.outputQ = new ConcurrentLinkedQueue<>();
+        this.cf = new MinimalFuture<>();
+        this.pushScheduler =
+                SequentialScheduler.synchronizedScheduler(new DownstreamPusher());
+        this.downstreamSubscription = new SubscriptionBase(pushScheduler,
+                                                           this::downstreamCompletion);
+    }
+
+    @Override
+    public final void subscribe(Subscriber<?  super List<ByteBuffer>> downstreamSubscriber) {
+        Objects.requireNonNull(downstreamSubscriber);
+        this.downstreamSubscriber = downstreamSubscriber;
+    }
+
+    /**
+     * Wraps the given downstream wrapper in this. For each call to
+     * {@link #onNext(List<ByteBuffer>) } the incoming() method is called.
+     *
+     * The {@code downstreamCF} from the downstream wrapper is linked to this
+     * wrappers notifier.
+     *
+     * @param downstreamWrapper downstream destination
+     */
+    public SubscriberWrapper(Subscriber<? super List<ByteBuffer>> downstreamWrapper)
+    {
+        this();
+        subscribe(downstreamWrapper);
+    }
+
+    /**
+     * Delivers data to be processed by this wrapper. Generated data to be sent
+     * downstream, must be provided to the {@link #outgoing(List, boolean)}}
+     * method.
+     *
+     * @param buffers a List of ByteBuffers.
+     * @param complete if true then no more data will be added to the list
+     */
+    protected abstract void incoming(List<ByteBuffer> buffers, boolean complete);
+
+    /**
+     * This method is called to determine the window size to use at any time. The
+     * current window is supplied together with the current downstream queue size.
+     * {@code 0} should be returned if no change is
+     * required or a positive integer which will be added to the current window.
+     * The default implementation maintains a downstream queue size of no greater
+     * than 5. The method can be overridden if required.
+     *
+     * @param currentWindow the current upstream subscription window
+     * @param downstreamQsize the current number of buffers waiting to be sent
+     *                        downstream
+     *
+     * @return value to add to currentWindow
+     */
+    protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
+        if (downstreamQsize > 5) {
+            return 0;
+        }
+
+        if (currentWindow == 0) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Override this if anything needs to be done after the upstream subscriber
+     * has subscribed
+     */
+    protected void onSubscribe() {
+    }
+
+    /**
+     * Override this if anything needs to be done before checking for error
+     * and processing the input queue.
+     * @return
+     */
+    protected SchedulingAction enterScheduling() {
+        return SchedulingAction.CONTINUE;
+    }
+
+    protected boolean signalScheduling() {
+        if (downstreamCompleted || pushScheduler.isStopped()) {
+            return false;
+        }
+        pushScheduler.runOrSchedule();
+        return true;
+    }
+
+    /**
+     * Delivers buffers of data downstream. After incoming()
+     * has been called complete == true signifying completion of the upstream
+     * subscription, data may continue to be delivered, up to when outgoing() is
+     * called complete == true, after which, the downstream subscription is
+     * completed.
+     *
+     * It's an error to call outgoing() with complete = true if incoming() has
+     * not previously been called with it.
+     */
+    public void outgoing(ByteBuffer buffer, boolean complete) {
+        Objects.requireNonNull(buffer);
+        assert !complete || !buffer.hasRemaining();
+        outgoing(List.of(buffer), complete);
+    }
+
+    /**
+     * Sometime it might be necessary to complete the downstream subscriber
+     * before the upstream completes. For instance, when an SSL server
+     * sends a notify_close. In that case we should let the outgoing
+     * complete before upstream us completed.
+     * @return true, may be overridden by subclasses.
+     */
+    public boolean closing() {
+        return false;
+    }
+
+    public void outgoing(List<ByteBuffer> buffers, boolean complete) {
+        Objects.requireNonNull(buffers);
+        if (complete) {
+            assert Utils.remaining(buffers) == 0;
+            boolean closing = closing();
+            logger.log(Level.DEBUG,
+                    "completionAcknowledged upstreamCompleted:%s, downstreamCompleted:%s, closing:%s",
+                    upstreamCompleted, downstreamCompleted, closing);
+            if (!upstreamCompleted && !closing)
+                throw new IllegalStateException("upstream not completed");
+            completionAcknowledged = true;
+        } else {
+            logger.log(Level.DEBUG, () -> "Adding "
+                                   + Utils.remaining(buffers)
+                                   + " to outputQ queue");
+            outputQ.add(buffers);
+        }
+        logger.log(Level.DEBUG, () -> "pushScheduler "
+                   + (pushScheduler.isStopped() ? " is stopped!" : " is alive"));
+        pushScheduler.runOrSchedule();
+    }
+
+    /**
+     * Returns a CompletableFuture which completes when this wrapper completes.
+     * Normal completion happens with the following steps (in order):
+     *   1. onComplete() is called
+     *   2. incoming() called with complete = true
+     *   3. outgoing() may continue to be called normally
+     *   4. outgoing called with complete = true
+     *   5. downstream subscriber is called onComplete()
+     *
+     * If the subscription is canceled or onComplete() is invoked the
+     * CompletableFuture completes exceptionally. Exceptional completion
+     * also occurs if downstreamCF completes exceptionally.
+     */
+    public CompletableFuture<Void> completion() {
+        return cf;
+    }
+
+    /**
+     * Invoked whenever it 'may' be possible to push buffers downstream.
+     */
+    class DownstreamPusher implements Runnable {
+        @Override
+        public void run() {
+            try {
+                run1();
+            } catch (Throwable t) {
+                errorCommon(t);
+            }
+        }
+
+        private void run1() {
+            if (downstreamCompleted) {
+                logger.log(Level.DEBUG, "DownstreamPusher: downstream is already completed");
+                return;
+            }
+            switch (enterScheduling()) {
+                case CONTINUE: break;
+                case RESCHEDULE: pushScheduler.runOrSchedule(); return;
+                case RETURN: return;
+                default:
+                    errorRef.compareAndSet(null,
+                            new InternalError("unknown scheduling command"));
+                    break;
+            }
+            // If there was an error, send it downstream.
+            Throwable error = errorRef.get();
+            if (error != null) {
+                synchronized(this) {
+                    if (downstreamCompleted) return;
+                    downstreamCompleted = true;
+                }
+                logger.log(Level.DEBUG,
+                        () -> "DownstreamPusher: forwarding error downstream: " + error);
+                pushScheduler.stop();
+                outputQ.clear();
+                downstreamSubscriber.onError(error);
+                return;
+            }
+
+            // OK - no error, let's proceed
+            if (!outputQ.isEmpty()) {
+                logger.log(Level.DEBUG,
+                    "DownstreamPusher: queue not empty, downstreamSubscription: %s",
+                     downstreamSubscription);
+            } else {
+                logger.log(Level.DEBUG,
+                       "DownstreamPusher: queue empty, downstreamSubscription: %s",
+                       downstreamSubscription);
+            }
+
+            final boolean dbgOn = logger.isLoggable(Level.DEBUG);
+            while (!outputQ.isEmpty() && downstreamSubscription.tryDecrement()) {
+                List<ByteBuffer> b = outputQ.poll();
+                if (dbgOn) logger.log(Level.DEBUG,
+                                            "DownstreamPusher: Pushing "
+                                            + Utils.remaining(b)
+                                            + " bytes downstream");
+                downstreamSubscriber.onNext(b);
+            }
+            upstreamWindowUpdate();
+            checkCompletion();
+        }
+    }
+
+    void upstreamWindowUpdate() {
+        long downstreamQueueSize = outputQ.size();
+        long n = upstreamWindowUpdate(upstreamWindow.get(), downstreamQueueSize);
+        if (n > 0)
+            upstreamRequest(n);
+    }
+
+    @Override
+    public void onSubscribe(Flow.Subscription subscription) {
+        if (upstreamSubscription != null) {
+            throw new IllegalStateException("Single shot publisher");
+        }
+        this.upstreamSubscription = subscription;
+        upstreamRequest(upstreamWindowUpdate(0, 0));
+        logger.log(Level.DEBUG,
+               "calling downstreamSubscriber::onSubscribe on %s",
+               downstreamSubscriber);
+        downstreamSubscriber.onSubscribe(downstreamSubscription);
+        onSubscribe();
+    }
+
+    @Override
+    public void onNext(List<ByteBuffer> item) {
+        logger.log(Level.DEBUG, "onNext");
+        long prev = upstreamWindow.getAndDecrement();
+        if (prev <= 0)
+            throw new IllegalStateException("invalid onNext call");
+        incomingCaller(item, false);
+        upstreamWindowUpdate();
+    }
+
+    private void upstreamRequest(long n) {
+        logger.log(Level.DEBUG, "requesting %d", n);
+        upstreamWindow.getAndAdd(n);
+        upstreamSubscription.request(n);
+    }
+
+    protected void requestMore() {
+        if (upstreamWindow.get() == 0) {
+            upstreamRequest(1);
+        }
+    }
+
+    public long upstreamWindow() {
+        return upstreamWindow.get();
+    }
+
+    @Override
+    public void onError(Throwable throwable) {
+        logger.log(Level.DEBUG, () -> "onError: " + throwable);
+        errorCommon(Objects.requireNonNull(throwable));
+    }
+
+    protected boolean errorCommon(Throwable throwable) {
+        assert throwable != null ||
+                (throwable = new AssertionError("null throwable")) != null;
+        if (errorRef.compareAndSet(null, throwable)) {
+            logger.log(Level.DEBUG, "error", throwable);
+            pushScheduler.runOrSchedule();
+            upstreamCompleted = true;
+            cf.completeExceptionally(throwable);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void close() {
+        errorCommon(new RuntimeException("wrapper closed"));
+    }
+
+    private void incomingCaller(List<ByteBuffer> l, boolean complete) {
+        try {
+            incoming(l, complete);
+        } catch(Throwable t) {
+            errorCommon(t);
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        logger.log(Level.DEBUG, () -> "upstream completed: " + toString());
+        upstreamCompleted = true;
+        incomingCaller(Utils.EMPTY_BB_LIST, true);
+        // pushScheduler will call checkCompletion()
+        pushScheduler.runOrSchedule();
+    }
+
+    /** Adds the given data to the input queue. */
+    public void addData(ByteBuffer l) {
+        if (upstreamSubscription == null) {
+            throw new IllegalStateException("can't add data before upstream subscriber subscribes");
+        }
+        incomingCaller(List.of(l), false);
+    }
+
+    void checkCompletion() {
+        if (downstreamCompleted || !upstreamCompleted) {
+            return;
+        }
+        if (!outputQ.isEmpty()) {
+            return;
+        }
+        if (errorRef.get() != null) {
+            pushScheduler.runOrSchedule();
+            return;
+        }
+        if (completionAcknowledged) {
+            logger.log(Level.DEBUG, "calling downstreamSubscriber.onComplete()");
+            downstreamSubscriber.onComplete();
+            // Fix me subscriber.onComplete.run();
+            downstreamCompleted = true;
+            cf.complete(null);
+        }
+    }
+
+    // called from the downstream Subscription.cancel()
+    void downstreamCompletion() {
+        upstreamSubscription.cancel();
+        cf.complete(null);
+    }
+
+    public void resetDownstreamDemand() {
+        downstreamSubscription.demand.reset();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SubscriberWrapper:")
+          .append(" upstreamCompleted: ").append(Boolean.toString(upstreamCompleted))
+          .append(" upstreamWindow: ").append(upstreamWindow.toString())
+          .append(" downstreamCompleted: ").append(Boolean.toString(downstreamCompleted))
+          .append(" completionAcknowledged: ").append(Boolean.toString(completionAcknowledged))
+          .append(" outputQ size: ").append(Integer.toString(outputQ.size()))
+          //.append(" outputQ: ").append(outputQ.toString())
+          .append(" cf: ").append(cf.toString())
+          .append(" downstreamSubscription: ").append(downstreamSubscription.toString());
+
+        return sb.toString();
+    }
+
+    public String dbgString() {
+        return "SubscriberWrapper";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriptionBase.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,88 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Maintains subscription counter and provides primitives for
+ * - accessing window
+ * - reducing window when delivering items externally
+ * - resume delivery when window was zero previously
+ *
+ * @author mimcmah
+ */
+public class SubscriptionBase implements Flow.Subscription {
+
+    final Demand demand = new Demand();
+
+    final SequentialScheduler scheduler; // when window was zero and is opened, run this
+    final Runnable cancelAction; // when subscription cancelled, run this
+    final AtomicBoolean cancelled;
+
+    public SubscriptionBase(SequentialScheduler scheduler, Runnable cancelAction) {
+        this.scheduler = scheduler;
+        this.cancelAction = cancelAction;
+        this.cancelled = new AtomicBoolean(false);
+    }
+
+    @Override
+    public void request(long n) {
+        if (demand.increase(n))
+            scheduler.runOrSchedule();
+    }
+
+
+
+    @Override
+    public synchronized String toString() {
+        return "SubscriptionBase: window = " + demand.get() +
+                " cancelled = " + cancelled.toString();
+    }
+
+    /**
+     * Returns true if the window was reduced by 1. In that case
+     * items must be supplied to subscribers and the scheduler run
+     * externally. If the window could not be reduced by 1, then false
+     * is returned and the scheduler will run later when the window is updated.
+     */
+    public boolean tryDecrement() {
+        return demand.tryDecrement();
+    }
+
+    public long window() {
+        return demand.get();
+    }
+
+    @Override
+    public void cancel() {
+        if (cancelled.getAndSet(true))
+            return;
+        scheduler.stop();
+        cancelAction.run();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,755 @@
+/*
+ * 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.internal.net.http.common;
+
+import java.net.http.HttpHeaders;
+import sun.net.NetProperties;
+import sun.net.util.IPAddressUtil;
+import sun.net.www.HeaderParser;
+
+import javax.net.ssl.SSLParameters;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URLPermission;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.joining;
+
+/**
+ * Miscellaneous utilities
+ */
+public final class Utils {
+
+    public static final boolean ASSERTIONSENABLED;
+    static {
+        boolean enabled = false;
+        assert enabled = true;
+        ASSERTIONSENABLED = enabled;
+    }
+//    public static final boolean TESTING;
+//    static {
+//        if (ASSERTIONSENABLED) {
+//            PrivilegedAction<String> action = () -> System.getProperty("test.src");
+//            TESTING = AccessController.doPrivileged(action) != null;
+//        } else TESTING = false;
+//    }
+    public static final boolean DEBUG = // Revisit: temporary dev flag.
+            getBooleanProperty(DebugLogger.HTTP_NAME, false);
+    public static final boolean DEBUG_HPACK = // Revisit: temporary dev flag.
+            getBooleanProperty(DebugLogger.HPACK_NAME, false);
+    public static final boolean TESTING = DEBUG;
+
+    /**
+     * Allocated buffer size. Must never be higher than 16K. But can be lower
+     * if smaller allocation units preferred. HTTP/2 mandates that all
+     * implementations support frame payloads of at least 16K.
+     */
+    private static final int DEFAULT_BUFSIZE = 16 * 1024;
+
+    public static final int BUFSIZE = getIntegerNetProperty(
+            "jdk.httpclient.bufsize", DEFAULT_BUFSIZE
+    );
+
+    private static final Set<String> DISALLOWED_HEADERS_SET;
+    static {
+        // A case insensitive TreeSet of strings.
+        TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        treeSet.addAll(Set.of("connection", "content-length",
+                "date", "expect", "from", "host", "origin",
+                "referer", "upgrade",
+                "via", "warning"));
+        DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(treeSet);
+    }
+
+    public static final Predicate<String>
+        ALLOWED_HEADERS = header -> !DISALLOWED_HEADERS_SET.contains(header);
+
+    private static final Predicate<String> IS_PROXY_HEADER = (k) ->
+            k != null && k.length() > 6 && "proxy-".equalsIgnoreCase(k.substring(0,6));
+    private static final Predicate<String> NO_PROXY_HEADER =
+            IS_PROXY_HEADER.negate();
+    private static final Predicate<String> ALL_HEADERS = (s) -> true;
+
+    private static final Set<String> PROXY_AUTH_DISABLED_SCHEMES;
+    private static final Set<String> PROXY_AUTH_TUNNEL_DISABLED_SCHEMES;
+    static {
+        String proxyAuthDisabled =
+                getNetProperty("jdk.http.auth.proxying.disabledSchemes");
+        String proxyAuthTunnelDisabled =
+                getNetProperty("jdk.http.auth.tunneling.disabledSchemes");
+        PROXY_AUTH_DISABLED_SCHEMES =
+                proxyAuthDisabled == null ? Set.of() :
+                        Stream.of(proxyAuthDisabled.split(","))
+                                .map(String::trim)
+                                .filter((s) -> !s.isEmpty())
+                                .collect(Collectors.toUnmodifiableSet());
+        PROXY_AUTH_TUNNEL_DISABLED_SCHEMES =
+                proxyAuthTunnelDisabled == null ? Set.of() :
+                        Stream.of(proxyAuthTunnelDisabled.split(","))
+                                .map(String::trim)
+                                .filter((s) -> !s.isEmpty())
+                                .collect(Collectors.toUnmodifiableSet());
+    }
+
+    private static final String WSPACES = " \t\r\n";
+    private static final boolean isAllowedForProxy(String name,
+                                                   List<String> value,
+                                                   Set<String> disabledSchemes,
+                                                   Predicate<String> allowedKeys) {
+        if (!allowedKeys.test(name)) return false;
+        if (disabledSchemes.isEmpty()) return true;
+        if (name.equalsIgnoreCase("proxy-authorization")) {
+            if (value.isEmpty()) return false;
+            for (String scheme : disabledSchemes) {
+                int slen = scheme.length();
+                for (String v : value) {
+                    int vlen = v.length();
+                    if (vlen == slen) {
+                        if (v.equalsIgnoreCase(scheme)) {
+                            return false;
+                        }
+                    } else if (vlen > slen) {
+                        if (v.substring(0,slen).equalsIgnoreCase(scheme)) {
+                            int c = v.codePointAt(slen);
+                            if (WSPACES.indexOf(c) > -1
+                                    || Character.isSpaceChar(c)
+                                    || Character.isWhitespace(c)) {
+                                return false;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    public static final BiPredicate<String, List<String>> PROXY_TUNNEL_FILTER =
+            (s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_TUNNEL_DISABLED_SCHEMES,
+                    IS_PROXY_HEADER);
+    public static final BiPredicate<String, List<String>> PROXY_FILTER =
+            (s,v) -> isAllowedForProxy(s, v, PROXY_AUTH_DISABLED_SCHEMES,
+                    ALL_HEADERS);
+    public static final BiPredicate<String, List<String>> NO_PROXY_HEADERS_FILTER =
+            (n,v) -> Utils.NO_PROXY_HEADER.test(n);
+
+
+    public static boolean proxyHasDisabledSchemes(boolean tunnel) {
+        return tunnel ? ! PROXY_AUTH_TUNNEL_DISABLED_SCHEMES.isEmpty()
+                      : ! PROXY_AUTH_DISABLED_SCHEMES.isEmpty();
+    }
+
+    public static ByteBuffer getBuffer() {
+        return ByteBuffer.allocate(BUFSIZE);
+    }
+
+    public static Throwable getCompletionCause(Throwable x) {
+        if (!(x instanceof CompletionException)
+                && !(x instanceof ExecutionException)) return x;
+        final Throwable cause = x.getCause();
+        if (cause == null) {
+            throw new InternalError("Unexpected null cause", x);
+        }
+        return cause;
+    }
+
+    public static IOException getIOException(Throwable t) {
+        if (t instanceof IOException) {
+            return (IOException) t;
+        }
+        Throwable cause = t.getCause();
+        if (cause != null) {
+            return getIOException(cause);
+        }
+        return new IOException(t);
+    }
+
+    private Utils() { }
+
+    /**
+     * Returns the security permissions required to connect to the proxy, or
+     * {@code null} if none is required or applicable.
+     */
+    public static URLPermission permissionForProxy(InetSocketAddress proxyAddress) {
+        if (proxyAddress == null)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("socket://")
+          .append(proxyAddress.getHostString()).append(":")
+          .append(proxyAddress.getPort());
+        String urlString = sb.toString();
+        return new URLPermission(urlString, "CONNECT");
+    }
+
+    /**
+     * Returns the security permission required for the given details.
+     */
+    public static URLPermission permissionForServer(URI uri,
+                                                    String method,
+                                                    Stream<String> headers) {
+        String urlString = new StringBuilder()
+                .append(uri.getScheme()).append("://")
+                .append(uri.getAuthority())
+                .append(uri.getPath()).toString();
+
+        StringBuilder actionStringBuilder = new StringBuilder(method);
+        String collected = headers.collect(joining(","));
+        if (!collected.isEmpty()) {
+            actionStringBuilder.append(":").append(collected);
+        }
+        return new URLPermission(urlString, actionStringBuilder.toString());
+    }
+
+
+    // ABNF primitives defined in RFC 7230
+    private static final boolean[] tchar      = new boolean[256];
+    private static final boolean[] fieldvchar = new boolean[256];
+
+    static {
+        char[] allowedTokenChars =
+                ("!#$%&'*+-.^_`|~0123456789" +
+                 "abcdefghijklmnopqrstuvwxyz" +
+                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
+        for (char c : allowedTokenChars) {
+            tchar[c] = true;
+        }
+        for (char c = 0x21; c < 0xFF; c++) {
+            fieldvchar[c] = true;
+        }
+        fieldvchar[0x7F] = false; // a little hole (DEL) in the range
+    }
+
+    /*
+     * Validates a RFC 7230 field-name.
+     */
+    public static boolean isValidName(String token) {
+        for (int i = 0; i < token.length(); i++) {
+            char c = token.charAt(i);
+            if (c > 255 || !tchar[c]) {
+                return false;
+            }
+        }
+        return !token.isEmpty();
+    }
+
+    /**
+     * If the address was created with a domain name, then return
+     * the domain name string. If created with a literal IP address
+     * then return null. We do this to avoid doing a reverse lookup
+     * Used to populate the TLS SNI parameter. So, SNI is only set
+     * when a domain name was supplied.
+     */
+    public static String getServerName(InetSocketAddress addr) {
+        String host = addr.getHostString();
+        if (IPAddressUtil.textToNumericFormatV4(host) != null)
+            return null;
+        if (IPAddressUtil.textToNumericFormatV6(host) != null)
+            return null;
+        return host;
+    }
+
+    /*
+     * Validates a RFC 7230 field-value.
+     *
+     * "Obsolete line folding" rule
+     *
+     *     obs-fold = CRLF 1*( SP / HTAB )
+     *
+     * is not permitted!
+     */
+    public static boolean isValidValue(String token) {
+        boolean accepted = true;
+        for (int i = 0; i < token.length(); i++) {
+            char c = token.charAt(i);
+            if (c > 255) {
+                return false;
+            }
+            if (accepted) {
+                if (c == ' ' || c == '\t') {
+                    accepted = false;
+                } else if (!fieldvchar[c]) {
+                    return false; // forbidden byte
+                }
+            } else {
+                if (c != ' ' && c != '\t') {
+                    if (fieldvchar[c]) {
+                        accepted = true;
+                    } else {
+                        return false; // forbidden byte
+                    }
+                }
+            }
+        }
+        return accepted;
+    }
+
+    public static int getIntegerNetProperty(String name, int defaultValue) {
+        return AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
+                NetProperties.getInteger(name, defaultValue));
+    }
+
+    static String getNetProperty(String name) {
+        return AccessController.doPrivileged((PrivilegedAction<String>) () ->
+                NetProperties.get(name));
+    }
+
+    static boolean getBooleanProperty(String name, boolean def) {
+        return AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
+                Boolean.parseBoolean(System.getProperty(name, String.valueOf(def))));
+    }
+
+    public static SSLParameters copySSLParameters(SSLParameters p) {
+        SSLParameters p1 = new SSLParameters();
+        p1.setAlgorithmConstraints(p.getAlgorithmConstraints());
+        p1.setCipherSuites(p.getCipherSuites());
+        // JDK 8 EXCL START
+        p1.setEnableRetransmissions(p.getEnableRetransmissions());
+        p1.setMaximumPacketSize(p.getMaximumPacketSize());
+        // JDK 8 EXCL END
+        p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm());
+        p1.setNeedClientAuth(p.getNeedClientAuth());
+        String[] protocols = p.getProtocols();
+        if (protocols != null) {
+            p1.setProtocols(protocols.clone());
+        }
+        p1.setSNIMatchers(p.getSNIMatchers());
+        p1.setServerNames(p.getServerNames());
+        p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
+        p1.setWantClientAuth(p.getWantClientAuth());
+        return p1;
+    }
+
+    /**
+     * Set limit to position, and position to mark.
+     */
+    public static void flipToMark(ByteBuffer buffer, int mark) {
+        buffer.limit(buffer.position());
+        buffer.position(mark);
+    }
+
+    public static String stackTrace(Throwable t) {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        String s = null;
+        try {
+            PrintStream p = new PrintStream(bos, true, "US-ASCII");
+            t.printStackTrace(p);
+            s = bos.toString("US-ASCII");
+        } catch (UnsupportedEncodingException ex) {
+            throw new InternalError(ex); // Can't happen
+        }
+        return s;
+    }
+
+    /**
+     * Copies as much of src to dst as possible.
+     * Return number of bytes copied
+     */
+    public static int copy(ByteBuffer src, ByteBuffer dst) {
+        int srcLen = src.remaining();
+        int dstLen = dst.remaining();
+        if (srcLen > dstLen) {
+            int diff = srcLen - dstLen;
+            int limit = src.limit();
+            src.limit(limit - diff);
+            dst.put(src);
+            src.limit(limit);
+        } else {
+            dst.put(src);
+        }
+        return srcLen - src.remaining();
+    }
+
+    /** Threshold beyond which data is no longer copied into the current
+     * buffer, if that buffer has enough unused space. */
+    private static final int COPY_THRESHOLD = 8192;
+
+    /**
+     * Adds the data from buffersToAdd to currentList. Either 1) appends the
+     * data from a particular buffer to the last buffer in the list ( if
+     * there is enough unused space ), or 2) adds it to the list.
+     *
+     * @return the number of bytes added
+     */
+    public static long accumulateBuffers(List<ByteBuffer> currentList,
+                                         List<ByteBuffer> buffersToAdd) {
+        long accumulatedBytes = 0;
+        for (ByteBuffer bufferToAdd : buffersToAdd) {
+            int remaining = bufferToAdd.remaining();
+            if (remaining <= 0)
+                continue;
+            int listSize = currentList.size();
+            if (listSize == 0) {
+                currentList.add(bufferToAdd);
+                accumulatedBytes = remaining;
+                continue;
+            }
+
+            ByteBuffer lastBuffer = currentList.get(listSize - 1);
+            int freeSpace = lastBuffer.capacity() - lastBuffer.limit();
+            if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) {
+                // append the new data to the unused space in the last buffer
+                int position = lastBuffer.position();
+                int limit = lastBuffer.limit();
+                lastBuffer.position(limit);
+                lastBuffer.limit(limit + remaining);
+                lastBuffer.put(bufferToAdd);
+                lastBuffer.position(position);
+            } else {
+                currentList.add(bufferToAdd);
+            }
+            accumulatedBytes += remaining;
+        }
+        return accumulatedBytes;
+    }
+
+    public static ByteBuffer copy(ByteBuffer src) {
+        ByteBuffer dst = ByteBuffer.allocate(src.remaining());
+        dst.put(src);
+        dst.flip();
+        return dst;
+    }
+
+    public static String dump(Object... objects) {
+        return Arrays.toString(objects);
+    }
+
+    public static String stringOf(Collection<?> source) {
+        // We don't know anything about toString implementation of this
+        // collection, so let's create an array
+        return Arrays.toString(source.toArray());
+    }
+
+    public static long remaining(ByteBuffer[] bufs) {
+        long remain = 0;
+        for (ByteBuffer buf : bufs) {
+            remain += buf.remaining();
+        }
+        return remain;
+    }
+
+    public static boolean hasRemaining(List<ByteBuffer> bufs) {
+        synchronized (bufs) {
+            for (ByteBuffer buf : bufs) {
+                if (buf.hasRemaining())
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    public static long remaining(List<ByteBuffer> bufs) {
+        long remain = 0;
+        synchronized (bufs) {
+            for (ByteBuffer buf : bufs) {
+                remain += buf.remaining();
+            }
+        }
+        return remain;
+    }
+
+    public static int remaining(List<ByteBuffer> bufs, int max) {
+        long remain = 0;
+        synchronized (bufs) {
+            for (ByteBuffer buf : bufs) {
+                remain += buf.remaining();
+                if (remain > max) {
+                    throw new IllegalArgumentException("too many bytes");
+                }
+            }
+        }
+        return (int) remain;
+    }
+
+    public static long remaining(ByteBufferReference[] refs) {
+        long remain = 0;
+        for (ByteBufferReference ref : refs) {
+            remain += ref.get().remaining();
+        }
+        return remain;
+    }
+
+    public static int remaining(ByteBufferReference[] refs, int max) {
+        long remain = 0;
+        for (ByteBufferReference ref : refs) {
+            remain += ref.get().remaining();
+            if (remain > max) {
+                throw new IllegalArgumentException("too many bytes");
+            }
+        }
+        return (int) remain;
+    }
+
+    public static int remaining(ByteBuffer[] refs, int max) {
+        long remain = 0;
+        for (ByteBuffer b : refs) {
+            remain += b.remaining();
+            if (remain > max) {
+                throw new IllegalArgumentException("too many bytes");
+            }
+        }
+        return (int) remain;
+    }
+
+    public static void close(Closeable... closeables) {
+        for (Closeable c : closeables) {
+            try {
+                c.close();
+            } catch (IOException ignored) { }
+        }
+    }
+
+    // Put all these static 'empty' singletons here
+    public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
+    public static final ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0];
+    public static final List<ByteBuffer> EMPTY_BB_LIST = List.of();
+    public static final ByteBufferReference[] EMPTY_BBR_ARRAY = new ByteBufferReference[0];
+
+    /**
+     * Returns a slice of size {@code amount} from the given buffer. If the
+     * buffer contains more data than {@code amount}, then the slice's capacity
+     * ( and, but not just, its limit ) is set to {@code amount}. If the buffer
+     * does not contain more data than {@code amount}, then the slice's capacity
+     * will be the same as the given buffer's capacity.
+     */
+    public static ByteBuffer sliceWithLimitedCapacity(ByteBuffer buffer, int amount) {
+        final int index = buffer.position() + amount;
+        final int limit = buffer.limit();
+        if (index != limit) {
+            // additional data in the buffer
+            buffer.limit(index);  // ensures that the slice does not go beyond
+        } else {
+            // no additional data in the buffer
+            buffer.limit(buffer.capacity());  // allows the slice full capacity
+        }
+
+        ByteBuffer newb = buffer.slice();
+        buffer.position(index);
+        buffer.limit(limit);    // restore the original buffer's limit
+        newb.limit(amount);     // slices limit to amount (capacity may be greater)
+        return newb;
+    }
+
+    /**
+     * Get the Charset from the Content-encoding header. Defaults to
+     * UTF_8
+     */
+    public static Charset charsetFrom(HttpHeaders headers) {
+        String type = headers.firstValue("Content-type")
+                .orElse("text/html; charset=utf-8");
+        int i = type.indexOf(";");
+        if (i >= 0) type = type.substring(i+1);
+        try {
+            HeaderParser parser = new HeaderParser(type);
+            String value = parser.findValue("charset");
+            if (value == null) return StandardCharsets.UTF_8;
+            return Charset.forName(value);
+        } catch (Throwable x) {
+            Log.logTrace("Can't find charset in \"{0}\" ({1})", type, x);
+            return StandardCharsets.UTF_8;
+        }
+    }
+
+    public static UncheckedIOException unchecked(IOException e) {
+        return new UncheckedIOException(e);
+    }
+
+    /**
+     * Get a logger for debug HTTP traces.
+     *
+     * The logger should only be used with levels whose severity is
+     * {@code <= DEBUG}. By default, this logger will forward all messages
+     * logged to an internal logger named "jdk.internal.httpclient.debug".
+     * In addition, if the property -Djdk.internal.httpclient.debug=true is set,
+     * it will print the messages on stderr.
+     * The logger will add some decoration to the printed message, in the form of
+     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
+     *
+     * @param dbgTag A lambda that returns a string that identifies the caller
+     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
+     *
+     * @return A logger for HTTP internal debug traces
+     */
+    public static Logger getDebugLogger(Supplier<String> dbgTag) {
+        return getDebugLogger(dbgTag, DEBUG);
+    }
+
+    /**
+     * Get a logger for debug HTTP traces.The logger should only be used
+     * with levels whose severity is {@code <= DEBUG}.
+     *
+     * By default, this logger will forward all messages logged to an internal
+     * logger named "jdk.internal.httpclient.debug".
+     * In addition, if the message severity level is >= to
+     * the provided {@code errLevel} it will print the messages on stderr.
+     * The logger will add some decoration to the printed message, in the form of
+     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
+     *
+     * @apiNote To obtain a logger that will always print things on stderr in
+     *          addition to forwarding to the internal logger, use
+     *          {@code getDebugLogger(this::dbgTag, Level.ALL);}.
+     *          This is also equivalent to calling
+     *          {@code getDebugLogger(this::dbgTag, true);}.
+     *          To obtain a logger that will only forward to the internal logger,
+     *          use {@code getDebugLogger(this::dbgTag, Level.OFF);}.
+     *          This is also equivalent to calling
+     *          {@code getDebugLogger(this::dbgTag, false);}.
+     *
+     * @param dbgTag A lambda that returns a string that identifies the caller
+     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
+     * @param errLevel The level above which messages will be also printed on
+     *               stderr (in addition to be forwarded to the internal logger).
+     *
+     * @return A logger for HTTP internal debug traces
+     */
+    static Logger getDebugLogger(Supplier<String> dbgTag, Level errLevel) {
+        return DebugLogger.createHttpLogger(dbgTag, Level.OFF, errLevel);
+    }
+
+    /**
+     * Get a logger for debug HTTP traces.The logger should only be used
+     * with levels whose severity is {@code <= DEBUG}.
+     *
+     * By default, this logger will forward all messages logged to an internal
+     * logger named "jdk.internal.httpclient.debug".
+     * In addition, the provided boolean {@code on==true}, it will print the
+     * messages on stderr.
+     * The logger will add some decoration to the printed message, in the form of
+     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
+     *
+     * @apiNote To obtain a logger that will always print things on stderr in
+     *          addition to forwarding to the internal logger, use
+     *          {@code getDebugLogger(this::dbgTag, true);}.
+     *          This is also equivalent to calling
+     *          {@code getDebugLogger(this::dbgTag, Level.ALL);}.
+     *          To obtain a logger that will only forward to the internal logger,
+     *          use {@code getDebugLogger(this::dbgTag, false);}.
+     *          This is also equivalent to calling
+     *          {@code getDebugLogger(this::dbgTag, Level.OFF);}.
+     *
+     * @param dbgTag A lambda that returns a string that identifies the caller
+     *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
+     * @param on  Whether messages should also be printed on
+     *               stderr (in addition to be forwarded to the internal logger).
+     *
+     * @return A logger for HTTP internal debug traces
+     */
+    public static Logger getDebugLogger(Supplier<String> dbgTag, boolean on) {
+        Level errLevel = on ? Level.ALL : Level.OFF;
+        return getDebugLogger(dbgTag, errLevel);
+    }
+
+    /**
+     * Get a logger for debug HPACK traces.The logger should only be used
+     * with levels whose severity is {@code <= DEBUG}.
+     *
+     * By default, this logger will forward all messages logged to an internal
+     * logger named "jdk.internal.httpclient.hpack.debug".
+     * In addition, if the message severity level is >= to
+     * the provided {@code outLevel} it will print the messages on stdout.
+     * The logger will add some decoration to the printed message, in the form of
+     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
+     *
+     * @apiNote To obtain a logger that will always print things on stdout in
+     *          addition to forwarding to the internal logger, use
+     *          {@code getHpackLogger(this::dbgTag, Level.ALL);}.
+     *          This is also equivalent to calling
+     *          {@code getHpackLogger(this::dbgTag, true);}.
+     *          To obtain a logger that will only forward to the internal logger,
+     *          use {@code getHpackLogger(this::dbgTag, Level.OFF);}.
+     *          This is also equivalent to calling
+     *          {@code getHpackLogger(this::dbgTag, false);}.
+     *
+     * @param dbgTag A lambda that returns a string that identifies the caller
+     *               (e.g: "Http2Connection(SocketTube(3))/hpack.Decoder(3)")
+     * @param outLevel The level above which messages will be also printed on
+     *               stdout (in addition to be forwarded to the internal logger).
+     *
+     * @return A logger for HPACK internal debug traces
+     */
+    public static Logger getHpackLogger(Supplier<String> dbgTag, Level outLevel) {
+        Level errLevel = Level.OFF;
+        return DebugLogger.createHpackLogger(dbgTag, outLevel, errLevel);
+    }
+
+    /**
+     * Get a logger for debug HPACK traces.The logger should only be used
+     * with levels whose severity is {@code <= DEBUG}.
+     *
+     * By default, this logger will forward all messages logged to an internal
+     * logger named "jdk.internal.httpclient.hpack.debug".
+     * In addition, the provided boolean {@code on==true}, it will print the
+     * messages on stdout.
+     * The logger will add some decoration to the printed message, in the form of
+     * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
+     *
+     * @apiNote To obtain a logger that will always print things on stdout in
+     *          addition to forwarding to the internal logger, use
+     *          {@code getHpackLogger(this::dbgTag, true);}.
+     *          This is also equivalent to calling
+     *          {@code getHpackLogger(this::dbgTag, Level.ALL);}.
+     *          To obtain a logger that will only forward to the internal logger,
+     *          use {@code getHpackLogger(this::dbgTag, false);}.
+     *          This is also equivalent to calling
+     *          {@code getHpackLogger(this::dbgTag, Level.OFF);}.
+     *
+     * @param dbgTag A lambda that returns a string that identifies the caller
+     *               (e.g: "Http2Connection(SocketTube(3))/hpack.Decoder(3)")
+     * @param on  Whether messages should also be printed on
+     *            stdout (in addition to be forwarded to the internal logger).
+     *
+     * @return A logger for HPACK internal debug traces
+     */
+    public static Logger getHpackLogger(Supplier<String> dbgTag, boolean on) {
+        Level outLevel = on ? Level.ALL : Level.OFF;
+        return getHpackLogger(dbgTag, outLevel);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/ContinuationFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,53 @@
+/*
+ * 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.internal.net.http.frame;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class ContinuationFrame extends HeaderFrame {
+
+    public static final int TYPE = 0x9;
+
+    public ContinuationFrame(int streamid, int flags, List<ByteBuffer> headerBlocks) {
+        super(streamid, flags, headerBlocks);
+    }
+
+    public ContinuationFrame(int streamid, ByteBuffer headersBlock) {
+        this(streamid, 0, List.of(headersBlock));
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return headerLength;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/DataFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,112 @@
+/*
+ * 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.internal.net.http.frame;
+
+import jdk.internal.net.http.common.Utils;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class DataFrame extends Http2Frame {
+
+    public static final int TYPE = 0x0;
+
+    // Flags
+    public static final int END_STREAM = 0x1;
+    public static final int PADDED = 0x8;
+
+    private int padLength;
+    private final List<ByteBuffer> data;
+    private final int dataLength;
+
+    public DataFrame(int streamid, int flags, ByteBuffer data) {
+        this(streamid, flags, List.of(data));
+    }
+
+    public DataFrame(int streamid, int flags, List<ByteBuffer> data) {
+        super(streamid, flags);
+        this.data = data;
+        this.dataLength = Utils.remaining(data, Integer.MAX_VALUE);
+    }
+
+    public DataFrame(int streamid, int flags, List<ByteBuffer> data, int padLength) {
+        this(streamid, flags, data);
+        if (padLength > 0) {
+            setPadLength(padLength);
+        }
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return dataLength + (((flags & PADDED) != 0) ? (padLength + 1) : 0);
+    }
+
+    @Override
+    public String flagAsString(int flag) {
+        switch (flag) {
+        case END_STREAM:
+            return "END_STREAM";
+        case PADDED:
+            return "PADDED";
+        }
+        return super.flagAsString(flag);
+    }
+
+    public List<ByteBuffer> getData() {
+        return data;
+    }
+
+    public int getDataLength() {
+        return dataLength;
+    }
+
+    int getPadLength() {
+        return padLength;
+    }
+
+    public void setPadLength(int padLength) {
+        this.padLength = padLength;
+        flags |= PADDED;
+    }
+
+    public int payloadLength() {
+        // RFC 7540 6.1:
+        // The entire DATA frame payload is included in flow control,
+        // including the Pad Length and Padding fields if present
+        if ((flags & PADDED) != 0) {
+            return dataLength + (1 + padLength);
+        } else {
+            return dataLength;
+        }
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/ErrorFrame.java	Wed Feb 07 21:45:37 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.internal.net.http.frame;
+
+public abstract class ErrorFrame extends Http2Frame {
+
+    // error codes
+    public static final int NO_ERROR = 0x0;
+    public static final int PROTOCOL_ERROR = 0x1;
+    public static final int INTERNAL_ERROR = 0x2;
+    public static final int FLOW_CONTROL_ERROR = 0x3;
+    public static final int SETTINGS_TIMEOUT = 0x4;
+    public static final int STREAM_CLOSED = 0x5;
+    public static final int FRAME_SIZE_ERROR = 0x6;
+    public static final int REFUSED_STREAM = 0x7;
+    public static final int CANCEL = 0x8;
+    public static final int COMPRESSION_ERROR = 0x9;
+    public static final int CONNECT_ERROR = 0xa;
+    public static final int ENHANCE_YOUR_CALM = 0xb;
+    public static final int INADEQUATE_SECURITY = 0xc;
+    public static final int HTTP_1_1_REQUIRED = 0xd;
+    static final int LAST_ERROR = 0xd;
+
+    static final String[] errorStrings = {
+        "Not an error",
+        "Protocol error",
+        "Internal error",
+        "Flow control error",
+        "Settings timeout",
+        "Stream is closed",
+        "Frame size error",
+        "Stream not processed",
+        "Stream cancelled",
+        "Compression state not updated",
+        "TCP Connection error on CONNECT",
+        "Processing capacity exceeded",
+        "Negotiated TLS parameters not acceptable",
+        "Use HTTP/1.1 for request"
+    };
+
+    public static String stringForCode(int code) {
+        if (code < 0) {
+            throw new IllegalArgumentException();
+        }
+
+        if (code > LAST_ERROR) {
+            return "Error: " + Integer.toString(code);
+        } else {
+            return errorStrings[code];
+        }
+    }
+
+    int errorCode;
+
+    public ErrorFrame(int streamid, int flags, int errorCode) {
+        super(streamid, flags);
+        this.errorCode = errorCode;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " Error: " + stringForCode(errorCode);
+    }
+
+    public int getErrorCode() {
+        return this.errorCode;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/FramesDecoder.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,545 @@
+/*
+ * 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.internal.net.http.frame;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.Utils;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Frames Decoder
+ * <p>
+ * collect buffers until frame decoding is possible,
+ * all decoded frames are passed to the FrameProcessor callback in order of decoding.
+ *
+ * It's a stateful class due to the fact that FramesDecoder stores buffers inside.
+ * Should be allocated only the single instance per connection.
+ */
+public class FramesDecoder {
+
+    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
+    static final System.Logger DEBUG_LOGGER =
+            Utils.getDebugLogger("FramesDecoder"::toString, DEBUG);
+
+    @FunctionalInterface
+    public interface FrameProcessor {
+        void processFrame(Http2Frame frame) throws IOException;
+    }
+
+    private final FrameProcessor frameProcessor;
+    private final int maxFrameSize;
+
+    private ByteBuffer currentBuffer; // current buffer either null or hasRemaining
+
+    private final ArrayDeque<ByteBuffer> tailBuffers = new ArrayDeque<>();
+    private int tailSize = 0;
+
+    private boolean slicedToDataFrame = false;
+
+    private final List<ByteBuffer> prepareToRelease = new ArrayList<>();
+
+    // if true  - Frame Header was parsed (9 bytes consumed) and subsequent fields have meaning
+    // otherwise - stopped at frames boundary
+    private boolean frameHeaderParsed = false;
+    private int frameLength;
+    private int frameType;
+    private int frameFlags;
+    private int frameStreamid;
+    private boolean closed;
+
+    /**
+     * Creates Frame Decoder
+     *
+     * @param frameProcessor - callback for decoded frames
+     */
+    public FramesDecoder(FrameProcessor frameProcessor) {
+        this(frameProcessor, 16 * 1024);
+    }
+
+    /**
+     * Creates Frame Decoder
+     * @param frameProcessor - callback for decoded frames
+     * @param maxFrameSize - maxFrameSize accepted by this decoder
+     */
+    public FramesDecoder(FrameProcessor frameProcessor, int maxFrameSize) {
+        this.frameProcessor = frameProcessor;
+        this.maxFrameSize = Math.min(Math.max(16 * 1024, maxFrameSize), 16 * 1024 * 1024 - 1);
+    }
+
+    /** Threshold beyond which data is no longer copied into the current buffer,
+     * if that buffer has enough unused space. */
+    private static final int COPY_THRESHOLD = 8192;
+
+    /**
+     * Adds the data from the given buffer, and performs frame decoding if
+     * possible.   Either 1) appends the data from the given buffer to the
+     * current buffer ( if there is enough unused space ), or 2) adds it to the
+     * next buffer in the queue.
+     *
+     * If there is enough data to perform frame decoding then, all buffers are
+     * decoded and the FrameProcessor is invoked.
+     */
+    public void decode(ByteBuffer inBoundBuffer) throws IOException {
+        if (closed) {
+            DEBUG_LOGGER.log(Level.DEBUG, "closed: ignoring buffer (%s bytes)",
+                    inBoundBuffer.remaining());
+            inBoundBuffer.position(inBoundBuffer.limit());
+            return;
+        }
+        int remaining = inBoundBuffer.remaining();
+        DEBUG_LOGGER.log(Level.DEBUG, "decodes: %d", remaining);
+        if (remaining > 0) {
+            if (currentBuffer == null) {
+                currentBuffer = inBoundBuffer;
+            } else {
+                ByteBuffer b = currentBuffer;
+                if (!tailBuffers.isEmpty()) {
+                    b = tailBuffers.getLast();
+                }
+
+                int limit = b.limit();
+                int freeSpace = b.capacity() - limit;
+                if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) {
+                    // append the new data to the unused space in the current buffer
+                    int position = b.position();
+                    b.position(limit);
+                    b.limit(limit + inBoundBuffer.remaining());
+                    b.put(inBoundBuffer);
+                    b.position(position);
+                    if (b != currentBuffer)
+                        tailSize += remaining;
+                    DEBUG_LOGGER.log(Level.DEBUG, "copied: %d", remaining);
+                } else {
+                    DEBUG_LOGGER.log(Level.DEBUG, "added: %d", remaining);
+                    tailBuffers.add(inBoundBuffer);
+                    tailSize += remaining;
+                }
+            }
+        }
+        DEBUG_LOGGER.log(Level.DEBUG, "Tail size is now: %d, current=",
+                tailSize,
+                (currentBuffer == null ? 0 :
+                   currentBuffer.remaining()));
+        Http2Frame frame;
+        while ((frame = nextFrame()) != null) {
+            DEBUG_LOGGER.log(Level.DEBUG, "Got frame: %s", frame);
+            frameProcessor.processFrame(frame);
+            frameProcessed();
+        }
+    }
+
+    private Http2Frame nextFrame() throws IOException {
+        while (true) {
+            if (currentBuffer == null) {
+                return null; // no data at all
+            }
+            long available = currentBuffer.remaining() + tailSize;
+            if (!frameHeaderParsed) {
+                if (available >= Http2Frame.FRAME_HEADER_SIZE) {
+                    parseFrameHeader();
+                    if (frameLength > maxFrameSize) {
+                        // connection error
+                        return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
+                                "Frame type("+frameType+") "
+                                +"length("+frameLength
+                                +") exceeds MAX_FRAME_SIZE("
+                                + maxFrameSize+")");
+                    }
+                    frameHeaderParsed = true;
+                } else {
+                    DEBUG_LOGGER.log(Level.DEBUG,
+                            "Not enough data to parse header, needs: %d, has: %d",
+                            Http2Frame.FRAME_HEADER_SIZE, available);
+                    return null;
+                }
+            }
+            available = currentBuffer == null ? 0 : currentBuffer.remaining() + tailSize;
+            if ((frameLength == 0) ||
+                    (currentBuffer != null && available >= frameLength)) {
+                Http2Frame frame = parseFrameBody();
+                frameHeaderParsed = false;
+                // frame == null means we have to skip this frame and try parse next
+                if (frame != null) {
+                    return frame;
+                }
+            } else {
+                DEBUG_LOGGER.log(Level.DEBUG,
+                        "Not enough data to parse frame body, needs: %d,  has: %d",
+                        frameLength, available);
+                return null;  // no data for the whole frame header
+            }
+        }
+    }
+
+    private void frameProcessed() {
+        prepareToRelease.clear();
+    }
+
+    private void parseFrameHeader() throws IOException {
+        int x = getInt();
+        this.frameLength = (x >>> 8) & 0x00ffffff;
+        this.frameType = x & 0xff;
+        this.frameFlags = getByte();
+        this.frameStreamid = getInt() & 0x7fffffff;
+        // R: A reserved 1-bit field.  The semantics of this bit are undefined,
+        // MUST be ignored when receiving.
+    }
+
+    // move next buffer from tailBuffers to currentBuffer if required
+    private void nextBuffer() {
+        if (!currentBuffer.hasRemaining()) {
+            if (!slicedToDataFrame) {
+                prepareToRelease.add(currentBuffer);
+            }
+            slicedToDataFrame = false;
+            currentBuffer = tailBuffers.poll();
+            if (currentBuffer != null) {
+                tailSize -= currentBuffer.remaining();
+            }
+        }
+    }
+
+    public int getByte() {
+        int res = currentBuffer.get() & 0xff;
+        nextBuffer();
+        return res;
+    }
+
+    public int getShort() {
+        if (currentBuffer.remaining() >= 2) {
+            int res = currentBuffer.getShort() & 0xffff;
+            nextBuffer();
+            return res;
+        }
+        int val = getByte();
+        val = (val << 8) + getByte();
+        return val;
+    }
+
+    public int getInt() {
+        if (currentBuffer.remaining() >= 4) {
+            int res = currentBuffer.getInt();
+            nextBuffer();
+            return res;
+        }
+        int val = getByte();
+        val = (val << 8) + getByte();
+        val = (val << 8) + getByte();
+        val = (val << 8) + getByte();
+        return val;
+
+    }
+
+    public byte[] getBytes(int n) {
+        byte[] bytes = new byte[n];
+        int offset = 0;
+        while (n > 0) {
+            int length = Math.min(n, currentBuffer.remaining());
+            currentBuffer.get(bytes, offset, length);
+            offset += length;
+            n -= length;
+            nextBuffer();
+        }
+        return bytes;
+
+    }
+
+    private List<ByteBuffer> getBuffers(boolean isDataFrame, int bytecount) {
+        List<ByteBuffer> res = new ArrayList<>();
+        while (bytecount > 0) {
+            int remaining = currentBuffer.remaining();
+            int extract = Math.min(remaining, bytecount);
+            ByteBuffer extractedBuf;
+            if (isDataFrame) {
+                extractedBuf = Utils.sliceWithLimitedCapacity(currentBuffer, extract)
+                                    .asReadOnlyBuffer();
+                slicedToDataFrame = true;
+            } else {
+                // Header frames here
+                // HPACK decoding should performed under lock and immediately after frame decoding.
+                // in that case it is safe to release original buffer,
+                // because of sliced buffer has a very short life
+                extractedBuf = Utils.sliceWithLimitedCapacity(currentBuffer, extract);
+            }
+            res.add(extractedBuf);
+            bytecount -= extract;
+            nextBuffer();
+        }
+        return res;
+    }
+
+    public void close(String msg) {
+        closed = true;
+        tailBuffers.clear();
+        int bytes = tailSize;
+        ByteBuffer b = currentBuffer;
+        if (b != null) {
+            bytes += b.remaining();
+            b.position(b.limit());
+        }
+        tailSize = 0;
+        currentBuffer = null;
+        DEBUG_LOGGER.log(Level.DEBUG, "closed %s, ignoring %d bytes", msg, bytes);
+    }
+
+    public void skipBytes(int bytecount) {
+        while (bytecount > 0) {
+            int remaining = currentBuffer.remaining();
+            int extract = Math.min(remaining, bytecount);
+            currentBuffer.position(currentBuffer.position() + extract);
+            bytecount -= remaining;
+            nextBuffer();
+        }
+    }
+
+    private Http2Frame parseFrameBody() throws IOException {
+        assert frameHeaderParsed;
+        switch (frameType) {
+            case DataFrame.TYPE:
+                return parseDataFrame(frameLength, frameStreamid, frameFlags);
+            case HeadersFrame.TYPE:
+                return parseHeadersFrame(frameLength, frameStreamid, frameFlags);
+            case PriorityFrame.TYPE:
+                return parsePriorityFrame(frameLength, frameStreamid, frameFlags);
+            case ResetFrame.TYPE:
+                return parseResetFrame(frameLength, frameStreamid, frameFlags);
+            case SettingsFrame.TYPE:
+                return parseSettingsFrame(frameLength, frameStreamid, frameFlags);
+            case PushPromiseFrame.TYPE:
+                return parsePushPromiseFrame(frameLength, frameStreamid, frameFlags);
+            case PingFrame.TYPE:
+                return parsePingFrame(frameLength, frameStreamid, frameFlags);
+            case GoAwayFrame.TYPE:
+                return parseGoAwayFrame(frameLength, frameStreamid, frameFlags);
+            case WindowUpdateFrame.TYPE:
+                return parseWindowUpdateFrame(frameLength, frameStreamid, frameFlags);
+            case ContinuationFrame.TYPE:
+                return parseContinuationFrame(frameLength, frameStreamid, frameFlags);
+            default:
+                // RFC 7540 4.1
+                // Implementations MUST ignore and discard any frame that has a type that is unknown.
+                Log.logTrace("Unknown incoming frame type: {0}", frameType);
+                skipBytes(frameLength);
+                return null;
+        }
+    }
+
+    private Http2Frame parseDataFrame(int frameLength, int streamid, int flags) {
+        // non-zero stream
+        if (streamid == 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                                      "zero streamId for DataFrame");
+        }
+        int padLength = 0;
+        if ((flags & DataFrame.PADDED) != 0) {
+            padLength = getByte();
+            if (padLength >= frameLength) {
+                return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                        "the length of the padding is the length of the frame payload or greater");
+            }
+            frameLength--;
+        }
+        DataFrame df = new DataFrame(streamid, flags,
+                getBuffers(true, frameLength - padLength), padLength);
+        skipBytes(padLength);
+        return df;
+
+    }
+
+    private Http2Frame parseHeadersFrame(int frameLength, int streamid, int flags) {
+        // non-zero stream
+        if (streamid == 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                                      "zero streamId for HeadersFrame");
+        }
+        int padLength = 0;
+        if ((flags & HeadersFrame.PADDED) != 0) {
+            padLength = getByte();
+            frameLength--;
+        }
+        boolean hasPriority = (flags & HeadersFrame.PRIORITY) != 0;
+        boolean exclusive = false;
+        int streamDependency = 0;
+        int weight = 0;
+        if (hasPriority) {
+            int x = getInt();
+            exclusive = (x & 0x80000000) != 0;
+            streamDependency = x & 0x7fffffff;
+            weight = getByte();
+            frameLength -= 5;
+        }
+        if(frameLength < padLength) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "Padding exceeds the size remaining for the header block");
+        }
+        HeadersFrame hf = new HeadersFrame(streamid, flags,
+                getBuffers(false, frameLength - padLength), padLength);
+        skipBytes(padLength);
+        if (hasPriority) {
+            hf.setPriority(streamDependency, exclusive, weight);
+        }
+        return hf;
+    }
+
+    private Http2Frame parsePriorityFrame(int frameLength, int streamid, int flags) {
+        // non-zero stream; no flags
+        if (streamid == 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "zero streamId for PriorityFrame");
+        }
+        if(frameLength != 5) {
+            skipBytes(frameLength);
+            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR, streamid,
+                    "PriorityFrame length is "+ frameLength+", expected 5");
+        }
+        int x = getInt();
+        int weight = getByte();
+        return new PriorityFrame(streamid, x & 0x7fffffff, (x & 0x80000000) != 0, weight);
+    }
+
+    private Http2Frame parseResetFrame(int frameLength, int streamid, int flags) {
+        // non-zero stream; no flags
+        if (streamid == 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "zero streamId for ResetFrame");
+        }
+        if(frameLength != 4) {
+            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
+                    "ResetFrame length is "+ frameLength+", expected 4");
+        }
+        return new ResetFrame(streamid, getInt());
+    }
+
+    private Http2Frame parseSettingsFrame(int frameLength, int streamid, int flags) {
+        // only zero stream
+        if (streamid != 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "non-zero streamId for SettingsFrame");
+        }
+        if ((SettingsFrame.ACK & flags) != 0 && frameLength > 0) {
+            // RFC 7540 6.5
+            // Receipt of a SETTINGS frame with the ACK flag set and a length
+            // field value other than 0 MUST be treated as a connection error
+            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
+                    "ACK SettingsFrame is not empty");
+        }
+        if (frameLength % 6 != 0) {
+            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
+                    "invalid SettingsFrame size: "+frameLength);
+        }
+        SettingsFrame sf = new SettingsFrame(flags);
+        int n = frameLength / 6;
+        for (int i=0; i<n; i++) {
+            int id = getShort();
+            int val = getInt();
+            if (id > 0 && id <= SettingsFrame.MAX_PARAM) {
+                // a known parameter. Ignore otherwise
+                sf.setParameter(id, val); // TODO parameters validation
+            }
+        }
+        return sf;
+    }
+
+    private Http2Frame parsePushPromiseFrame(int frameLength, int streamid, int flags) {
+        // non-zero stream
+        if (streamid == 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "zero streamId for PushPromiseFrame");
+        }
+        int padLength = 0;
+        if ((flags & PushPromiseFrame.PADDED) != 0) {
+            padLength = getByte();
+            frameLength--;
+        }
+        int promisedStream = getInt() & 0x7fffffff;
+        frameLength -= 4;
+        if(frameLength < padLength) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "Padding exceeds the size remaining for the PushPromiseFrame");
+        }
+        PushPromiseFrame ppf = new PushPromiseFrame(streamid, flags, promisedStream,
+                getBuffers(false, frameLength - padLength), padLength);
+        skipBytes(padLength);
+        return ppf;
+    }
+
+    private Http2Frame parsePingFrame(int frameLength, int streamid, int flags) {
+        // only zero stream
+        if (streamid != 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "non-zero streamId for PingFrame");
+        }
+        if(frameLength != 8) {
+            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
+                    "PingFrame length is "+ frameLength+", expected 8");
+        }
+        return new PingFrame(flags, getBytes(8));
+    }
+
+    private Http2Frame parseGoAwayFrame(int frameLength, int streamid, int flags) {
+        // only zero stream; no flags
+        if (streamid != 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "non-zero streamId for GoAwayFrame");
+        }
+        if (frameLength < 8) {
+            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
+                    "Invalid GoAway frame size");
+        }
+        int lastStream = getInt() & 0x7fffffff;
+        int errorCode = getInt();
+        byte[] debugData = getBytes(frameLength - 8);
+        if (debugData.length > 0) {
+            Log.logError("GoAway debugData " + new String(debugData, UTF_8));
+        }
+        return new GoAwayFrame(lastStream, errorCode, debugData);
+    }
+
+    private Http2Frame parseWindowUpdateFrame(int frameLength, int streamid, int flags) {
+        // any stream; no flags
+        if(frameLength != 4) {
+            return new MalformedFrame(ErrorFrame.FRAME_SIZE_ERROR,
+                    "WindowUpdateFrame length is "+ frameLength+", expected 4");
+        }
+        return new WindowUpdateFrame(streamid, getInt() & 0x7fffffff);
+    }
+
+    private Http2Frame parseContinuationFrame(int frameLength, int streamid, int flags) {
+        // non-zero stream;
+        if (streamid == 0) {
+            return new MalformedFrame(ErrorFrame.PROTOCOL_ERROR,
+                    "zero streamId for ContinuationFrame");
+        }
+        return new ContinuationFrame(streamid, flags, getBuffers(false, frameLength));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/FramesEncoder.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,293 @@
+/*
+ * 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.internal.net.http.frame;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Frames Encoder
+ *
+ * Encode framed into ByteBuffers.
+ * The class is stateless.
+ */
+public class FramesEncoder {
+
+
+    public FramesEncoder() {
+    }
+
+    public List<ByteBuffer> encodeFrames(List<HeaderFrame> frames) {
+        List<ByteBuffer> bufs = new ArrayList<>(frames.size() * 2);
+        for (HeaderFrame f : frames) {
+            bufs.addAll(encodeFrame(f));
+        }
+        return bufs;
+    }
+
+    public ByteBuffer encodeConnectionPreface(byte[] preface, SettingsFrame frame) {
+        final int length = frame.length();
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length + preface.length);
+        buf.put(preface);
+        putSettingsFrame(buf, frame, length);
+        buf.flip();
+        return buf;
+    }
+
+    public List<ByteBuffer> encodeFrame(Http2Frame frame) {
+        switch (frame.type()) {
+            case DataFrame.TYPE:
+                return encodeDataFrame((DataFrame) frame);
+            case HeadersFrame.TYPE:
+                return encodeHeadersFrame((HeadersFrame) frame);
+            case PriorityFrame.TYPE:
+                return encodePriorityFrame((PriorityFrame) frame);
+            case ResetFrame.TYPE:
+                return encodeResetFrame((ResetFrame) frame);
+            case SettingsFrame.TYPE:
+                return encodeSettingsFrame((SettingsFrame) frame);
+            case PushPromiseFrame.TYPE:
+                return encodePushPromiseFrame((PushPromiseFrame) frame);
+            case PingFrame.TYPE:
+                return encodePingFrame((PingFrame) frame);
+            case GoAwayFrame.TYPE:
+                return encodeGoAwayFrame((GoAwayFrame) frame);
+            case WindowUpdateFrame.TYPE:
+                return encodeWindowUpdateFrame((WindowUpdateFrame) frame);
+            case ContinuationFrame.TYPE:
+                return encodeContinuationFrame((ContinuationFrame) frame);
+            default:
+                throw new UnsupportedOperationException("Not supported frame "+frame.type()+" ("+frame.getClass().getName()+")");
+        }
+    }
+
+    private static final int NO_FLAGS = 0;
+    private static final int ZERO_STREAM = 0;
+
+    private List<ByteBuffer> encodeDataFrame(DataFrame frame) {
+        // non-zero stream
+        assert frame.streamid() != 0;
+        ByteBuffer buf = encodeDataFrameStart(frame);
+        if (frame.getFlag(DataFrame.PADDED)) {
+            return joinWithPadding(buf, frame.getData(), frame.getPadLength());
+        } else {
+            return join(buf, frame.getData());
+        }
+    }
+
+    private ByteBuffer encodeDataFrameStart(DataFrame frame) {
+        boolean isPadded = frame.getFlag(DataFrame.PADDED);
+        final int length = frame.getDataLength() + (isPadded ? (frame.getPadLength() + 1) : 0);
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0));
+        putHeader(buf, length, DataFrame.TYPE, frame.getFlags(), frame.streamid());
+        if (isPadded) {
+            buf.put((byte) frame.getPadLength());
+        }
+        buf.flip();
+        return buf;
+    }
+
+    private List<ByteBuffer> encodeHeadersFrame(HeadersFrame frame) {
+        // non-zero stream
+        assert frame.streamid() != 0;
+        ByteBuffer buf = encodeHeadersFrameStart(frame);
+        if (frame.getFlag(HeadersFrame.PADDED)) {
+            return joinWithPadding(buf, frame.getHeaderBlock(), frame.getPadLength());
+        } else {
+            return join(buf, frame.getHeaderBlock());
+        }
+    }
+
+    private ByteBuffer encodeHeadersFrameStart(HeadersFrame frame) {
+        boolean isPadded = frame.getFlag(HeadersFrame.PADDED);
+        boolean hasPriority = frame.getFlag(HeadersFrame.PRIORITY);
+        final int length = frame.getHeaderLength() + (isPadded ? (frame.getPadLength() + 1) : 0) + (hasPriority ? 5 : 0);
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 1 : 0) + (hasPriority ? 5 : 0));
+        putHeader(buf, length, HeadersFrame.TYPE, frame.getFlags(), frame.streamid());
+        if (isPadded) {
+            buf.put((byte) frame.getPadLength());
+        }
+        if (hasPriority) {
+            putPriority(buf, frame.getExclusive(), frame.getStreamDependency(), frame.getWeight());
+        }
+        buf.flip();
+        return buf;
+    }
+
+    private List<ByteBuffer> encodePriorityFrame(PriorityFrame frame) {
+        // non-zero stream; no flags
+        assert frame.streamid() != 0;
+        final int length = 5;
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
+        putHeader(buf, length, PriorityFrame.TYPE, NO_FLAGS, frame.streamid());
+        putPriority(buf, frame.exclusive(), frame.streamDependency(), frame.weight());
+        buf.flip();
+        return List.of(buf);
+    }
+
+    private List<ByteBuffer> encodeResetFrame(ResetFrame frame) {
+        // non-zero stream; no flags
+        assert frame.streamid() != 0;
+        final int length = 4;
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
+        putHeader(buf, length, ResetFrame.TYPE, NO_FLAGS, frame.streamid());
+        buf.putInt(frame.getErrorCode());
+        buf.flip();
+        return List.of(buf);
+    }
+
+    private List<ByteBuffer> encodeSettingsFrame(SettingsFrame frame) {
+        // only zero stream
+        assert frame.streamid() == 0;
+        final int length = frame.length();
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
+        putSettingsFrame(buf, frame, length);
+        buf.flip();
+        return List.of(buf);
+    }
+
+    private List<ByteBuffer> encodePushPromiseFrame(PushPromiseFrame frame) {
+        // non-zero stream
+        assert frame.streamid() != 0;
+        boolean isPadded = frame.getFlag(PushPromiseFrame.PADDED);
+        final int length = frame.getHeaderLength() + (isPadded ? 5 : 4);
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + (isPadded ? 5 : 4));
+        putHeader(buf, length, PushPromiseFrame.TYPE, frame.getFlags(), frame.streamid());
+        if (isPadded) {
+            buf.put((byte) frame.getPadLength());
+        }
+        buf.putInt(frame.getPromisedStream());
+        buf.flip();
+
+        if (frame.getFlag(PushPromiseFrame.PADDED)) {
+            return joinWithPadding(buf, frame.getHeaderBlock(), frame.getPadLength());
+        } else {
+            return join(buf, frame.getHeaderBlock());
+        }
+    }
+
+    private List<ByteBuffer> encodePingFrame(PingFrame frame) {
+        // only zero stream
+        assert frame.streamid() == 0;
+        final int length = 8;
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
+        putHeader(buf, length, PingFrame.TYPE, frame.getFlags(), ZERO_STREAM);
+        buf.put(frame.getData());
+        buf.flip();
+        return List.of(buf);
+    }
+
+    private List<ByteBuffer> encodeGoAwayFrame(GoAwayFrame frame) {
+        // only zero stream; no flags
+        assert frame.streamid() == 0;
+        byte[] debugData = frame.getDebugData();
+        final int length = 8 + debugData.length;
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
+        putHeader(buf, length, GoAwayFrame.TYPE, NO_FLAGS, ZERO_STREAM);
+        buf.putInt(frame.getLastStream());
+        buf.putInt(frame.getErrorCode());
+        if (debugData.length > 0) {
+            buf.put(debugData);
+        }
+        buf.flip();
+        return List.of(buf);
+    }
+
+    private List<ByteBuffer> encodeWindowUpdateFrame(WindowUpdateFrame frame) {
+        // any stream; no flags
+        final int length = 4;
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE + length);
+        putHeader(buf, length, WindowUpdateFrame.TYPE, NO_FLAGS, frame.streamid);
+        buf.putInt(frame.getUpdate());
+        buf.flip();
+        return List.of(buf);
+    }
+
+    private List<ByteBuffer> encodeContinuationFrame(ContinuationFrame frame) {
+        // non-zero stream;
+        assert frame.streamid() != 0;
+        final int length = frame.getHeaderLength();
+        ByteBuffer buf = getBuffer(Http2Frame.FRAME_HEADER_SIZE);
+        putHeader(buf, length, ContinuationFrame.TYPE, frame.getFlags(), frame.streamid());
+        buf.flip();
+        return join(buf, frame.getHeaderBlock());
+    }
+
+    private List<ByteBuffer> joinWithPadding(ByteBuffer buf, List<ByteBuffer> data, int padLength) {
+        int len = data.size();
+        if (len == 0) return List.of(buf, getPadding(padLength));
+        else if (len == 1) return List.of(buf, data.get(0), getPadding(padLength));
+        else if (len == 2) return List.of(buf, data.get(0), data.get(1), getPadding(padLength));
+        List<ByteBuffer> res = new ArrayList<>(len+2);
+        res.add(buf);
+        res.addAll(data);
+        res.add(getPadding(padLength));
+        return res;
+    }
+
+    private List<ByteBuffer> join(ByteBuffer buf, List<ByteBuffer> data) {
+        int len = data.size();
+        if (len == 0) return List.of(buf);
+        else if (len == 1) return List.of(buf, data.get(0));
+        else if (len == 2) return List.of(buf, data.get(0), data.get(1));
+        List<ByteBuffer> joined = new ArrayList<>(len + 1);
+        joined.add(buf);
+        joined.addAll(data);
+        return joined;
+    }
+
+    private void putSettingsFrame(ByteBuffer buf, SettingsFrame frame, int length) {
+        // only zero stream;
+        assert frame.streamid() == 0;
+        putHeader(buf, length, SettingsFrame.TYPE, frame.getFlags(), ZERO_STREAM);
+        frame.toByteBuffer(buf);
+    }
+
+    private void putHeader(ByteBuffer buf, int length, int type, int flags, int streamId) {
+        int x = (length << 8) + type;
+        buf.putInt(x);
+        buf.put((byte) flags);
+        buf.putInt(streamId);
+    }
+
+    private void putPriority(ByteBuffer buf, boolean exclusive, int streamDependency, int weight) {
+        buf.putInt(exclusive ? (1 << 31) + streamDependency : streamDependency);
+        buf.put((byte) weight);
+    }
+
+    private ByteBuffer getBuffer(int capacity) {
+        return ByteBuffer.allocate(capacity);
+    }
+
+    public ByteBuffer getPadding(int length) {
+        if (length > 255) {
+            throw new IllegalArgumentException("Padding too big");
+        }
+        return ByteBuffer.allocate(length); // zeroed!
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/GoAwayFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.net.http.frame;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class GoAwayFrame extends ErrorFrame {
+
+    private final int lastStream;
+    private final byte[] debugData;
+
+    public static final int TYPE = 0x7;
+
+
+    public GoAwayFrame(int lastStream, int errorCode) {
+        this(lastStream, errorCode, new byte[0]);
+    }
+
+    public GoAwayFrame(int lastStream, int errorCode, byte[] debugData) {
+        super(0, 0, errorCode);
+        this.lastStream = lastStream;
+        this.debugData = debugData.clone();
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return 8 + debugData.length;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " Debugdata: " + new String(debugData, UTF_8);
+    }
+
+    public int getLastStream() {
+        return this.lastStream;
+    }
+
+    public byte[] getDebugData() {
+        return debugData.clone();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/HeaderFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,76 @@
+/*
+ * 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.internal.net.http.frame;
+
+import jdk.internal.net.http.common.Utils;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Either a HeadersFrame or a ContinuationFrame
+ */
+public abstract class HeaderFrame extends Http2Frame {
+
+    final int headerLength;
+    final List<ByteBuffer> headerBlocks;
+
+    public static final int END_STREAM = 0x1;
+    public static final int END_HEADERS = 0x4;
+
+    public HeaderFrame(int streamid, int flags, List<ByteBuffer> headerBlocks) {
+        super(streamid, flags);
+        this.headerBlocks = headerBlocks;
+        this.headerLength = Utils.remaining(headerBlocks, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public String flagAsString(int flag) {
+        switch (flag) {
+            case END_HEADERS:
+                return "END_HEADERS";
+            case END_STREAM:
+                return "END_STREAM";
+        }
+        return super.flagAsString(flag);
+    }
+
+
+    public List<ByteBuffer> getHeaderBlock() {
+        return headerBlocks;
+    }
+
+    int getHeaderLength() {
+        return headerLength;
+    }
+
+    /**
+     * Returns true if this block is the final block of headers.
+     */
+    public boolean endHeaders() {
+        return getFlag(END_HEADERS);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/HeadersFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,114 @@
+/*
+ * 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.internal.net.http.frame;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class HeadersFrame extends HeaderFrame {
+
+    public static final int TYPE = 0x1;
+
+    // Flags
+    public static final int END_STREAM = 0x1;
+    public static final int PADDED = 0x8;
+    public static final int PRIORITY = 0x20;
+
+
+    private int padLength;
+    private int streamDependency;
+    private int weight;
+    private boolean exclusive;
+
+    public HeadersFrame(int streamid, int flags, List<ByteBuffer> headerBlocks, int padLength) {
+        super(streamid, flags, headerBlocks);
+        if (padLength > 0) {
+            setPadLength(padLength);
+        }
+    }
+
+    public HeadersFrame(int streamid, int flags, List<ByteBuffer> headerBlocks) {
+        super(streamid, flags, headerBlocks);
+    }
+
+    public HeadersFrame(int streamid, int flags, ByteBuffer headerBlock) {
+        this(streamid, flags, List.of(headerBlock));
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return headerLength
+                + ((flags & PADDED) != 0 ? (1 + padLength) : 0)
+                + ((flags & PRIORITY) != 0 ? 5 : 0);
+    }
+
+    @Override
+    public String flagAsString(int flag) {
+        switch (flag) {
+            case END_STREAM:
+                return "END_STREAM";
+            case PADDED:
+                return "PADDED";
+            case PRIORITY:
+                return "PRIORITY";
+        }
+        return super.flagAsString(flag);
+    }
+
+    public void setPadLength(int padLength) {
+        this.padLength = padLength;
+        flags |= PADDED;
+    }
+
+    int getPadLength() {
+        return padLength;
+    }
+
+    public void setPriority(int streamDependency, boolean exclusive, int weight) {
+        this.streamDependency = streamDependency;
+        this.exclusive = exclusive;
+        this.weight = weight;
+        this.flags |= PRIORITY;
+    }
+
+    public int getStreamDependency() {
+        return streamDependency;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public boolean getExclusive() {
+        return exclusive;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/Http2Frame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,141 @@
+/*
+ * 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.internal.net.http.frame;
+
+/**
+ * When sending a frame, the length field must be set in sub-class
+ * by calling computeLength()
+ */
+public abstract class Http2Frame {
+
+    public static final int FRAME_HEADER_SIZE = 9;
+
+    protected int streamid;
+    protected int flags;
+
+    public Http2Frame(int streamid, int flags) {
+        this.streamid = streamid;
+        this.flags = flags;
+    }
+
+    public int streamid() {
+        return streamid;
+    }
+
+    public void setFlag(int flag) {
+        flags |= flag;
+    }
+
+    public int getFlags() {
+        return flags;
+    }
+
+    public boolean getFlag(int flag) {
+        return (flags & flag) != 0;
+    }
+
+//    public void clearFlag(int flag) {
+//        flags &= 0xffffffff ^ flag;
+//    }
+
+    public void streamid(int streamid) {
+        this.streamid = streamid;
+    }
+
+
+    private String typeAsString() {
+        return asString(type());
+    }
+
+    public int type() {
+        return -1; // Unknown type
+    }
+
+    int length() {
+        return -1; // Unknown length
+    }
+
+
+    public static String asString(int type) {
+        switch (type) {
+          case DataFrame.TYPE:
+            return "DATA";
+          case HeadersFrame.TYPE:
+            return "HEADERS";
+          case ContinuationFrame.TYPE:
+            return "CONTINUATION";
+          case ResetFrame.TYPE:
+            return "RESET";
+          case PriorityFrame.TYPE:
+            return "PRIORITY";
+          case SettingsFrame.TYPE:
+            return "SETTINGS";
+          case GoAwayFrame.TYPE:
+            return "GOAWAY";
+          case PingFrame.TYPE:
+            return "PING";
+          case PushPromiseFrame.TYPE:
+            return "PUSH_PROMISE";
+          case WindowUpdateFrame.TYPE:
+            return "WINDOW_UPDATE";
+          default:
+            return "UNKNOWN";
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(typeAsString())
+                .append(": length=")
+                .append(Integer.toString(length()))
+                .append(", streamid=")
+                .append(streamid)
+                .append(", flags=");
+
+        int f = flags;
+        int i = 0;
+        if (f == 0) {
+            sb.append("0 ");
+        } else {
+            while (f != 0) {
+                if ((f & 1) == 1) {
+                    sb.append(flagAsString(1 << i))
+                      .append(' ');
+                }
+                f = f >> 1;
+                i++;
+            }
+        }
+        return sb.toString();
+    }
+
+    // Override
+    public String flagAsString(int f) {
+        return "unknown";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/MalformedFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.net.http.frame;
+
+public class MalformedFrame extends Http2Frame {
+
+    private int errorCode;
+    // if errorStream == 0 means Connection Error; RFC 7540 5.4.1
+    // if errorStream != 0 means Stream Error; RFC 7540 5.4.2
+    private int errorStream;
+    private String msg;
+
+    /**
+     * Creates Connection Error malformed frame
+     * @param errorCode - error code, as specified by RFC 7540
+     * @param msg - internal debug message
+     */
+    public MalformedFrame(int errorCode, String msg) {
+        this(errorCode, 0 , msg);
+    }
+
+    /**
+     * Creates Stream Error malformed frame
+     * @param errorCode - error code, as specified by RFC 7540
+     * @param errorStream - id of error stream (RST_FRAME will be send for this stream)
+     * @param msg - internal debug message
+     */
+    public MalformedFrame(int errorCode, int errorStream, String msg) {
+        super(0, 0);
+        this.errorCode = errorCode;
+        this.errorStream = errorStream;
+        this.msg = msg;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " MalformedFrame, Error: " + ErrorFrame.stringForCode(errorCode)
+                + " streamid: " + streamid + " reason: " + msg;
+    }
+
+    public int getErrorCode() {
+        return errorCode;
+    }
+
+    public String getMessage() {
+        return msg;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/OutgoingHeaders.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,83 @@
+/*
+ * 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.internal.net.http.frame;
+
+import java.net.http.HttpHeaders;
+
+/**
+ * Contains all parameters for outgoing headers. Is converted to
+ * HeadersFrame and ContinuationFrames by Http2Connection.
+ */
+public class OutgoingHeaders<T> extends Http2Frame {
+
+    int streamDependency;
+    int weight;
+    boolean exclusive;
+    T attachment;
+
+    public static final int PRIORITY = 0x20;
+
+    HttpHeaders user, system;
+
+    public OutgoingHeaders(HttpHeaders hdrs1, HttpHeaders hdrs2, T attachment) {
+        super(0, 0);
+        this.user = hdrs2;
+        this.system = hdrs1;
+        this.attachment = attachment;
+    }
+
+    public void setPriority(int streamDependency, boolean exclusive, int weight) {
+        this.streamDependency = streamDependency;
+        this.exclusive = exclusive;
+        this.weight = weight;
+        this.flags |= PRIORITY;
+    }
+
+    public int getStreamDependency() {
+        return streamDependency;
+    }
+
+    public int getWeight() {
+        return weight;
+    }
+
+    public boolean getExclusive() {
+        return exclusive;
+    }
+
+    public T getAttachment() {
+        return attachment;
+    }
+
+    public HttpHeaders getUserHeaders() {
+        return user;
+    }
+
+    public HttpHeaders getSystemHeaders() {
+        return system;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/PingFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.net.http.frame;
+
+public class PingFrame extends Http2Frame {
+
+
+    private final byte[] data;
+
+    public static final int TYPE = 0x6;
+
+    // Flags
+    public static final int ACK = 0x1;
+
+    public PingFrame(int flags, byte[] data) {
+        super(0, flags);
+        assert data.length == 8;
+        this.data = data.clone();
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return 8;
+    }
+
+    @Override
+    public String flagAsString(int flag) {
+        switch (flag) {
+        case ACK:
+            return "ACK";
+        }
+        return super.flagAsString(flag);
+    }
+
+    public byte[] getData() {
+        return data.clone();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/PriorityFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,65 @@
+/*
+ * 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.internal.net.http.frame;
+
+public class PriorityFrame extends Http2Frame {
+
+    private final int streamDependency;
+    private final int weight;
+    private final boolean exclusive;
+
+    public static final int TYPE = 0x2;
+
+    public PriorityFrame(int streamId, int streamDependency, boolean exclusive, int weight) {
+        super(streamId, 0);
+        this.streamDependency = streamDependency;
+        this.exclusive = exclusive;
+        this.weight = weight;
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return 5;
+    }
+
+    public int streamDependency() {
+        return streamDependency;
+    }
+
+    public int weight() {
+        return weight;
+    }
+
+    public boolean exclusive() {
+        return exclusive;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/PushPromiseFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,95 @@
+/*
+ * 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.internal.net.http.frame;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class PushPromiseFrame extends HeaderFrame {
+
+    private int padLength;
+    private final int promisedStream;
+
+    public static final int TYPE = 0x5;
+
+    // Flags
+    public static final int END_HEADERS = 0x4;
+    public static final int PADDED = 0x8;
+
+    public PushPromiseFrame(int streamid, int flags, int promisedStream, List<ByteBuffer> buffers, int padLength) {
+        super(streamid, flags, buffers);
+        this.promisedStream = promisedStream;
+        if(padLength > 0 ) {
+            setPadLength(padLength);
+        }
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return headerLength + ((flags & PADDED) != 0 ? 5 : 4);
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " promisedStreamid: " + promisedStream
+                + " headerLength: " + headerLength;
+    }
+
+    @Override
+    public String flagAsString(int flag) {
+        switch (flag) {
+            case PADDED:
+                return "PADDED";
+            case END_HEADERS:
+                return "END_HEADERS";
+        }
+        return super.flagAsString(flag);
+    }
+
+    public void setPadLength(int padLength) {
+        this.padLength = padLength;
+        flags |= PADDED;
+    }
+
+    public int getPadLength() {
+        return padLength;
+    }
+
+    public int getPromisedStream() {
+        return promisedStream;
+    }
+
+    @Override
+    public boolean endHeaders() {
+        return getFlag(END_HEADERS);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/ResetFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,48 @@
+/*
+ * 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.internal.net.http.frame;
+
+public class ResetFrame extends ErrorFrame {
+
+    public static final int TYPE = 0x3;
+
+    // See ErrorFrame for error values
+
+    public ResetFrame(int streamid, int errorCode) {
+        super(streamid, 0, errorCode);
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return 4;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/SettingsFrame.java	Wed Feb 07 21:45:37 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.internal.net.http.frame;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public class SettingsFrame extends Http2Frame {
+
+    private final int[] parameters;
+
+    public static final int TYPE = 0x4;
+
+    // Flags
+    public static final int ACK = 0x1;
+
+    @Override
+    public String flagAsString(int flag) {
+        switch (flag) {
+        case ACK:
+            return "ACK";
+        }
+        return super.flagAsString(flag);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(super.toString())
+          .append(" Settings: ");
+
+        for (int i = 0; i < MAX_PARAM; i++) {
+            if (parameters[i] != -1) {
+                sb.append(name(i))
+                  .append("=")
+                  .append(Integer.toString(parameters[i]))
+                  .append(' ');
+            }
+        }
+        return sb.toString();
+    }
+
+    // Parameters
+    public static final int HEADER_TABLE_SIZE = 0x1;
+    public static final int ENABLE_PUSH = 0x2;
+    public static final int MAX_CONCURRENT_STREAMS = 0x3;
+    public static final int INITIAL_WINDOW_SIZE = 0x4;
+    public static final int MAX_FRAME_SIZE = 0x5;
+    public static final int MAX_HEADER_LIST_SIZE = 0x6;
+
+    private String name(int i) {
+        switch (i+1) {
+        case HEADER_TABLE_SIZE:
+            return "HEADER_TABLE_SIZE";
+        case ENABLE_PUSH:
+            return "ENABLE_PUSH";
+        case MAX_CONCURRENT_STREAMS:
+            return "MAX_CONCURRENT_STREAMS";
+        case INITIAL_WINDOW_SIZE:
+            return "INITIAL_WINDOW_SIZE";
+        case MAX_FRAME_SIZE:
+            return "MAX_FRAME_SIZE";
+        case MAX_HEADER_LIST_SIZE:
+            return "MAX_HEADER_LIST_SIZE";
+        }
+        return "unknown parameter";
+    }
+    public static final int MAX_PARAM = 0x6;
+
+    public SettingsFrame(int flags) {
+        super(0, flags);
+        parameters = new int [MAX_PARAM];
+        Arrays.fill(parameters, -1);
+    }
+
+    public SettingsFrame() {
+        this(0);
+    }
+
+    public SettingsFrame(SettingsFrame other) {
+        super(0, other.flags);
+        parameters = Arrays.copyOf(other.parameters, MAX_PARAM);
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    public int getParameter(int paramID) {
+        if (paramID > MAX_PARAM) {
+            throw new IllegalArgumentException("illegal parameter");
+        }
+        return parameters[paramID-1];
+    }
+
+    public SettingsFrame setParameter(int paramID, int value) {
+        if (paramID > MAX_PARAM) {
+            throw new IllegalArgumentException("illegal parameter");
+        }
+        parameters[paramID-1] = value;
+        return this;
+    }
+
+    int length() {
+        int len = 0;
+        for (int i : parameters) {
+            if (i != -1) {
+                len  += 6;
+            }
+        }
+        return len;
+    }
+
+    void toByteBuffer(ByteBuffer buf) {
+        for (int i = 0; i < MAX_PARAM; i++) {
+            if (parameters[i] != -1) {
+                buf.putShort((short) (i + 1));
+                buf.putInt(parameters[i]);
+            }
+        }
+    }
+
+    public byte[] toByteArray() {
+        byte[] bytes = new byte[length()];
+        ByteBuffer buf = ByteBuffer.wrap(bytes);
+        toByteBuffer(buf);
+        return bytes;
+    }
+
+    private static final int K = 1024;
+
+    public static SettingsFrame getDefaultSettings() {
+        SettingsFrame f = new SettingsFrame();
+        // TODO: check these values
+        f.setParameter(ENABLE_PUSH, 1);
+        f.setParameter(HEADER_TABLE_SIZE, 4 * K);
+        f.setParameter(MAX_CONCURRENT_STREAMS, 35);
+        f.setParameter(INITIAL_WINDOW_SIZE, 64 * K - 1);
+        f.setParameter(MAX_FRAME_SIZE, 16 * K);
+        return f;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/WindowUpdateFrame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,62 @@
+/*
+ * 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.internal.net.http.frame;
+
+public class WindowUpdateFrame extends Http2Frame {
+
+    private final int windowUpdate;
+
+    public static final int TYPE = 0x8;
+
+    public WindowUpdateFrame(int streamid, int windowUpdate) {
+        super(streamid, 0);
+        this.windowUpdate = windowUpdate;
+    }
+
+    @Override
+    public int type() {
+        return TYPE;
+    }
+
+    @Override
+    int length() {
+        return 4;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(super.toString())
+          .append(" WindowUpdate: ")
+          .append(windowUpdate);
+        return sb.toString();
+    }
+
+    public int getUpdate() {
+        return this.windowUpdate;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/BinaryRepresentationWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,34 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+
+interface BinaryRepresentationWriter {
+
+    boolean write(HeaderTable table, ByteBuffer destination);
+
+    BinaryRepresentationWriter reset();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/BulkSizeUpdateWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,78 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+
+import static java.util.Objects.requireNonNull;
+
+final class BulkSizeUpdateWriter implements BinaryRepresentationWriter {
+
+    private final SizeUpdateWriter writer = new SizeUpdateWriter();
+    private Iterator<Integer> maxSizes;
+    private boolean writing;
+    private boolean configured;
+
+    BulkSizeUpdateWriter maxHeaderTableSizes(Iterable<Integer> sizes) {
+        if (configured) {
+            throw new IllegalStateException("Already configured");
+        }
+        requireNonNull(sizes, "sizes");
+        maxSizes = sizes.iterator();
+        configured = true;
+        return this;
+    }
+
+    @Override
+    public boolean write(HeaderTable table, ByteBuffer destination) {
+        if (!configured) {
+            throw new IllegalStateException("Configure first");
+        }
+        while (true) {
+            if (writing) {
+                if (!writer.write(table, destination)) {
+                    return false;
+                }
+                writing = false;
+            } else if (maxSizes.hasNext()) {
+                writing = true;
+                writer.reset();
+                writer.maxHeaderTableSize(maxSizes.next());
+            } else {
+                configured = false;
+                return true;
+            }
+        }
+    }
+
+    @Override
+    public BulkSizeUpdateWriter reset() {
+        maxSizes = null;
+        writing = false;
+        configured = false;
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Decoder.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,594 @@
+/*
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.hpack;
+
+import jdk.internal.net.http.hpack.HPACK.Logger;
+import jdk.internal.vm.annotation.Stable;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Decodes headers from their binary representation.
+ *
+ * <p> Typical lifecycle looks like this:
+ *
+ * <p> {@link #Decoder(int) new Decoder}
+ * ({@link #setMaxCapacity(int) setMaxCapacity}?
+ * {@link #decode(ByteBuffer, boolean, DecodingCallback) decode})*
+ *
+ * @apiNote
+ *
+ * <p> The design intentions behind Decoder were to facilitate flexible and
+ * incremental style of processing.
+ *
+ * <p> {@code Decoder} does not require a complete header block in a single
+ * {@code ByteBuffer}. The header block can be spread across many buffers of any
+ * size and decoded one-by-one the way it makes most sense for the user. This
+ * way also allows not to limit the size of the header block.
+ *
+ * <p> Headers are delivered to the {@linkplain DecodingCallback callback} as
+ * soon as they become decoded. Using the callback also gives the user a freedom
+ * to decide how headers are processed. The callback does not limit the number
+ * of headers decoded during single decoding operation.
+ *
+ * @since 9
+ */
+public final class Decoder {
+
+    private final Logger logger;
+    private static final AtomicLong DECODERS_IDS = new AtomicLong();
+
+    @Stable
+    private static final State[] states = new State[256];
+
+    static {
+        // To be able to do a quick lookup, each of 256 possibilities are mapped
+        // to corresponding states.
+        //
+        // We can safely do this since patterns 1, 01, 001, 0001, 0000 are
+        // Huffman prefixes and therefore are inherently not ambiguous.
+        //
+        // I do it mainly for better debugging (to not go each time step by step
+        // through if...else tree). As for performance win for the decoding, I
+        // believe is negligible.
+        for (int i = 0; i < states.length; i++) {
+            if ((i & 0b1000_0000) == 0b1000_0000) {
+                states[i] = State.INDEXED;
+            } else if ((i & 0b1100_0000) == 0b0100_0000) {
+                states[i] = State.LITERAL_WITH_INDEXING;
+            } else if ((i & 0b1110_0000) == 0b0010_0000) {
+                states[i] = State.SIZE_UPDATE;
+            } else if ((i & 0b1111_0000) == 0b0001_0000) {
+                states[i] = State.LITERAL_NEVER_INDEXED;
+            } else if ((i & 0b1111_0000) == 0b0000_0000) {
+                states[i] = State.LITERAL;
+            } else {
+                throw new InternalError(String.valueOf(i));
+            }
+        }
+    }
+
+    private final long id;
+    private final HeaderTable table;
+
+    private State state = State.READY;
+    private final IntegerReader integerReader;
+    private final StringReader stringReader;
+    private final StringBuilder name;
+    private final StringBuilder value;
+    private int intValue;
+    private boolean firstValueRead;
+    private boolean firstValueIndex;
+    private boolean nameHuffmanEncoded;
+    private boolean valueHuffmanEncoded;
+    private int capacity;
+
+    /**
+     * Constructs a {@code Decoder} with the specified initial capacity of the
+     * header table.
+     *
+     * <p> The value has to be agreed between decoder and encoder out-of-band,
+     * e.g. by a protocol that uses HPACK
+     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
+     *
+     * @param capacity
+     *         a non-negative integer
+     *
+     * @throws IllegalArgumentException
+     *         if capacity is negative
+     */
+    public Decoder(int capacity) {
+        id = DECODERS_IDS.incrementAndGet();
+        logger = HPACK.getLogger().subLogger("Decoder#" + id);
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("new decoder with maximum table size %s",
+                                            capacity));
+        }
+        if (logger.isLoggable(NORMAL)) {
+            /* To correlate with logging outside HPACK, knowing
+               hashCode/toString is important */
+            logger.log(NORMAL, () -> {
+                String hashCode = Integer.toHexString(
+                        System.identityHashCode(this));
+                return format("toString='%s', identityHashCode=%s",
+                              toString(), hashCode);
+            });
+        }
+        setMaxCapacity0(capacity);
+        table = new HeaderTable(capacity, logger.subLogger("HeaderTable"));
+        integerReader = new IntegerReader();
+        stringReader = new StringReader();
+        name = new StringBuilder(512);
+        value = new StringBuilder(1024);
+    }
+
+    /**
+     * Sets a maximum capacity of the header table.
+     *
+     * <p> The value has to be agreed between decoder and encoder out-of-band,
+     * e.g. by a protocol that uses HPACK
+     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
+     *
+     * @param capacity
+     *         a non-negative integer
+     *
+     * @throws IllegalArgumentException
+     *         if capacity is negative
+     */
+    public void setMaxCapacity(int capacity) {
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("setting maximum table size to %s",
+                                            capacity));
+        }
+        setMaxCapacity0(capacity);
+    }
+
+    private void setMaxCapacity0(int capacity) {
+        if (capacity < 0) {
+            throw new IllegalArgumentException("capacity >= 0: " + capacity);
+        }
+        // FIXME: await capacity update if less than what was prior to it
+        this.capacity = capacity;
+    }
+
+    /**
+     * Decodes a header block from the given buffer to the given callback.
+     *
+     * <p> Suppose a header block is represented by a sequence of
+     * {@code ByteBuffer}s in the form of {@code Iterator<ByteBuffer>}. And the
+     * consumer of decoded headers is represented by the callback. Then to
+     * decode the header block, the following approach might be used:
+     *
+     * <pre>{@code
+     * while (buffers.hasNext()) {
+     *     ByteBuffer input = buffers.next();
+     *     decoder.decode(input, callback, !buffers.hasNext());
+     * }
+     * }</pre>
+     *
+     * <p> The decoder reads as much as possible of the header block from the
+     * given buffer, starting at the buffer's position, and increments its
+     * position to reflect the bytes read. The buffer's mark and limit will not
+     * be modified.
+     *
+     * <p> Once the method is invoked with {@code endOfHeaderBlock == true}, the
+     * current header block is deemed ended, and inconsistencies, if any, are
+     * reported immediately by throwing an {@code IOException}.
+     *
+     * <p> Each callback method is called only after the implementation has
+     * processed the corresponding bytes. If the bytes revealed a decoding
+     * error, the callback method is not called.
+     *
+     * <p> In addition to exceptions thrown directly by the method, any
+     * exceptions thrown from the {@code callback} will bubble up.
+     *
+     * @apiNote The method asks for {@code endOfHeaderBlock} flag instead of
+     * returning it for two reasons. The first one is that the user of the
+     * decoder always knows which chunk is the last. The second one is to throw
+     * the most detailed exception possible, which might be useful for
+     * diagnosing issues.
+     *
+     * @implNote This implementation is not atomic in respect to decoding
+     * errors. In other words, if the decoding operation has thrown a decoding
+     * error, the decoder is no longer usable.
+     *
+     * @param headerBlock
+     *         the chunk of the header block, may be empty
+     * @param endOfHeaderBlock
+     *         true if the chunk is the final (or the only one) in the sequence
+     *
+     * @param consumer
+     *         the callback
+     * @throws IOException
+     *         in case of a decoding error
+     * @throws NullPointerException
+     *         if either headerBlock or consumer are null
+     */
+    public void decode(ByteBuffer headerBlock,
+                       boolean endOfHeaderBlock,
+                       DecodingCallback consumer) throws IOException {
+        requireNonNull(headerBlock, "headerBlock");
+        requireNonNull(consumer, "consumer");
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("reading %s, end of header block? %s",
+                                            headerBlock, endOfHeaderBlock));
+        }
+        while (headerBlock.hasRemaining()) {
+            proceed(headerBlock, consumer);
+        }
+        if (endOfHeaderBlock && state != State.READY) {
+            logger.log(NORMAL, () -> format("unexpected end of %s representation",
+                                            state));
+            throw new IOException("Unexpected end of header block");
+        }
+    }
+
+    private void proceed(ByteBuffer input, DecodingCallback action)
+            throws IOException {
+        switch (state) {
+            case READY:
+                resumeReady(input);
+                break;
+            case INDEXED:
+                resumeIndexed(input, action);
+                break;
+            case LITERAL:
+                resumeLiteral(input, action);
+                break;
+            case LITERAL_WITH_INDEXING:
+                resumeLiteralWithIndexing(input, action);
+                break;
+            case LITERAL_NEVER_INDEXED:
+                resumeLiteralNeverIndexed(input, action);
+                break;
+            case SIZE_UPDATE:
+                resumeSizeUpdate(input, action);
+                break;
+            default:
+                throw new InternalError("Unexpected decoder state: " + state);
+        }
+    }
+
+    private void resumeReady(ByteBuffer input) {
+        int b = input.get(input.position()) & 0xff; // absolute read
+        State s = states[b];
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("next binary representation %s (first byte 0x%02x)",
+                                           s, b));
+        }
+        switch (s) {
+            case INDEXED:
+                integerReader.configure(7);
+                state = State.INDEXED;
+                firstValueIndex = true;
+                break;
+            case LITERAL:
+                state = State.LITERAL;
+                firstValueIndex = (b & 0b0000_1111) != 0;
+                if (firstValueIndex) {
+                    integerReader.configure(4);
+                }
+                break;
+            case LITERAL_WITH_INDEXING:
+                state = State.LITERAL_WITH_INDEXING;
+                firstValueIndex = (b & 0b0011_1111) != 0;
+                if (firstValueIndex) {
+                    integerReader.configure(6);
+                }
+                break;
+            case LITERAL_NEVER_INDEXED:
+                state = State.LITERAL_NEVER_INDEXED;
+                firstValueIndex = (b & 0b0000_1111) != 0;
+                if (firstValueIndex) {
+                    integerReader.configure(4);
+                }
+                break;
+            case SIZE_UPDATE:
+                integerReader.configure(5);
+                state = State.SIZE_UPDATE;
+                firstValueIndex = true;
+                break;
+            default:
+                throw new InternalError(String.valueOf(s));
+        }
+        if (!firstValueIndex) {
+            input.get(); // advance, next stop: "String Literal"
+        }
+    }
+
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 1 |        Index (7+)         |
+    //            +---+---------------------------+
+    //
+    private void resumeIndexed(ByteBuffer input, DecodingCallback action)
+            throws IOException {
+        if (!integerReader.read(input)) {
+            return;
+        }
+        intValue = integerReader.get();
+        integerReader.reset();
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("indexed %s", intValue));
+        }
+        try {
+            HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
+            action.onIndexed(intValue, f.name, f.value);
+        } finally {
+            state = State.READY;
+        }
+    }
+
+    private HeaderTable.HeaderField getHeaderFieldAt(int index)
+            throws IOException
+    {
+        HeaderTable.HeaderField f;
+        try {
+            f = table.get(index);
+        } catch (IndexOutOfBoundsException e) {
+            throw new IOException("header fields table index", e);
+        }
+        return f;
+    }
+
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 0 | 0 | 0 | 0 |  Index (4+)   |
+    //            +---+---+-----------------------+
+    //            | H |     Value Length (7+)     |
+    //            +---+---------------------------+
+    //            | Value String (Length octets)  |
+    //            +-------------------------------+
+    //
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 0 | 0 | 0 | 0 |       0       |
+    //            +---+---+-----------------------+
+    //            | H |     Name Length (7+)      |
+    //            +---+---------------------------+
+    //            |  Name String (Length octets)  |
+    //            +---+---------------------------+
+    //            | H |     Value Length (7+)     |
+    //            +---+---------------------------+
+    //            | Value String (Length octets)  |
+    //            +-------------------------------+
+    //
+    private void resumeLiteral(ByteBuffer input, DecodingCallback action)
+            throws IOException {
+        if (!completeReading(input)) {
+            return;
+        }
+        try {
+            if (firstValueIndex) {
+                if (logger.isLoggable(NORMAL)) {
+                    logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
+                                                    intValue, value));
+                }
+                HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
+                action.onLiteral(intValue, f.name, value, valueHuffmanEncoded);
+            } else {
+                if (logger.isLoggable(NORMAL)) {
+                    logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
+                                                    name, value));
+                }
+                action.onLiteral(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
+            }
+        } finally {
+            cleanUpAfterReading();
+        }
+    }
+
+    //
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 0 | 1 |      Index (6+)       |
+    //            +---+---+-----------------------+
+    //            | H |     Value Length (7+)     |
+    //            +---+---------------------------+
+    //            | Value String (Length octets)  |
+    //            +-------------------------------+
+    //
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 0 | 1 |           0           |
+    //            +---+---+-----------------------+
+    //            | H |     Name Length (7+)      |
+    //            +---+---------------------------+
+    //            |  Name String (Length octets)  |
+    //            +---+---------------------------+
+    //            | H |     Value Length (7+)     |
+    //            +---+---------------------------+
+    //            | Value String (Length octets)  |
+    //            +-------------------------------+
+    //
+    private void resumeLiteralWithIndexing(ByteBuffer input,
+                                           DecodingCallback action)
+            throws IOException {
+        if (!completeReading(input)) {
+            return;
+        }
+        try {
+            //
+            // 1. (name, value) will be stored in the table as strings
+            // 2. Most likely the callback will also create strings from them
+            // ------------------------------------------------------------------------
+            //    Let's create those string beforehand (and only once!) to benefit everyone
+            //
+            String n;
+            String v = value.toString();
+            if (firstValueIndex) {
+                if (logger.isLoggable(NORMAL)) {
+                    logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
+                                                    intValue, value));
+                }
+                HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
+                n = f.name;
+                action.onLiteralWithIndexing(intValue, n, v, valueHuffmanEncoded);
+            } else {
+                n = name.toString();
+                if (logger.isLoggable(NORMAL)) {
+                    logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
+                                                    n, value));
+                }
+                action.onLiteralWithIndexing(n, nameHuffmanEncoded, v, valueHuffmanEncoded);
+            }
+            table.put(n, v);
+        } finally {
+            cleanUpAfterReading();
+        }
+    }
+
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 0 | 0 | 0 | 1 |  Index (4+)   |
+    //            +---+---+-----------------------+
+    //            | H |     Value Length (7+)     |
+    //            +---+---------------------------+
+    //            | Value String (Length octets)  |
+    //            +-------------------------------+
+    //
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 0 | 0 | 0 | 1 |       0       |
+    //            +---+---+-----------------------+
+    //            | H |     Name Length (7+)      |
+    //            +---+---------------------------+
+    //            |  Name String (Length octets)  |
+    //            +---+---------------------------+
+    //            | H |     Value Length (7+)     |
+    //            +---+---------------------------+
+    //            | Value String (Length octets)  |
+    //            +-------------------------------+
+    //
+    private void resumeLiteralNeverIndexed(ByteBuffer input,
+                                           DecodingCallback action)
+            throws IOException {
+        if (!completeReading(input)) {
+            return;
+        }
+        try {
+            if (firstValueIndex) {
+                if (logger.isLoggable(NORMAL)) {
+                    logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
+                                                    intValue, value));
+                }
+                HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
+                action.onLiteralNeverIndexed(intValue, f.name, value, valueHuffmanEncoded);
+            } else {
+                if (logger.isLoggable(NORMAL)) {
+                    logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
+                                                    name, value));
+                }
+                action.onLiteralNeverIndexed(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
+            }
+        } finally {
+            cleanUpAfterReading();
+        }
+    }
+
+    //              0   1   2   3   4   5   6   7
+    //            +---+---+---+---+---+---+---+---+
+    //            | 0 | 0 | 1 |   Max size (5+)   |
+    //            +---+---------------------------+
+    //
+    private void resumeSizeUpdate(ByteBuffer input,
+                                  DecodingCallback action) throws IOException {
+        if (!integerReader.read(input)) {
+            return;
+        }
+        intValue = integerReader.get();
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("dynamic table size update %s",
+                                            intValue));
+        }
+        assert intValue >= 0;
+        if (intValue > capacity) {
+            throw new IOException(
+                    format("Received capacity exceeds expected: capacity=%s, expected=%s",
+                           intValue, capacity));
+        }
+        integerReader.reset();
+        try {
+            action.onSizeUpdate(intValue);
+            table.setMaxSize(intValue);
+        } finally {
+            state = State.READY;
+        }
+    }
+
+    private boolean completeReading(ByteBuffer input) throws IOException {
+        if (!firstValueRead) {
+            if (firstValueIndex) {
+                if (!integerReader.read(input)) {
+                    return false;
+                }
+                intValue = integerReader.get();
+                integerReader.reset();
+            } else {
+                if (!stringReader.read(input, name)) {
+                    return false;
+                }
+                nameHuffmanEncoded = stringReader.isHuffmanEncoded();
+                stringReader.reset();
+            }
+            firstValueRead = true;
+            return false;
+        } else {
+            if (!stringReader.read(input, value)) {
+                return false;
+            }
+        }
+        valueHuffmanEncoded = stringReader.isHuffmanEncoded();
+        stringReader.reset();
+        return true;
+    }
+
+    private void cleanUpAfterReading() {
+        name.setLength(0);
+        value.setLength(0);
+        firstValueRead = false;
+        state = State.READY;
+    }
+
+    private enum State {
+        READY,
+        INDEXED,
+        LITERAL_NEVER_INDEXED,
+        LITERAL,
+        LITERAL_WITH_INDEXING,
+        SIZE_UPDATE
+    }
+
+    HeaderTable getTable() {
+        return table;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/DecodingCallback.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,295 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Delivers results of the {@link Decoder#decode(ByteBuffer, boolean,
+ * DecodingCallback) decoding operation}.
+ *
+ * <p> Methods of the callback are never called by a decoder with any of the
+ * arguments being {@code null}.
+ *
+ * @apiNote
+ *
+ * <p> The callback provides methods for all possible
+ * <a href="https://tools.ietf.org/html/rfc7541#section-6">binary representations</a>.
+ * This could be useful for implementing an intermediary, logging, debugging,
+ * etc.
+ *
+ * <p> The callback is an interface in order to interoperate with lambdas (in
+ * the most common use case):
+ * <pre>{@code
+ *     DecodingCallback callback = (name, value) -> System.out.println(name + ", " + value);
+ * }</pre>
+ *
+ * <p> Names and values are {@link CharSequence}s rather than {@link String}s in
+ * order to allow users to decide whether or not they need to create objects. A
+ * {@code CharSequence} might be used in-place, for example, to be appended to
+ * an {@link Appendable} (e.g. {@link StringBuilder}) and then discarded.
+ *
+ * <p> That said, if a passed {@code CharSequence} needs to outlast the method
+ * call, it needs to be copied.
+ *
+ * @since 9
+ */
+@FunctionalInterface
+public interface DecodingCallback {
+
+    /**
+     * A method the more specific methods of the callback forward their calls
+     * to.
+     *
+     * @param name
+     *         header name
+     * @param value
+     *         header value
+     */
+    void onDecoded(CharSequence name, CharSequence value);
+
+    /**
+     * A more finer-grained version of {@link #onDecoded(CharSequence,
+     * CharSequence)} that also reports on value sensitivity.
+     *
+     * <p> Value sensitivity must be considered, for example, when implementing
+     * an intermediary. A {@code value} is sensitive if it was represented as <a
+     * href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal Header
+     * Field Never Indexed</a>.
+     *
+     * <p> It is required that intermediaries MUST use the {@linkplain
+     * Encoder#header(CharSequence, CharSequence, boolean) same representation}
+     * for encoding this header field in order to protect its value which is not
+     * to be put at risk by compressing it.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes {@code onDecoded(name, value)}.
+     *
+     * @param name
+     *         header name
+     * @param value
+     *         header value
+     * @param sensitive
+     *         whether or not the value is sensitive
+     *
+     * @see #onLiteralNeverIndexed(int, CharSequence, CharSequence, boolean)
+     * @see #onLiteralNeverIndexed(CharSequence, boolean, CharSequence, boolean)
+     */
+    default void onDecoded(CharSequence name,
+                           CharSequence value,
+                           boolean sensitive) {
+        onDecoded(name, value);
+    }
+
+    /**
+     * An <a href="https://tools.ietf.org/html/rfc7541#section-6.1">Indexed
+     * Header Field</a> decoded.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes
+     * {@code onDecoded(name, value, false)}.
+     *
+     * @param index
+     *         index of an entry in the table
+     * @param name
+     *         header name
+     * @param value
+     *         header value
+     */
+    default void onIndexed(int index, CharSequence name, CharSequence value) {
+        onDecoded(name, value, false);
+    }
+
+    /**
+     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.2">Literal
+     * Header Field without Indexing</a> decoded, where a {@code name} was
+     * referred by an {@code index}.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes
+     * {@code onDecoded(name, value, false)}.
+     *
+     * @param index
+     *         index of an entry in the table
+     * @param name
+     *         header name
+     * @param value
+     *         header value
+     * @param valueHuffman
+     *         if the {@code value} was Huffman encoded
+     */
+    default void onLiteral(int index,
+                           CharSequence name,
+                           CharSequence value,
+                           boolean valueHuffman) {
+        onDecoded(name, value, false);
+    }
+
+    /**
+     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.2">Literal
+     * Header Field without Indexing</a> decoded, where both a {@code name} and
+     * a {@code value} were literal.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes
+     * {@code onDecoded(name, value, false)}.
+     *
+     * @param name
+     *         header name
+     * @param nameHuffman
+     *         if the {@code name} was Huffman encoded
+     * @param value
+     *         header value
+     * @param valueHuffman
+     *         if the {@code value} was Huffman encoded
+     */
+    default void onLiteral(CharSequence name,
+                           boolean nameHuffman,
+                           CharSequence value,
+                           boolean valueHuffman) {
+        onDecoded(name, value, false);
+    }
+
+    /**
+     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal
+     * Header Field Never Indexed</a> decoded, where a {@code name}
+     * was referred by an {@code index}.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes
+     * {@code onDecoded(name, value, true)}.
+     *
+     * @param index
+     *         index of an entry in the table
+     * @param name
+     *         header name
+     * @param value
+     *         header value
+     * @param valueHuffman
+     *         if the {@code value} was Huffman encoded
+     */
+    default void onLiteralNeverIndexed(int index,
+                                       CharSequence name,
+                                       CharSequence value,
+                                       boolean valueHuffman) {
+        onDecoded(name, value, true);
+    }
+
+    /**
+     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal
+     * Header Field Never Indexed</a> decoded, where both a {@code
+     * name} and a {@code value} were literal.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes
+     * {@code onDecoded(name, value, true)}.
+     *
+     * @param name
+     *         header name
+     * @param nameHuffman
+     *         if the {@code name} was Huffman encoded
+     * @param value
+     *         header value
+     * @param valueHuffman
+     *         if the {@code value} was Huffman encoded
+     */
+    default void onLiteralNeverIndexed(CharSequence name,
+                                       boolean nameHuffman,
+                                       CharSequence value,
+                                       boolean valueHuffman) {
+        onDecoded(name, value, true);
+    }
+
+    /**
+     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.1">Literal
+     * Header Field with Incremental Indexing</a> decoded, where a {@code name}
+     * was referred by an {@code index}.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes
+     * {@code onDecoded(name, value, false)}.
+     *
+     * @param index
+     *         index of an entry in the table
+     * @param name
+     *         header name
+     * @param value
+     *         header value
+     * @param valueHuffman
+     *         if the {@code value} was Huffman encoded
+     */
+    default void onLiteralWithIndexing(int index,
+                                       CharSequence name,
+                                       CharSequence value,
+                                       boolean valueHuffman) {
+        onDecoded(name, value, false);
+    }
+
+    /**
+     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.1">Literal
+     * Header Field with Incremental Indexing</a> decoded, where both a {@code
+     * name} and a {@code value} were literal.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation invokes
+     * {@code onDecoded(name, value, false)}.
+     *
+     * @param name
+     *         header name
+     * @param nameHuffman
+     *         if the {@code name} was Huffman encoded
+     * @param value
+     *         header value
+     * @param valueHuffman
+     *         if the {@code value} was Huffman encoded
+     */
+    default void onLiteralWithIndexing(CharSequence name,
+                                       boolean nameHuffman,
+                                       CharSequence value,
+                                       boolean valueHuffman) {
+        onDecoded(name, value, false);
+    }
+
+    /**
+     * A <a href="https://tools.ietf.org/html/rfc7541#section-6.3">Dynamic Table
+     * Size Update</a> decoded.
+     *
+     * @implSpec
+     *
+     * <p> The default implementation does nothing.
+     *
+     * @param capacity
+     *         new capacity of the header table
+     */
+    default void onSizeUpdate(int capacity) { }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Encoder.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,525 @@
+/*
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.hpack;
+
+import jdk.internal.net.http.hpack.HPACK.Logger;
+
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL;
+
+/**
+ * Encodes headers to their binary representation.
+ *
+ * <p> Typical lifecycle looks like this:
+ *
+ * <p> {@link #Encoder(int) new Encoder}
+ * ({@link #setMaxCapacity(int) setMaxCapacity}?
+ * {@link #encode(ByteBuffer) encode})*
+ *
+ * <p> Suppose headers are represented by {@code Map<String, List<String>>}.
+ * A supplier and a consumer of {@link ByteBuffer}s in forms of
+ * {@code Supplier<ByteBuffer>} and {@code Consumer<ByteBuffer>} respectively.
+ * Then to encode headers, the following approach might be used:
+ *
+ * <pre>{@code
+ *     for (Map.Entry<String, List<String>> h : headers.entrySet()) {
+ *         String name = h.getKey();
+ *         for (String value : h.getValue()) {
+ *             encoder.header(name, value);        // Set up header
+ *             boolean encoded;
+ *             do {
+ *                 ByteBuffer b = buffersSupplier.get();
+ *                 encoded = encoder.encode(b);    // Encode the header
+ *                 buffersConsumer.accept(b);
+ *             } while (!encoded);
+ *         }
+ *     }
+ * }</pre>
+ *
+ * <p> Though the specification <a href="https://tools.ietf.org/html/rfc7541#section-2">does not define</a>
+ * how an encoder is to be implemented, a default implementation is provided by
+ * the method {@link #header(CharSequence, CharSequence, boolean)}.
+ *
+ * <p> To provide a custom encoding implementation, {@code Encoder} has to be
+ * extended. A subclass then can access methods for encoding using specific
+ * representations (e.g. {@link #literal(int, CharSequence, boolean) literal},
+ * {@link #indexed(int) indexed}, etc.)
+ *
+ * @apiNote
+ *
+ * <p> An Encoder provides an incremental way of encoding headers.
+ * {@link #encode(ByteBuffer)} takes a buffer a returns a boolean indicating
+ * whether, or not, the buffer was sufficiently sized to hold the
+ * remaining of the encoded representation.
+ *
+ * <p> This way, there's no need to provide a buffer of a specific size, or to
+ * resize (and copy) the buffer on demand, when the remaining encoded
+ * representation will not fit in the buffer's remaining space. Instead, an
+ * array of existing buffers can be used, prepended with a frame that encloses
+ * the resulting header block afterwards.
+ *
+ * <p> Splitting the encoding operation into header set up and header encoding,
+ * separates long lived arguments ({@code name}, {@code value},
+ * {@code sensitivity}, etc.) from the short lived ones (e.g. {@code buffer}),
+ * simplifying each operation itself.
+ *
+ * @implNote
+ *
+ * <p> The default implementation does not use dynamic table. It reports to a
+ * coupled Decoder a size update with the value of {@code 0}, and never changes
+ * it afterwards.
+ *
+ * @since 9
+ */
+public class Encoder {
+
+    private static final AtomicLong ENCODERS_IDS = new AtomicLong();
+
+    // TODO: enum: no huffman/smart huffman/always huffman
+    private static final boolean DEFAULT_HUFFMAN = true;
+
+    private final Logger logger;
+    private final long id;
+    private final IndexedWriter indexedWriter = new IndexedWriter();
+    private final LiteralWriter literalWriter = new LiteralWriter();
+    private final LiteralNeverIndexedWriter literalNeverIndexedWriter
+            = new LiteralNeverIndexedWriter();
+    private final LiteralWithIndexingWriter literalWithIndexingWriter
+            = new LiteralWithIndexingWriter();
+    private final SizeUpdateWriter sizeUpdateWriter = new SizeUpdateWriter();
+    private final BulkSizeUpdateWriter bulkSizeUpdateWriter
+            = new BulkSizeUpdateWriter();
+
+    private BinaryRepresentationWriter writer;
+    private final HeaderTable headerTable;
+
+    private boolean encoding;
+
+    private int maxCapacity;
+    private int currCapacity;
+    private int lastCapacity;
+    private long minCapacity;
+    private boolean capacityUpdate;
+    private boolean configuredCapacityUpdate;
+
+    /**
+     * Constructs an {@code Encoder} with the specified maximum capacity of the
+     * header table.
+     *
+     * <p> The value has to be agreed between decoder and encoder out-of-band,
+     * e.g. by a protocol that uses HPACK
+     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
+     *
+     * @param maxCapacity
+     *         a non-negative integer
+     *
+     * @throws IllegalArgumentException
+     *         if maxCapacity is negative
+     */
+    public Encoder(int maxCapacity) {
+        id = ENCODERS_IDS.incrementAndGet();
+        this.logger = HPACK.getLogger().subLogger("Encoder#" + id);
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("new encoder with maximum table size %s",
+                                            maxCapacity));
+        }
+        if (logger.isLoggable(EXTRA)) {
+            /* To correlate with logging outside HPACK, knowing
+               hashCode/toString is important */
+            logger.log(EXTRA, () -> {
+                String hashCode = Integer.toHexString(
+                        System.identityHashCode(this));
+                /* Since Encoder can be subclassed hashCode AND identity
+                   hashCode might be different. So let's print both. */
+                return format("toString='%s', hashCode=%s, identityHashCode=%s",
+                              toString(), hashCode(), hashCode);
+            });
+        }
+        if (maxCapacity < 0) {
+            throw new IllegalArgumentException(
+                    "maxCapacity >= 0: " + maxCapacity);
+        }
+        // Initial maximum capacity update mechanics
+        minCapacity = Long.MAX_VALUE;
+        currCapacity = -1;
+        setMaxCapacity0(maxCapacity);
+        headerTable = new HeaderTable(lastCapacity, logger.subLogger("HeaderTable"));
+    }
+
+    /**
+     * Sets up the given header {@code (name, value)}.
+     *
+     * <p> Fixates {@code name} and {@code value} for the duration of encoding.
+     *
+     * @param name
+     *         the name
+     * @param value
+     *         the value
+     *
+     * @throws NullPointerException
+     *         if any of the arguments are {@code null}
+     * @throws IllegalStateException
+     *         if the encoder hasn't fully encoded the previous header, or
+     *         hasn't yet started to encode it
+     * @see #header(CharSequence, CharSequence, boolean)
+     */
+    public void header(CharSequence name, CharSequence value)
+            throws IllegalStateException {
+        header(name, value, false);
+    }
+
+    /**
+     * Sets up the given header {@code (name, value)} with possibly sensitive
+     * value.
+     *
+     * <p> If the {@code value} is sensitive (think security, secrecy, etc.)
+     * this encoder will compress it using a special representation
+     * (see <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">6.2.3.  Literal Header Field Never Indexed</a>).
+     *
+     * <p> Fixates {@code name} and {@code value} for the duration of encoding.
+     *
+     * @param name
+     *         the name
+     * @param value
+     *         the value
+     * @param sensitive
+     *         whether or not the value is sensitive
+     *
+     * @throws NullPointerException
+     *         if any of the arguments are {@code null}
+     * @throws IllegalStateException
+     *         if the encoder hasn't fully encoded the previous header, or
+     *         hasn't yet started to encode it
+     * @see #header(CharSequence, CharSequence)
+     * @see DecodingCallback#onDecoded(CharSequence, CharSequence, boolean)
+     */
+    public void header(CharSequence name,
+                       CharSequence value,
+                       boolean sensitive) throws IllegalStateException {
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("encoding ('%s', '%s'), sensitive: %s",
+                                            name, value, sensitive));
+        }
+        // Arguably a good balance between complexity of implementation and
+        // efficiency of encoding
+        requireNonNull(name, "name");
+        requireNonNull(value, "value");
+        HeaderTable t = getHeaderTable();
+        int index = t.indexOf(name, value);
+        if (index > 0) {
+            indexed(index);
+        } else if (index < 0) {
+            if (sensitive) {
+                literalNeverIndexed(-index, value, DEFAULT_HUFFMAN);
+            } else {
+                literal(-index, value, DEFAULT_HUFFMAN);
+            }
+        } else {
+            if (sensitive) {
+                literalNeverIndexed(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
+            } else {
+                literal(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
+            }
+        }
+    }
+
+    /**
+     * Sets a maximum capacity of the header table.
+     *
+     * <p> The value has to be agreed between decoder and encoder out-of-band,
+     * e.g. by a protocol that uses HPACK
+     * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
+     *
+     * <p> May be called any number of times after or before a complete header
+     * has been encoded.
+     *
+     * <p> If the encoder decides to change the actual capacity, an update will
+     * be encoded before a new encoding operation starts.
+     *
+     * @param capacity
+     *         a non-negative integer
+     *
+     * @throws IllegalArgumentException
+     *         if capacity is negative
+     * @throws IllegalStateException
+     *         if the encoder hasn't fully encoded the previous header, or
+     *         hasn't yet started to encode it
+     */
+    public void setMaxCapacity(int capacity) {
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("setting maximum table size to %s",
+                                            capacity));
+        }
+        setMaxCapacity0(capacity);
+    }
+
+    private void setMaxCapacity0(int capacity) {
+        checkEncoding();
+        if (capacity < 0) {
+            throw new IllegalArgumentException("capacity >= 0: " + capacity);
+        }
+        int calculated = calculateCapacity(capacity);
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("actual maximum table size will be %s",
+                                            calculated));
+        }
+        if (calculated < 0 || calculated > capacity) {
+            throw new IllegalArgumentException(
+                    format("0 <= calculated <= capacity: calculated=%s, capacity=%s",
+                            calculated, capacity));
+        }
+        capacityUpdate = true;
+        // maxCapacity needs to be updated unconditionally, so the encoder
+        // always has the newest one (in case it decides to update it later
+        // unsolicitedly)
+        // Suppose maxCapacity = 4096, and the encoder has decided to use only
+        // 2048. It later can choose anything else from the region [0, 4096].
+        maxCapacity = capacity;
+        lastCapacity = calculated;
+        minCapacity = Math.min(minCapacity, lastCapacity);
+    }
+
+    /**
+     * Calculates actual capacity to be used by this encoder in response to
+     * a request to update maximum table size.
+     *
+     * <p> Default implementation does not add anything to the headers table,
+     * hence this method returns {@code 0}.
+     *
+     * <p> It is an error to return a value {@code c}, where {@code c < 0} or
+     * {@code c > maxCapacity}.
+     *
+     * @param maxCapacity
+     *         upper bound
+     *
+     * @return actual capacity
+     */
+    protected int calculateCapacity(int maxCapacity) {
+        return 0;
+    }
+
+    /**
+     * Encodes the {@linkplain #header(CharSequence, CharSequence) set up}
+     * header into the given buffer.
+     *
+     * <p> The encoder writes as much as possible of the header's binary
+     * representation into the given buffer, starting at the buffer's position,
+     * and increments its position to reflect the bytes written. The buffer's
+     * mark and limit will not be modified.
+     *
+     * <p> Once the method has returned {@code true}, the current header is
+     * deemed encoded. A new header may be set up.
+     *
+     * @param headerBlock
+     *         the buffer to encode the header into, may be empty
+     *
+     * @return {@code true} if the current header has been fully encoded,
+     *         {@code false} otherwise
+     *
+     * @throws NullPointerException
+     *         if the buffer is {@code null}
+     * @throws ReadOnlyBufferException
+     *         if this buffer is read-only
+     * @throws IllegalStateException
+     *         if there is no set up header
+     */
+    public final boolean encode(ByteBuffer headerBlock) {
+        if (!encoding) {
+            throw new IllegalStateException("A header hasn't been set up");
+        }
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("writing to %s", headerBlock));
+        }
+        if (!prependWithCapacityUpdate(headerBlock)) { // TODO: log
+            return false;
+        }
+        boolean done = writer.write(headerTable, headerBlock);
+        if (done) {
+            writer.reset(); // FIXME: WHY?
+            encoding = false;
+        }
+        return done;
+    }
+
+    private boolean prependWithCapacityUpdate(ByteBuffer headerBlock) {
+        if (capacityUpdate) {
+            if (!configuredCapacityUpdate) {
+                List<Integer> sizes = new LinkedList<>();
+                if (minCapacity < currCapacity) {
+                    sizes.add((int) minCapacity);
+                    if (minCapacity != lastCapacity) {
+                        sizes.add(lastCapacity);
+                    }
+                } else if (lastCapacity != currCapacity) {
+                    sizes.add(lastCapacity);
+                }
+                bulkSizeUpdateWriter.maxHeaderTableSizes(sizes);
+                configuredCapacityUpdate = true;
+            }
+            boolean done = bulkSizeUpdateWriter.write(headerTable, headerBlock);
+            if (done) {
+                minCapacity = lastCapacity;
+                currCapacity = lastCapacity;
+                bulkSizeUpdateWriter.reset();
+                capacityUpdate = false;
+                configuredCapacityUpdate = false;
+            }
+            return done;
+        }
+        return true;
+    }
+
+    protected final void indexed(int index) throws IndexOutOfBoundsException {
+        checkEncoding();
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("indexed %s", index));
+        }
+        encoding = true;
+        writer = indexedWriter.index(index);
+    }
+
+    protected final void literal(int index,
+                                 CharSequence value,
+                                 boolean useHuffman)
+            throws IndexOutOfBoundsException {
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("literal without indexing ('%s', '%s')",
+                                           index, value));
+        }
+        checkEncoding();
+        encoding = true;
+        writer = literalWriter
+                .index(index).value(value, useHuffman);
+    }
+
+    protected final void literal(CharSequence name,
+                                 boolean nameHuffman,
+                                 CharSequence value,
+                                 boolean valueHuffman) {
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("literal without indexing ('%s', '%s')",
+                                           name, value));
+        }
+        checkEncoding();
+        encoding = true;
+        writer = literalWriter
+                .name(name, nameHuffman).value(value, valueHuffman);
+    }
+
+    protected final void literalNeverIndexed(int index,
+                                             CharSequence value,
+                                             boolean valueHuffman)
+            throws IndexOutOfBoundsException {
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("literal never indexed ('%s', '%s')",
+                                           index, value));
+        }
+        checkEncoding();
+        encoding = true;
+        writer = literalNeverIndexedWriter
+                .index(index).value(value, valueHuffman);
+    }
+
+    protected final void literalNeverIndexed(CharSequence name,
+                                             boolean nameHuffman,
+                                             CharSequence value,
+                                             boolean valueHuffman) {
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("literal never indexed ('%s', '%s')",
+                                           name, value));
+        }
+        checkEncoding();
+        encoding = true;
+        writer = literalNeverIndexedWriter
+                .name(name, nameHuffman).value(value, valueHuffman);
+    }
+
+    protected final void literalWithIndexing(int index,
+                                             CharSequence value,
+                                             boolean valueHuffman)
+            throws IndexOutOfBoundsException {
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("literal with incremental indexing ('%s', '%s')",
+                                           index, value));
+        }
+        checkEncoding();
+        encoding = true;
+        writer = literalWithIndexingWriter
+                .index(index).value(value, valueHuffman);
+    }
+
+    protected final void literalWithIndexing(CharSequence name,
+                                             boolean nameHuffman,
+                                             CharSequence value,
+                                             boolean valueHuffman) {
+        if (logger.isLoggable(EXTRA)) { // TODO: include huffman info?
+            logger.log(EXTRA, () -> format("literal with incremental indexing ('%s', '%s')",
+                                           name, value));
+        }
+        checkEncoding();
+        encoding = true;
+        writer = literalWithIndexingWriter
+                .name(name, nameHuffman).value(value, valueHuffman);
+    }
+
+    protected final void sizeUpdate(int capacity)
+            throws IllegalArgumentException {
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("dynamic table size update %s",
+                                           capacity));
+        }
+        checkEncoding();
+        // Ensure subclass follows the contract
+        if (capacity > this.maxCapacity) {
+            throw new IllegalArgumentException(
+                    format("capacity <= maxCapacity: capacity=%s, maxCapacity=%s",
+                            capacity, maxCapacity));
+        }
+        writer = sizeUpdateWriter.maxHeaderTableSize(capacity);
+    }
+
+    protected final int getMaxCapacity() {
+        return maxCapacity;
+    }
+
+    protected final HeaderTable getHeaderTable() {
+        return headerTable;
+    }
+
+    protected final void checkEncoding() { // TODO: better name e.g. checkIfEncodingInProgress()
+        if (encoding) {
+            throw new IllegalStateException(
+                    "Previous encoding operation hasn't finished yet");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/HPACK.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,174 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.hpack.HPACK.Logger.Level;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.joining;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NONE;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL;
+
+/**
+ * Internal utilities and stuff.
+ */
+public final class HPACK {
+
+    private static final RootLogger LOGGER;
+    private static final Map<String, Level> logLevels =
+            Map.of("NORMAL", NORMAL, "EXTRA", EXTRA);
+
+    static {
+        String PROPERTY = "jdk.internal.httpclient.hpack.log.level";
+
+        String value = AccessController.doPrivileged(
+                (PrivilegedAction<String>) () -> System.getProperty(PROPERTY));
+
+        if (value == null) {
+            LOGGER = new RootLogger(NONE);
+        } else {
+            String upperCasedValue = value.toUpperCase();
+            Level l = logLevels.get(upperCasedValue);
+            if (l == null) {
+                LOGGER = new RootLogger(NONE);
+                LOGGER.log(System.Logger.Level.INFO,
+                        () -> format("%s value '%s' not recognized (use %s); logging disabled",
+                                     PROPERTY, value, logLevels.keySet().stream().collect(joining(", "))));
+            } else {
+                LOGGER = new RootLogger(l);
+                LOGGER.log(System.Logger.Level.DEBUG,
+                        () -> format("logging level %s", l));
+            }
+        }
+    }
+
+    public static Logger getLogger() {
+        return LOGGER;
+    }
+
+    private HPACK() { }
+
+    /**
+     * The purpose of this logger is to provide means of diagnosing issues _in
+     * the HPACK implementation_. It's not a general purpose logger.
+     */
+    // implements System.Logger to make it possible to skip this class
+    // when looking for the Caller.
+    public static class Logger implements System.Logger {
+
+        /**
+         * Log detail level.
+         */
+        public enum Level {
+
+            NONE(0, System.Logger.Level.OFF),
+            NORMAL(1, System.Logger.Level.DEBUG),
+            EXTRA(2, System.Logger.Level.TRACE);
+
+            private final int level;
+            final System.Logger.Level systemLevel;
+
+            Level(int i, System.Logger.Level system) {
+                level = i;
+                systemLevel = system;
+            }
+
+            public final boolean implies(Level other) {
+                return this.level >= other.level;
+            }
+        }
+
+        private final String name;
+        private final Level level;
+        private final String path;
+        private final System.Logger logger;
+
+        private Logger(String path, String name, Level level) {
+            this(path, name, level, null);
+        }
+
+        private Logger(String p, String name, Level level, System.Logger logger) {
+            this.path = p;
+            this.name = name;
+            this.level = level;
+            this.logger = Utils.getHpackLogger(path::toString, level.systemLevel);
+        }
+
+        public final String getName() {
+            return name;
+        }
+
+        @Override
+        public boolean isLoggable(System.Logger.Level level) {
+            return logger.isLoggable(level);
+        }
+
+        @Override
+        public void log(System.Logger.Level level, ResourceBundle bundle, String msg, Throwable thrown) {
+            logger.log(level, bundle, msg,thrown);
+        }
+
+        @Override
+        public void log(System.Logger.Level level, ResourceBundle bundle, String format, Object... params) {
+            logger.log(level, bundle, format, params);
+        }
+
+        /*
+         * Usual performance trick for logging, reducing performance overhead in
+         * the case where logging with the specified level is a NOP.
+         */
+
+        public boolean isLoggable(Level level) {
+            return this.level.implies(level);
+        }
+
+        public void log(Level level, Supplier<String> s) {
+            if (this.level.implies(level)) {
+                logger.log(level.systemLevel, s);
+            }
+        }
+
+        public Logger subLogger(String name) {
+            return new Logger(path + "/" + name, name, level);
+        }
+
+    }
+
+    private static final class RootLogger extends Logger {
+
+        protected RootLogger(Level level) {
+            super("hpack", "hpack", level);
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/HeaderTable.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,546 @@
+/*
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.hpack;
+
+import jdk.internal.net.http.hpack.HPACK.Logger;
+import jdk.internal.vm.annotation.Stable;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import static java.lang.String.format;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA;
+import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL;
+
+//
+// Header Table combined from two tables: static and dynamic.
+//
+// There is a single address space for index values. Index-aware methods
+// correspond to the table as a whole. Size-aware methods only to the dynamic
+// part of it.
+//
+final class HeaderTable {
+
+    @Stable
+    private static final HeaderField[] staticTable = {
+            null, // To make index 1-based, instead of 0-based
+            new HeaderField(":authority"),
+            new HeaderField(":method", "GET"),
+            new HeaderField(":method", "POST"),
+            new HeaderField(":path", "/"),
+            new HeaderField(":path", "/index.html"),
+            new HeaderField(":scheme", "http"),
+            new HeaderField(":scheme", "https"),
+            new HeaderField(":status", "200"),
+            new HeaderField(":status", "204"),
+            new HeaderField(":status", "206"),
+            new HeaderField(":status", "304"),
+            new HeaderField(":status", "400"),
+            new HeaderField(":status", "404"),
+            new HeaderField(":status", "500"),
+            new HeaderField("accept-charset"),
+            new HeaderField("accept-encoding", "gzip, deflate"),
+            new HeaderField("accept-language"),
+            new HeaderField("accept-ranges"),
+            new HeaderField("accept"),
+            new HeaderField("access-control-allow-origin"),
+            new HeaderField("age"),
+            new HeaderField("allow"),
+            new HeaderField("authorization"),
+            new HeaderField("cache-control"),
+            new HeaderField("content-disposition"),
+            new HeaderField("content-encoding"),
+            new HeaderField("content-language"),
+            new HeaderField("content-length"),
+            new HeaderField("content-location"),
+            new HeaderField("content-range"),
+            new HeaderField("content-type"),
+            new HeaderField("cookie"),
+            new HeaderField("date"),
+            new HeaderField("etag"),
+            new HeaderField("expect"),
+            new HeaderField("expires"),
+            new HeaderField("from"),
+            new HeaderField("host"),
+            new HeaderField("if-match"),
+            new HeaderField("if-modified-since"),
+            new HeaderField("if-none-match"),
+            new HeaderField("if-range"),
+            new HeaderField("if-unmodified-since"),
+            new HeaderField("last-modified"),
+            new HeaderField("link"),
+            new HeaderField("location"),
+            new HeaderField("max-forwards"),
+            new HeaderField("proxy-authenticate"),
+            new HeaderField("proxy-authorization"),
+            new HeaderField("range"),
+            new HeaderField("referer"),
+            new HeaderField("refresh"),
+            new HeaderField("retry-after"),
+            new HeaderField("server"),
+            new HeaderField("set-cookie"),
+            new HeaderField("strict-transport-security"),
+            new HeaderField("transfer-encoding"),
+            new HeaderField("user-agent"),
+            new HeaderField("vary"),
+            new HeaderField("via"),
+            new HeaderField("www-authenticate")
+    };
+
+    private static final int STATIC_TABLE_LENGTH = staticTable.length - 1;
+    private static final int ENTRY_SIZE = 32;
+    private static final Map<String, LinkedHashMap<String, Integer>> staticIndexes;
+
+    static {
+        staticIndexes = new HashMap<>(STATIC_TABLE_LENGTH); // TODO: Map.of
+        for (int i = 1; i <= STATIC_TABLE_LENGTH; i++) {
+            HeaderField f = staticTable[i];
+            Map<String, Integer> values = staticIndexes
+                    .computeIfAbsent(f.name, k -> new LinkedHashMap<>());
+            values.put(f.value, i);
+        }
+    }
+
+    private final Logger logger;
+    private final Table dynamicTable = new Table(0);
+    private int maxSize;
+    private int size;
+
+    public HeaderTable(int maxSize, Logger logger) {
+        this.logger = logger;
+        setMaxSize(maxSize);
+    }
+
+    //
+    // The method returns:
+    //
+    // * a positive integer i where i (i = [1..Integer.MAX_VALUE]) is an
+    // index of an entry with a header (n, v), where n.equals(name) &&
+    // v.equals(value)
+    //
+    // * a negative integer j where j (j = [-Integer.MAX_VALUE..-1]) is an
+    // index of an entry with a header (n, v), where n.equals(name)
+    //
+    // * 0 if there's no entry e such that e.getName().equals(name)
+    //
+    // The rationale behind this design is to allow to pack more useful data
+    // into a single invocation, facilitating a single pass where possible
+    // (the idea is the same as in java.util.Arrays.binarySearch(int[], int)).
+    //
+    public int indexOf(CharSequence name, CharSequence value) {
+        // Invoking toString() will possibly allocate Strings for the sake of
+        // the search, which doesn't feel right.
+        String n = name.toString();
+        String v = value.toString();
+
+        // 1. Try exact match in the static region
+        Map<String, Integer> values = staticIndexes.get(n);
+        if (values != null) {
+            Integer idx = values.get(v);
+            if (idx != null) {
+                return idx;
+            }
+        }
+        // 2. Try exact match in the dynamic region
+        int didx = dynamicTable.indexOf(n, v);
+        if (didx > 0) {
+            return STATIC_TABLE_LENGTH + didx;
+        } else if (didx < 0) {
+            if (values != null) {
+                // 3. Return name match from the static region
+                return -values.values().iterator().next(); // Iterator allocation
+            } else {
+                // 4. Return name match from the dynamic region
+                return -STATIC_TABLE_LENGTH + didx;
+            }
+        } else {
+            if (values != null) {
+                // 3. Return name match from the static region
+                return -values.values().iterator().next(); // Iterator allocation
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public int maxSize() {
+        return maxSize;
+    }
+
+    public int length() {
+        return STATIC_TABLE_LENGTH + dynamicTable.size();
+    }
+
+    HeaderField get(int index) {
+        checkIndex(index);
+        if (index <= STATIC_TABLE_LENGTH) {
+            return staticTable[index];
+        } else {
+            return dynamicTable.get(index - STATIC_TABLE_LENGTH);
+        }
+    }
+
+    void put(CharSequence name, CharSequence value) {
+        // Invoking toString() will possibly allocate Strings. But that's
+        // unavoidable at this stage. If a CharSequence is going to be stored in
+        // the table, it must not be mutable (e.g. for the sake of hashing).
+        put(new HeaderField(name.toString(), value.toString()));
+    }
+
+    private void put(HeaderField h) {
+        if (logger.isLoggable(NORMAL)) {
+            logger.log(NORMAL, () -> format("adding ('%s', '%s')",
+                                            h.name, h.value));
+        }
+        int entrySize = sizeOf(h);
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("size of ('%s', '%s') is %s",
+                                           h.name, h.value, entrySize));
+        }
+        while (entrySize > maxSize - size && size != 0) {
+            if (logger.isLoggable(EXTRA)) {
+                logger.log(EXTRA, () -> format("insufficient space %s, must evict entry",
+                                               (maxSize - size)));
+            }
+            evictEntry();
+        }
+        if (entrySize > maxSize - size) {
+            if (logger.isLoggable(EXTRA)) {
+                logger.log(EXTRA, () -> format("not adding ('%s, '%s'), too big",
+                                               h.name, h.value));
+            }
+            return;
+        }
+        size += entrySize;
+        dynamicTable.add(h);
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("('%s, '%s') added", h.name, h.value));
+            logger.log(EXTRA, this::toString);
+        }
+    }
+
+    void setMaxSize(int maxSize) {
+        if (maxSize < 0) {
+            throw new IllegalArgumentException(
+                    "maxSize >= 0: maxSize=" + maxSize);
+        }
+        while (maxSize < size && size != 0) {
+            evictEntry();
+        }
+        this.maxSize = maxSize;
+        int upperBound = (maxSize / ENTRY_SIZE) + 1;
+        this.dynamicTable.setCapacity(upperBound);
+    }
+
+    HeaderField evictEntry() {
+        HeaderField f = dynamicTable.remove();
+        int s = sizeOf(f);
+        this.size -= s;
+        if (logger.isLoggable(EXTRA)) {
+            logger.log(EXTRA, () -> format("evicted entry ('%s', '%s') of size %s",
+                                           f.name, f.value, s));
+            logger.log(EXTRA, this::toString);
+        }
+        return f;
+    }
+
+    @Override
+    public String toString() {
+        double used = maxSize == 0 ? 0 : 100 * (((double) size) / maxSize);
+        return format("dynamic length: %d, full length: %s, used space: %s/%s (%.1f%%)",
+                      dynamicTable.size(), length(), size, maxSize, used);
+    }
+
+    private int checkIndex(int index) {
+        int len = length();
+        if (index < 1 || index > len) {
+            throw new IndexOutOfBoundsException(
+                    format("1 <= index <= length(): index=%s, length()=%s",
+                           index, len));
+        }
+        return index;
+    }
+
+    int sizeOf(HeaderField f) {
+        return f.name.length() + f.value.length() + ENTRY_SIZE;
+    }
+
+    //
+    // Diagnostic information in the form used in the RFC 7541
+    //
+    String getStateString() {
+        if (size == 0) {
+            return "empty.";
+        }
+
+        StringBuilder b = new StringBuilder();
+        for (int i = 1, size = dynamicTable.size(); i <= size; i++) {
+            HeaderField e = dynamicTable.get(i);
+            b.append(format("[%3d] (s = %3d) %s: %s\n", i,
+                    sizeOf(e), e.name, e.value));
+        }
+        b.append(format("      Table size:%4s", this.size));
+        return b.toString();
+    }
+
+    // Convert to a Value Object (JDK-8046159)?
+    static final class HeaderField {
+
+        final String name;
+        final String value;
+
+        public HeaderField(String name) {
+            this(name, "");
+        }
+
+        public HeaderField(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return value.isEmpty() ? name : name + ": " + value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            HeaderField that = (HeaderField) o;
+            return name.equals(that.name) && value.equals(that.value);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * name.hashCode() + value.hashCode();
+        }
+    }
+
+    //
+    // To quickly find an index of an entry in the dynamic table with the given
+    // contents an effective inverse mapping is needed. Here's a simple idea
+    // behind such a mapping.
+    //
+    // # The problem:
+    //
+    // We have a queue with an O(1) lookup by index:
+    //
+    //     get: index -> x
+    //
+    // What we want is an O(1) reverse lookup:
+    //
+    //     indexOf: x -> index
+    //
+    // # Solution:
+    //
+    // Let's store an inverse mapping in a Map<x, Integer>. This have a problem
+    // that when a new element is added to the queue, all indexes in the map
+    // become invalid. Namely, the new element is assigned with an index of 1,
+    // and each index i, i > 1 becomes shifted by 1 to the left:
+    //
+    //     1, 1, 2, 3, ... , n-1, n
+    //
+    // Re-establishing the invariant would seem to require a pass through the
+    // map incrementing all indexes (map values) by 1, which is O(n).
+    //
+    // The good news is we can do much better then this!
+    //
+    // Let's create a single field of type long, called 'counter'. Then each
+    // time a new element 'x' is added to the queue, a value of this field gets
+    // incremented. Then the resulting value of the 'counter_x' is then put as a
+    // value under key 'x' to the map:
+    //
+    //    map.put(x, counter_x)
+    //
+    // It gives us a map that maps an element to a value the counter had at the
+    // time the element had been added.
+    //
+    // In order to retrieve an index of any element 'x' in the queue (at any
+    // given time) we simply need to subtract the value (the snapshot of the
+    // counter at the time when the 'x' was added) from the current value of the
+    // counter. This operation basically answers the question:
+    //
+    //     How many elements ago 'x' was the tail of the queue?
+    //
+    // Which is the same as its index in the queue now. Given, of course, it's
+    // still in the queue.
+    //
+    // I'm pretty sure in a real life long overflow will never happen, so it's
+    // not too practical to add recalibrating code, but a pedantic person might
+    // want to do so:
+    //
+    //     if (counter == Long.MAX_VALUE) {
+    //         recalibrate();
+    //     }
+    //
+    // Where 'recalibrate()' goes through the table doing this:
+    //
+    //     value -= counter
+    //
+    // That's given, of course, the size of the table itself is less than
+    // Long.MAX_VALUE :-)
+    //
+    private static final class Table {
+
+        private final Map<String, Map<String, Long>> map;
+        private final CircularBuffer<HeaderField> buffer;
+        private long counter = 1;
+
+        Table(int capacity) {
+            buffer = new CircularBuffer<>(capacity);
+            map = new HashMap<>(capacity);
+        }
+
+        void add(HeaderField f) {
+            buffer.add(f);
+            Map<String, Long> values = map.computeIfAbsent(f.name, k -> new HashMap<>());
+            values.put(f.value, counter++);
+        }
+
+        HeaderField get(int index) {
+            return buffer.get(index - 1);
+        }
+
+        int indexOf(String name, String value) {
+            Map<String, Long> values = map.get(name);
+            if (values == null) {
+                return 0;
+            }
+            Long index = values.get(value);
+            if (index != null) {
+                return (int) (counter - index);
+            } else {
+                assert !values.isEmpty();
+                Long any = values.values().iterator().next(); // Iterator allocation
+                return -(int) (counter - any);
+            }
+        }
+
+        HeaderField remove() {
+            HeaderField f = buffer.remove();
+            Map<String, Long> values = map.get(f.name);
+            Long index = values.remove(f.value);
+            assert index != null;
+            if (values.isEmpty()) {
+                map.remove(f.name);
+            }
+            return f;
+        }
+
+        int size() {
+            return buffer.size;
+        }
+
+        public void setCapacity(int capacity) {
+            buffer.resize(capacity);
+        }
+    }
+
+    //                    head
+    //                    v
+    // [ ][ ][A][B][C][D][ ][ ][ ]
+    //        ^
+    //        tail
+    //
+    //       |<- size ->| (4)
+    // |<------ capacity ------->| (9)
+    //
+    static final class CircularBuffer<E> {
+
+        int tail, head, size, capacity;
+        Object[] elements;
+
+        CircularBuffer(int capacity) {
+            this.capacity = capacity;
+            elements = new Object[capacity];
+        }
+
+        void add(E elem) {
+            if (size == capacity) {
+                throw new IllegalStateException(
+                        format("No room for '%s': capacity=%s", elem, capacity));
+            }
+            elements[head] = elem;
+            head = (head + 1) % capacity;
+            size++;
+        }
+
+        @SuppressWarnings("unchecked")
+        E remove() {
+            if (size == 0) {
+                throw new NoSuchElementException("Empty");
+            }
+            E elem = (E) elements[tail];
+            elements[tail] = null;
+            tail = (tail + 1) % capacity;
+            size--;
+            return elem;
+        }
+
+        @SuppressWarnings("unchecked")
+        E get(int index) {
+            if (index < 0 || index >= size) {
+                throw new IndexOutOfBoundsException(
+                        format("0 <= index <= capacity: index=%s, capacity=%s",
+                                index, capacity));
+            }
+            int idx = (tail + (size - index - 1)) % capacity;
+            return (E) elements[idx];
+        }
+
+        public void resize(int newCapacity) {
+            if (newCapacity < size) {
+                throw new IllegalStateException(
+                        format("newCapacity >= size: newCapacity=%s, size=%s",
+                                newCapacity, size));
+            }
+
+            Object[] newElements = new Object[newCapacity];
+
+            if (tail < head || size == 0) {
+                System.arraycopy(elements, tail, newElements, 0, size);
+            } else {
+                System.arraycopy(elements, tail, newElements, 0, elements.length - tail);
+                System.arraycopy(elements, 0, newElements, elements.length - tail, head);
+            }
+
+            elements = newElements;
+            tail = 0;
+            head = size;
+            this.capacity = newCapacity;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Huffman.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.hpack;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import static java.lang.String.format;
+
+/**
+ * Huffman coding table.
+ *
+ * <p> Instances of this class are safe for use by multiple threads.
+ *
+ * @since 9
+ */
+public final class Huffman {
+
+    // TODO: check if reset is done in both reader and writer
+
+    static final class Reader {
+
+        private Node curr; // position in the trie
+        private int len;   // length of the path from the root to 'curr'
+        private int p;     // byte probe
+
+        {
+            reset();
+        }
+
+        public void read(ByteBuffer source,
+                         Appendable destination,
+                         boolean isLast) throws IOException {
+            read(source, destination, true, isLast);
+        }
+
+        // Takes 'isLast' rather than returns whether the reading is done or
+        // not, for more informative exceptions.
+        void read(ByteBuffer source,
+                  Appendable destination,
+                  boolean reportEOS, /* reportEOS is exposed for tests */
+                  boolean isLast) throws IOException {
+            Node c = curr;
+            int l = len;
+            /*
+               Since ByteBuffer is itself stateful, its position is
+               remembered here NOT as a part of Reader's state,
+               but to set it back in the case of a failure
+             */
+            int pos = source.position();
+
+            while (source.hasRemaining()) {
+                int d = source.get();
+                for (; p != 0; p >>= 1) {
+                    c = c.getChild(p & d);
+                    l++;
+                    if (c.isLeaf()) {
+                        if (reportEOS && c.isEOSPath) {
+                            throw new IOException("Encountered EOS");
+                        }
+                        char ch;
+                        try {
+                            ch = c.getChar();
+                        } catch (IllegalStateException e) {
+                            source.position(pos); // do we need this?
+                            throw new IOException(e);
+                        }
+                        try {
+                            destination.append(ch);
+                        } catch (IOException e) {
+                            source.position(pos); // do we need this?
+                            throw e;
+                        }
+                        c = INSTANCE.root;
+                        l = 0;
+                    }
+                    curr = c;
+                    len = l;
+                }
+                resetProbe();
+                pos++;
+            }
+            if (!isLast) {
+                return; // it's too early to jump to any conclusions, let's wait
+            }
+            if (c.isLeaf()) {
+                return; // it's perfectly ok, no extra padding bits
+            }
+            if (c.isEOSPath && len <= 7) {
+                return; // it's ok, some extra padding bits
+            }
+            if (c.isEOSPath) {
+                throw new IOException(
+                        "Padding is too long (len=" + len + ") " +
+                                "or unexpected end of data");
+            }
+            throw new IOException(
+                    "Not a EOS prefix padding or unexpected end of data");
+        }
+
+        public void reset() {
+            curr = INSTANCE.root;
+            len = 0;
+            resetProbe();
+        }
+
+        private void resetProbe() {
+            p = 0x80;
+        }
+    }
+
+    static final class Writer {
+
+        private int pos;       // position in 'source'
+        private int avail = 8; // number of least significant bits available in 'curr'
+        private int curr;      // next byte to put to the destination
+        private int rem;       // number of least significant bits in 'code' yet to be processed
+        private int code;      // current code being written
+
+        private CharSequence source;
+        private int end;
+
+        public Writer from(CharSequence input, int start, int end) {
+            if (start < 0 || end < 0 || end > input.length() || start > end) {
+                throw new IndexOutOfBoundsException(
+                        String.format("input.length()=%s, start=%s, end=%s",
+                                input.length(), start, end));
+            }
+            pos = start;
+            this.end = end;
+            this.source = input;
+            return this;
+        }
+
+        public boolean write(ByteBuffer destination) {
+            for (; pos < end; pos++) {
+                if (rem == 0) {
+                    Code desc = INSTANCE.codeOf(source.charAt(pos));
+                    rem = desc.length;
+                    code = desc.code;
+                }
+                while (rem > 0) {
+                    if (rem < avail) {
+                        curr |= (code << (avail - rem));
+                        avail -= rem;
+                        rem = 0;
+                    } else {
+                        int c = (curr | (code >>> (rem - avail)));
+                        if (destination.hasRemaining()) {
+                            destination.put((byte) c);
+                        } else {
+                            return false;
+                        }
+                        curr = c;
+                        code <<= (32 - rem + avail);  // throw written bits off the cliff (is this Sparta?)
+                        code >>>= (32 - rem + avail); // return to the position
+                        rem -= avail;
+                        curr = 0;
+                        avail = 8;
+                    }
+                }
+            }
+
+            if (avail < 8) { // have to pad
+                if (destination.hasRemaining()) {
+                    destination.put((byte) (curr | (INSTANCE.EOS.code >>> (INSTANCE.EOS.length - avail))));
+                    avail = 8;
+                } else {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        public Writer reset() {
+            source = null;
+            end = -1;
+            pos = -1;
+            avail = 8;
+            curr = 0;
+            code = 0;
+            return this;
+        }
+    }
+
+    /**
+     * Shared instance.
+     */
+    public static final Huffman INSTANCE = new Huffman();
+
+    private final Code EOS = new Code(0x3fffffff, 30);
+    private final Code[] codes = new Code[257];
+    private final Node root = new Node() {
+        @Override
+        public String toString() { return "root"; }
+    };
+
+    // TODO: consider builder and immutable trie
+    private Huffman() {
+        // @formatter:off
+        addChar(0,   0x1ff8,     13);
+        addChar(1,   0x7fffd8,   23);
+        addChar(2,   0xfffffe2,  28);
+        addChar(3,   0xfffffe3,  28);
+        addChar(4,   0xfffffe4,  28);
+        addChar(5,   0xfffffe5,  28);
+        addChar(6,   0xfffffe6,  28);
+        addChar(7,   0xfffffe7,  28);
+        addChar(8,   0xfffffe8,  28);
+        addChar(9,   0xffffea,   24);
+        addChar(10,  0x3ffffffc, 30);
+        addChar(11,  0xfffffe9,  28);
+        addChar(12,  0xfffffea,  28);
+        addChar(13,  0x3ffffffd, 30);
+        addChar(14,  0xfffffeb,  28);
+        addChar(15,  0xfffffec,  28);
+        addChar(16,  0xfffffed,  28);
+        addChar(17,  0xfffffee,  28);
+        addChar(18,  0xfffffef,  28);
+        addChar(19,  0xffffff0,  28);
+        addChar(20,  0xffffff1,  28);
+        addChar(21,  0xffffff2,  28);
+        addChar(22,  0x3ffffffe, 30);
+        addChar(23,  0xffffff3,  28);
+        addChar(24,  0xffffff4,  28);
+        addChar(25,  0xffffff5,  28);
+        addChar(26,  0xffffff6,  28);
+        addChar(27,  0xffffff7,  28);
+        addChar(28,  0xffffff8,  28);
+        addChar(29,  0xffffff9,  28);
+        addChar(30,  0xffffffa,  28);
+        addChar(31,  0xffffffb,  28);
+        addChar(32,  0x14,        6);
+        addChar(33,  0x3f8,      10);
+        addChar(34,  0x3f9,      10);
+        addChar(35,  0xffa,      12);
+        addChar(36,  0x1ff9,     13);
+        addChar(37,  0x15,        6);
+        addChar(38,  0xf8,        8);
+        addChar(39,  0x7fa,      11);
+        addChar(40,  0x3fa,      10);
+        addChar(41,  0x3fb,      10);
+        addChar(42,  0xf9,        8);
+        addChar(43,  0x7fb,      11);
+        addChar(44,  0xfa,        8);
+        addChar(45,  0x16,        6);
+        addChar(46,  0x17,        6);
+        addChar(47,  0x18,        6);
+        addChar(48,  0x0,         5);
+        addChar(49,  0x1,         5);
+        addChar(50,  0x2,         5);
+        addChar(51,  0x19,        6);
+        addChar(52,  0x1a,        6);
+        addChar(53,  0x1b,        6);
+        addChar(54,  0x1c,        6);
+        addChar(55,  0x1d,        6);
+        addChar(56,  0x1e,        6);
+        addChar(57,  0x1f,        6);
+        addChar(58,  0x5c,        7);
+        addChar(59,  0xfb,        8);
+        addChar(60,  0x7ffc,     15);
+        addChar(61,  0x20,        6);
+        addChar(62,  0xffb,      12);
+        addChar(63,  0x3fc,      10);
+        addChar(64,  0x1ffa,     13);
+        addChar(65,  0x21,        6);
+        addChar(66,  0x5d,        7);
+        addChar(67,  0x5e,        7);
+        addChar(68,  0x5f,        7);
+        addChar(69,  0x60,        7);
+        addChar(70,  0x61,        7);
+        addChar(71,  0x62,        7);
+        addChar(72,  0x63,        7);
+        addChar(73,  0x64,        7);
+        addChar(74,  0x65,        7);
+        addChar(75,  0x66,        7);
+        addChar(76,  0x67,        7);
+        addChar(77,  0x68,        7);
+        addChar(78,  0x69,        7);
+        addChar(79,  0x6a,        7);
+        addChar(80,  0x6b,        7);
+        addChar(81,  0x6c,        7);
+        addChar(82,  0x6d,        7);
+        addChar(83,  0x6e,        7);
+        addChar(84,  0x6f,        7);
+        addChar(85,  0x70,        7);
+        addChar(86,  0x71,        7);
+        addChar(87,  0x72,        7);
+        addChar(88,  0xfc,        8);
+        addChar(89,  0x73,        7);
+        addChar(90,  0xfd,        8);
+        addChar(91,  0x1ffb,     13);
+        addChar(92,  0x7fff0,    19);
+        addChar(93,  0x1ffc,     13);
+        addChar(94,  0x3ffc,     14);
+        addChar(95,  0x22,        6);
+        addChar(96,  0x7ffd,     15);
+        addChar(97,  0x3,         5);
+        addChar(98,  0x23,        6);
+        addChar(99,  0x4,         5);
+        addChar(100, 0x24,        6);
+        addChar(101, 0x5,         5);
+        addChar(102, 0x25,        6);
+        addChar(103, 0x26,        6);
+        addChar(104, 0x27,        6);
+        addChar(105, 0x6,         5);
+        addChar(106, 0x74,        7);
+        addChar(107, 0x75,        7);
+        addChar(108, 0x28,        6);
+        addChar(109, 0x29,        6);
+        addChar(110, 0x2a,        6);
+        addChar(111, 0x7,         5);
+        addChar(112, 0x2b,        6);
+        addChar(113, 0x76,        7);
+        addChar(114, 0x2c,        6);
+        addChar(115, 0x8,         5);
+        addChar(116, 0x9,         5);
+        addChar(117, 0x2d,        6);
+        addChar(118, 0x77,        7);
+        addChar(119, 0x78,        7);
+        addChar(120, 0x79,        7);
+        addChar(121, 0x7a,        7);
+        addChar(122, 0x7b,        7);
+        addChar(123, 0x7ffe,     15);
+        addChar(124, 0x7fc,      11);
+        addChar(125, 0x3ffd,     14);
+        addChar(126, 0x1ffd,     13);
+        addChar(127, 0xffffffc,  28);
+        addChar(128, 0xfffe6,    20);
+        addChar(129, 0x3fffd2,   22);
+        addChar(130, 0xfffe7,    20);
+        addChar(131, 0xfffe8,    20);
+        addChar(132, 0x3fffd3,   22);
+        addChar(133, 0x3fffd4,   22);
+        addChar(134, 0x3fffd5,   22);
+        addChar(135, 0x7fffd9,   23);
+        addChar(136, 0x3fffd6,   22);
+        addChar(137, 0x7fffda,   23);
+        addChar(138, 0x7fffdb,   23);
+        addChar(139, 0x7fffdc,   23);
+        addChar(140, 0x7fffdd,   23);
+        addChar(141, 0x7fffde,   23);
+        addChar(142, 0xffffeb,   24);
+        addChar(143, 0x7fffdf,   23);
+        addChar(144, 0xffffec,   24);
+        addChar(145, 0xffffed,   24);
+        addChar(146, 0x3fffd7,   22);
+        addChar(147, 0x7fffe0,   23);
+        addChar(148, 0xffffee,   24);
+        addChar(149, 0x7fffe1,   23);
+        addChar(150, 0x7fffe2,   23);
+        addChar(151, 0x7fffe3,   23);
+        addChar(152, 0x7fffe4,   23);
+        addChar(153, 0x1fffdc,   21);
+        addChar(154, 0x3fffd8,   22);
+        addChar(155, 0x7fffe5,   23);
+        addChar(156, 0x3fffd9,   22);
+        addChar(157, 0x7fffe6,   23);
+        addChar(158, 0x7fffe7,   23);
+        addChar(159, 0xffffef,   24);
+        addChar(160, 0x3fffda,   22);
+        addChar(161, 0x1fffdd,   21);
+        addChar(162, 0xfffe9,    20);
+        addChar(163, 0x3fffdb,   22);
+        addChar(164, 0x3fffdc,   22);
+        addChar(165, 0x7fffe8,   23);
+        addChar(166, 0x7fffe9,   23);
+        addChar(167, 0x1fffde,   21);
+        addChar(168, 0x7fffea,   23);
+        addChar(169, 0x3fffdd,   22);
+        addChar(170, 0x3fffde,   22);
+        addChar(171, 0xfffff0,   24);
+        addChar(172, 0x1fffdf,   21);
+        addChar(173, 0x3fffdf,   22);
+        addChar(174, 0x7fffeb,   23);
+        addChar(175, 0x7fffec,   23);
+        addChar(176, 0x1fffe0,   21);
+        addChar(177, 0x1fffe1,   21);
+        addChar(178, 0x3fffe0,   22);
+        addChar(179, 0x1fffe2,   21);
+        addChar(180, 0x7fffed,   23);
+        addChar(181, 0x3fffe1,   22);
+        addChar(182, 0x7fffee,   23);
+        addChar(183, 0x7fffef,   23);
+        addChar(184, 0xfffea,    20);
+        addChar(185, 0x3fffe2,   22);
+        addChar(186, 0x3fffe3,   22);
+        addChar(187, 0x3fffe4,   22);
+        addChar(188, 0x7ffff0,   23);
+        addChar(189, 0x3fffe5,   22);
+        addChar(190, 0x3fffe6,   22);
+        addChar(191, 0x7ffff1,   23);
+        addChar(192, 0x3ffffe0,  26);
+        addChar(193, 0x3ffffe1,  26);
+        addChar(194, 0xfffeb,    20);
+        addChar(195, 0x7fff1,    19);
+        addChar(196, 0x3fffe7,   22);
+        addChar(197, 0x7ffff2,   23);
+        addChar(198, 0x3fffe8,   22);
+        addChar(199, 0x1ffffec,  25);
+        addChar(200, 0x3ffffe2,  26);
+        addChar(201, 0x3ffffe3,  26);
+        addChar(202, 0x3ffffe4,  26);
+        addChar(203, 0x7ffffde,  27);
+        addChar(204, 0x7ffffdf,  27);
+        addChar(205, 0x3ffffe5,  26);
+        addChar(206, 0xfffff1,   24);
+        addChar(207, 0x1ffffed,  25);
+        addChar(208, 0x7fff2,    19);
+        addChar(209, 0x1fffe3,   21);
+        addChar(210, 0x3ffffe6,  26);
+        addChar(211, 0x7ffffe0,  27);
+        addChar(212, 0x7ffffe1,  27);
+        addChar(213, 0x3ffffe7,  26);
+        addChar(214, 0x7ffffe2,  27);
+        addChar(215, 0xfffff2,   24);
+        addChar(216, 0x1fffe4,   21);
+        addChar(217, 0x1fffe5,   21);
+        addChar(218, 0x3ffffe8,  26);
+        addChar(219, 0x3ffffe9,  26);
+        addChar(220, 0xffffffd,  28);
+        addChar(221, 0x7ffffe3,  27);
+        addChar(222, 0x7ffffe4,  27);
+        addChar(223, 0x7ffffe5,  27);
+        addChar(224, 0xfffec,    20);
+        addChar(225, 0xfffff3,   24);
+        addChar(226, 0xfffed,    20);
+        addChar(227, 0x1fffe6,   21);
+        addChar(228, 0x3fffe9,   22);
+        addChar(229, 0x1fffe7,   21);
+        addChar(230, 0x1fffe8,   21);
+        addChar(231, 0x7ffff3,   23);
+        addChar(232, 0x3fffea,   22);
+        addChar(233, 0x3fffeb,   22);
+        addChar(234, 0x1ffffee,  25);
+        addChar(235, 0x1ffffef,  25);
+        addChar(236, 0xfffff4,   24);
+        addChar(237, 0xfffff5,   24);
+        addChar(238, 0x3ffffea,  26);
+        addChar(239, 0x7ffff4,   23);
+        addChar(240, 0x3ffffeb,  26);
+        addChar(241, 0x7ffffe6,  27);
+        addChar(242, 0x3ffffec,  26);
+        addChar(243, 0x3ffffed,  26);
+        addChar(244, 0x7ffffe7,  27);
+        addChar(245, 0x7ffffe8,  27);
+        addChar(246, 0x7ffffe9,  27);
+        addChar(247, 0x7ffffea,  27);
+        addChar(248, 0x7ffffeb,  27);
+        addChar(249, 0xffffffe,  28);
+        addChar(250, 0x7ffffec,  27);
+        addChar(251, 0x7ffffed,  27);
+        addChar(252, 0x7ffffee,  27);
+        addChar(253, 0x7ffffef,  27);
+        addChar(254, 0x7fffff0,  27);
+        addChar(255, 0x3ffffee,  26);
+        addEOS (256, EOS.code,   EOS.length);
+        // @formatter:on
+    }
+
+
+    /**
+     * Calculates the number of bytes required to represent the given {@code
+     * CharSequence} with the Huffman coding.
+     *
+     * @param value
+     *         characters
+     *
+     * @return number of bytes
+     *
+     * @throws NullPointerException
+     *         if the value is null
+     */
+    public int lengthOf(CharSequence value) {
+        return lengthOf(value, 0, value.length());
+    }
+
+    /**
+     * Calculates the number of bytes required to represent a subsequence of the
+     * given {@code CharSequence} with the Huffman coding.
+     *
+     * @param value
+     *         characters
+     * @param start
+     *         the start index, inclusive
+     * @param end
+     *         the end index, exclusive
+     *
+     * @return number of bytes
+     *
+     * @throws NullPointerException
+     *         if the value is null
+     * @throws IndexOutOfBoundsException
+     *         if any invocation of {@code value.charAt(i)}, where
+     *         {@code start <= i < end} would throw an IndexOutOfBoundsException
+     */
+    public int lengthOf(CharSequence value, int start, int end) {
+        int len = 0;
+        for (int i = start; i < end; i++) {
+            char c = value.charAt(i);
+            len += INSTANCE.codeOf(c).length;
+        }
+        // Integer division with ceiling, assumption:
+        assert (len / 8 + (len % 8 != 0 ? 1 : 0)) == (len + 7) / 8 : len;
+        return (len + 7) / 8;
+    }
+
+    private void addChar(int c, int code, int bitLength) {
+        addLeaf(c, code, bitLength, false);
+        codes[c] = new Code(code, bitLength);
+    }
+
+    private void addEOS(int c, int code, int bitLength) {
+        addLeaf(c, code, bitLength, true);
+        codes[c] = new Code(code, bitLength);
+    }
+
+    private void addLeaf(int c, int code, int bitLength, boolean isEOS) {
+        if (bitLength < 1) {
+            throw new IllegalArgumentException("bitLength < 1");
+        }
+        Node curr = root;
+        for (int p = 1 << bitLength - 1; p != 0 && !curr.isLeaf(); p = p >> 1) {
+            curr.isEOSPath |= isEOS; // If it's already true, it can't become false
+            curr = curr.addChildIfAbsent(p & code);
+        }
+        curr.isEOSPath |= isEOS; // The last one needs to have this property as well
+        if (curr.isLeaf()) {
+            throw new IllegalStateException("Specified code is already taken");
+        }
+        curr.setChar((char) c);
+    }
+
+    private Code codeOf(char c) {
+        if (c > 255) {
+            throw new IllegalArgumentException("char=" + ((int) c));
+        }
+        return codes[c];
+    }
+
+    //
+    // For debugging/testing purposes
+    //
+    Node getRoot() {
+        return root;
+    }
+
+    //
+    // Guarantees:
+    //
+    //  if (isLeaf() == true) => getChar() is a legal call
+    //  if (isLeaf() == false) => getChild(i) is a legal call (though it can
+    //                                                           return null)
+    //
+    static class Node {
+
+        Node left;
+        Node right;
+        boolean isEOSPath;
+
+        boolean charIsSet;
+        char c;
+
+        Node getChild(int selector) {
+            if (isLeaf()) {
+                throw new IllegalStateException("This is a leaf node");
+            }
+            Node result = selector == 0 ? left : right;
+            if (result == null) {
+                throw new IllegalStateException(format(
+                        "Node doesn't have a child (selector=%s)", selector));
+            }
+            return result;
+        }
+
+        boolean isLeaf() {
+            return charIsSet;
+        }
+
+        char getChar() {
+            if (!isLeaf()) {
+                throw new IllegalStateException("This node is not a leaf node");
+            }
+            return c;
+        }
+
+        void setChar(char c) {
+            if (charIsSet) {
+                throw new IllegalStateException(
+                        "This node has been taken already");
+            }
+            if (left != null || right != null) {
+                throw new IllegalStateException("The node cannot be made "
+                        + "a leaf as it's already has a child");
+            }
+            this.c = c;
+            charIsSet = true;
+        }
+
+        Node addChildIfAbsent(int i) {
+            if (charIsSet) {
+                throw new IllegalStateException("The node cannot have a child "
+                        + "as it's already a leaf node");
+            }
+            Node child;
+            if (i == 0) {
+                if ((child = left) == null) {
+                    child = left = new Node();
+                }
+            } else {
+                if ((child = right) == null) {
+                    child = right = new Node();
+                }
+            }
+            return child;
+        }
+
+        @Override
+        public String toString() {
+            if (isLeaf()) {
+                if (isEOSPath) {
+                    return "EOS";
+                } else {
+                    return format("char: (%3s) '%s'", (int) c, c);
+                }
+            }
+            return "/\\";
+        }
+    }
+
+    // TODO: value-based class?
+    // FIXME: can we re-use Node instead of this class?
+    private static final class Code {
+
+        final int code;
+        final int length;
+
+        private Code(int code, int length) {
+            this.code = code;
+            this.length = length;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public int getLength() {
+            return length;
+        }
+
+        @Override
+        public String toString() {
+            long p = 1 << length;
+            return Long.toBinaryString(code + p).substring(1)
+                    + ", length=" + length;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/ISO_8859_1.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,103 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+//
+// Custom implementation of ISO/IEC 8859-1:1998
+//
+// The rationale behind this is not to deal with CharsetEncoder/CharsetDecoder,
+// basically because it would require wrapping every single CharSequence into a
+// CharBuffer and then copying it back.
+//
+// But why not to give a CharBuffer instead of Appendable? Because I can choose
+// an Appendable (e.g. StringBuilder) that adjusts its length when needed and
+// therefore not to deal with pre-sized CharBuffers or copying.
+//
+// The encoding is simple and well known: 1 byte <-> 1 char
+//
+final class ISO_8859_1 {
+
+    private ISO_8859_1() { }
+
+    public static final class Reader {
+
+        public void read(ByteBuffer source, Appendable destination)
+                throws IOException {
+            for (int i = 0, len = source.remaining(); i < len; i++) {
+                char c = (char) (source.get() & 0xff);
+                try {
+                    destination.append(c);
+                } catch (IOException e) {
+                    throw new IOException(
+                            "Error appending to the destination", e);
+                }
+            }
+        }
+
+        public Reader reset() {
+            return this;
+        }
+    }
+
+    public static final class Writer {
+
+        private CharSequence source;
+        private int pos;
+        private int end;
+
+        public Writer configure(CharSequence source, int start, int end) {
+            this.source = source;
+            this.pos = start;
+            this.end = end;
+            return this;
+        }
+
+        public boolean write(ByteBuffer destination) {
+            for (; pos < end; pos++) {
+                char c = source.charAt(pos);
+                if (c > '\u00FF') {
+                    throw new IllegalArgumentException(
+                            "Illegal ISO-8859-1 char: " + (int) c);
+                }
+                if (destination.hasRemaining()) {
+                    destination.put((byte) c);
+                } else {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public Writer reset() {
+            source = null;
+            pos = -1;
+            end = -1;
+            return this;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/IndexNameValueWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,102 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+
+abstract class IndexNameValueWriter implements BinaryRepresentationWriter {
+
+    private final int pattern;
+    private final int prefix;
+    private final IntegerWriter intWriter = new IntegerWriter();
+    private final StringWriter nameWriter = new StringWriter();
+    private final StringWriter valueWriter = new StringWriter();
+
+    protected boolean indexedRepresentation;
+
+    private static final int NEW               = 0;
+    private static final int NAME_PART_WRITTEN = 1;
+    private static final int VALUE_WRITTEN     = 2;
+
+    private int state = NEW;
+
+    protected IndexNameValueWriter(int pattern, int prefix) {
+        this.pattern = pattern;
+        this.prefix = prefix;
+    }
+
+    IndexNameValueWriter index(int index) {
+        indexedRepresentation = true;
+        intWriter.configure(index, prefix, pattern);
+        return this;
+    }
+
+    IndexNameValueWriter name(CharSequence name, boolean useHuffman) {
+        indexedRepresentation = false;
+        intWriter.configure(0, prefix, pattern);
+        nameWriter.configure(name, useHuffman);
+        return this;
+    }
+
+    IndexNameValueWriter value(CharSequence value, boolean useHuffman) {
+        valueWriter.configure(value, useHuffman);
+        return this;
+    }
+
+    @Override
+    public boolean write(HeaderTable table, ByteBuffer destination) {
+        if (state < NAME_PART_WRITTEN) {
+            if (indexedRepresentation) {
+                if (!intWriter.write(destination)) {
+                    return false;
+                }
+            } else {
+                if (!intWriter.write(destination) ||
+                        !nameWriter.write(destination)) {
+                    return false;
+                }
+            }
+            state = NAME_PART_WRITTEN;
+        }
+        if (state < VALUE_WRITTEN) {
+            if (!valueWriter.write(destination)) {
+                return false;
+            }
+            state = VALUE_WRITTEN;
+        }
+        return state == VALUE_WRITTEN;
+    }
+
+    @Override
+    public IndexNameValueWriter reset() {
+        intWriter.reset();
+        if (!indexedRepresentation) {
+            nameWriter.reset();
+        }
+        valueWriter.reset();
+        state = NEW;
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/IndexedWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,50 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+
+final class IndexedWriter implements BinaryRepresentationWriter {
+
+    private final IntegerWriter intWriter = new IntegerWriter();
+
+    IndexedWriter() { }
+
+    IndexedWriter index(int index) {
+        intWriter.configure(index, 7, 0b1000_0000);
+        return this;
+    }
+
+    @Override
+    public boolean write(HeaderTable table, ByteBuffer destination) {
+        return intWriter.write(destination);
+    }
+
+    @Override
+    public BinaryRepresentationWriter reset() {
+        intWriter.reset();
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/IntegerReader.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.hpack;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static java.lang.String.format;
+
+final class IntegerReader {
+
+    private static final int NEW             = 0;
+    private static final int CONFIGURED      = 1;
+    private static final int FIRST_BYTE_READ = 2;
+    private static final int DONE            = 4;
+
+    private int state = NEW;
+
+    private int N;
+    private int maxValue;
+    private int value;
+    private long r;
+    private long b = 1;
+
+    public IntegerReader configure(int N) {
+        return configure(N, Integer.MAX_VALUE);
+    }
+
+    //
+    // Why is it important to configure 'maxValue' here. After all we can wait
+    // for the integer to be fully read and then check it. Can't we?
+    //
+    // Two reasons.
+    //
+    // 1. Value wraps around long won't be unnoticed.
+    // 2. It can spit out an exception as soon as it becomes clear there's
+    // an overflow. Therefore, no need to wait for the value to be fully read.
+    //
+    public IntegerReader configure(int N, int maxValue) {
+        if (state != NEW) {
+            throw new IllegalStateException("Already configured");
+        }
+        checkPrefix(N);
+        if (maxValue < 0) {
+            throw new IllegalArgumentException(
+                    "maxValue >= 0: maxValue=" + maxValue);
+        }
+        this.maxValue = maxValue;
+        this.N = N;
+        state = CONFIGURED;
+        return this;
+    }
+
+    public boolean read(ByteBuffer input) throws IOException {
+        if (state == NEW) {
+            throw new IllegalStateException("Configure first");
+        }
+        if (state == DONE) {
+            return true;
+        }
+        if (!input.hasRemaining()) {
+            return false;
+        }
+        if (state == CONFIGURED) {
+            int max = (2 << (N - 1)) - 1;
+            int n = input.get() & max;
+            if (n != max) {
+                value = n;
+                state = DONE;
+                return true;
+            } else {
+                r = max;
+            }
+            state = FIRST_BYTE_READ;
+        }
+        if (state == FIRST_BYTE_READ) {
+            // variable-length quantity (VLQ)
+            byte i;
+            do {
+                if (!input.hasRemaining()) {
+                    return false;
+                }
+                i = input.get();
+                long increment = b * (i & 127);
+                if (r + increment > maxValue) {
+                    throw new IOException(format(
+                            "Integer overflow: maxValue=%,d, value=%,d",
+                            maxValue, r + increment));
+                }
+                r += increment;
+                b *= 128;
+            } while ((128 & i) == 128);
+
+            value = (int) r;
+            state = DONE;
+            return true;
+        }
+        throw new InternalError(Arrays.toString(
+                new Object[]{state, N, maxValue, value, r, b}));
+    }
+
+    public int get() throws IllegalStateException {
+        if (state != DONE) {
+            throw new IllegalStateException("Has not been fully read yet");
+        }
+        return value;
+    }
+
+    private static void checkPrefix(int N) {
+        if (N < 1 || N > 8) {
+            throw new IllegalArgumentException("1 <= N <= 8: N= " + N);
+        }
+    }
+
+    public IntegerReader reset() {
+        b = 1;
+        state = NEW;
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/IntegerWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,117 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+final class IntegerWriter {
+
+    private static final int NEW                = 0;
+    private static final int CONFIGURED         = 1;
+    private static final int FIRST_BYTE_WRITTEN = 2;
+    private static final int DONE               = 4;
+
+    private int state = NEW;
+
+    private int payload;
+    private int N;
+    private int value;
+
+    //
+    //      0   1   2   3   4   5   6   7
+    //    +---+---+---+---+---+---+---+---+
+    //    |   |   |   |   |   |   |   |   |
+    //    +---+---+---+-------------------+
+    //    |<--------->|<----------------->|
+    //       payload           N=5
+    //
+    // payload is the contents of the left-hand side part of the octet;
+    //         it is truncated to fit into 8-N bits, where 1 <= N <= 8;
+    //
+    public IntegerWriter configure(int value, int N, int payload) {
+        if (state != NEW) {
+            throw new IllegalStateException("Already configured");
+        }
+        if (value < 0) {
+            throw new IllegalArgumentException("value >= 0: value=" + value);
+        }
+        checkPrefix(N);
+        this.value = value;
+        this.N = N;
+        this.payload = payload & 0xFF & (0xFFFFFFFF << N);
+        state = CONFIGURED;
+        return this;
+    }
+
+    public boolean write(ByteBuffer output) {
+        if (state == NEW) {
+            throw new IllegalStateException("Configure first");
+        }
+        if (state == DONE) {
+            return true;
+        }
+
+        if (!output.hasRemaining()) {
+            return false;
+        }
+        if (state == CONFIGURED) {
+            int max = (2 << (N - 1)) - 1;
+            if (value < max) {
+                output.put((byte) (payload | value));
+                state = DONE;
+                return true;
+            }
+            output.put((byte) (payload | max));
+            value -= max;
+            state = FIRST_BYTE_WRITTEN;
+        }
+        if (state == FIRST_BYTE_WRITTEN) {
+            while (value >= 128 && output.hasRemaining()) {
+                output.put((byte) (value % 128 + 128));
+                value /= 128;
+            }
+            if (!output.hasRemaining()) {
+                return false;
+            }
+            output.put((byte) value);
+            state = DONE;
+            return true;
+        }
+        throw new InternalError(Arrays.toString(
+                new Object[]{state, payload, N, value}));
+    }
+
+    private static void checkPrefix(int N) {
+        if (N < 1 || N > 8) {
+            throw new IllegalArgumentException("1 <= N <= 8: N= " + N);
+        }
+    }
+
+    public IntegerWriter reset() {
+        state = NEW;
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/LiteralNeverIndexedWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,32 @@
+/*
+ * 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.internal.net.http.hpack;
+
+final class LiteralNeverIndexedWriter extends IndexNameValueWriter {
+
+    LiteralNeverIndexedWriter() {
+        super(0b0001_0000, 4);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/LiteralWithIndexingWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+
+final class LiteralWithIndexingWriter extends IndexNameValueWriter {
+
+    private boolean tableUpdated;
+
+    private CharSequence name;
+    private CharSequence value;
+    private int index;
+
+    LiteralWithIndexingWriter() {
+        super(0b0100_0000, 6);
+    }
+
+    @Override
+    LiteralWithIndexingWriter index(int index) {
+        super.index(index);
+        this.index = index;
+        return this;
+    }
+
+    @Override
+    LiteralWithIndexingWriter name(CharSequence name, boolean useHuffman) {
+        super.name(name, useHuffman);
+        this.name = name;
+        return this;
+    }
+
+    @Override
+    LiteralWithIndexingWriter value(CharSequence value, boolean useHuffman) {
+        super.value(value, useHuffman);
+        this.value = value;
+        return this;
+    }
+
+    @Override
+    public boolean write(HeaderTable table, ByteBuffer destination) {
+        if (!tableUpdated) {
+            CharSequence n;
+            if (indexedRepresentation) {
+                n = table.get(index).name;
+            } else {
+                n = name;
+            }
+            table.put(n, value);
+            tableUpdated = true;
+        }
+        return super.write(table, destination);
+    }
+
+    @Override
+    public IndexNameValueWriter reset() {
+        tableUpdated = false;
+        name = null;
+        value = null;
+        index = -1;
+        return super.reset();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/LiteralWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,32 @@
+/*
+ * 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.internal.net.http.hpack;
+
+final class LiteralWriter extends IndexNameValueWriter {
+
+    LiteralWriter() {
+        super(0b0000_0000, 4);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/SizeUpdateWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,59 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+
+final class SizeUpdateWriter implements BinaryRepresentationWriter {
+
+    private final IntegerWriter intWriter = new IntegerWriter();
+    private int maxSize;
+    private boolean tableUpdated;
+
+    SizeUpdateWriter() { }
+
+    SizeUpdateWriter maxHeaderTableSize(int size) {
+        intWriter.configure(size, 5, 0b0010_0000);
+        this.maxSize = size;
+        return this;
+    }
+
+    @Override
+    public boolean write(HeaderTable table, ByteBuffer destination) {
+        if (!tableUpdated) {
+            table.setMaxSize(maxSize);
+            tableUpdated = true;
+        }
+        return intWriter.write(destination);
+    }
+
+    @Override
+    public BinaryRepresentationWriter reset() {
+        intWriter.reset();
+        maxSize = -1;
+        tableUpdated = false;
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringReader.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,114 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+//
+//          0   1   2   3   4   5   6   7
+//        +---+---+---+---+---+---+---+---+
+//        | H |    String Length (7+)     |
+//        +---+---------------------------+
+//        |  String Data (Length octets)  |
+//        +-------------------------------+
+//
+final class StringReader {
+
+    private static final int NEW             = 0;
+    private static final int FIRST_BYTE_READ = 1;
+    private static final int LENGTH_READ     = 2;
+    private static final int DONE            = 4;
+
+    private final IntegerReader intReader = new IntegerReader();
+    private final Huffman.Reader huffmanReader = new Huffman.Reader();
+    private final ISO_8859_1.Reader plainReader = new ISO_8859_1.Reader();
+
+    private int state = NEW;
+
+    private boolean huffman;
+    private int remainingLength;
+
+    boolean read(ByteBuffer input, Appendable output) throws IOException {
+        if (state == DONE) {
+            return true;
+        }
+        if (!input.hasRemaining()) {
+            return false;
+        }
+        if (state == NEW) {
+            int p = input.position();
+            huffman = (input.get(p) & 0b10000000) != 0;
+            state = FIRST_BYTE_READ;
+            intReader.configure(7);
+        }
+        if (state == FIRST_BYTE_READ) {
+            boolean lengthRead = intReader.read(input);
+            if (!lengthRead) {
+                return false;
+            }
+            remainingLength = intReader.get();
+            state = LENGTH_READ;
+        }
+        if (state == LENGTH_READ) {
+            boolean isLast = input.remaining() >= remainingLength;
+            int oldLimit = input.limit();
+            if (isLast) {
+                input.limit(input.position() + remainingLength);
+            }
+            remainingLength -= Math.min(input.remaining(), remainingLength);
+            if (huffman) {
+                huffmanReader.read(input, output, isLast);
+            } else {
+                plainReader.read(input, output);
+            }
+            if (isLast) {
+                input.limit(oldLimit);
+                state = DONE;
+            }
+            return isLast;
+        }
+        throw new InternalError(Arrays.toString(
+                new Object[]{state, huffman, remainingLength}));
+    }
+
+    boolean isHuffmanEncoded() {
+        if (state < FIRST_BYTE_READ) {
+            throw new IllegalStateException("Has not been fully read yet");
+        }
+        return huffman;
+    }
+
+    void reset() {
+        if (huffman) {
+            huffmanReader.reset();
+        } else {
+            plainReader.reset();
+        }
+        intReader.reset();
+        state = NEW;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringWriter.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,128 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+//
+//          0   1   2   3   4   5   6   7
+//        +---+---+---+---+---+---+---+---+
+//        | H |    String Length (7+)     |
+//        +---+---------------------------+
+//        |  String Data (Length octets)  |
+//        +-------------------------------+
+//
+// StringWriter does not require a notion of endOfInput (isLast) in 'write'
+// methods due to the nature of string representation in HPACK. Namely, the
+// length of the string is put before string's contents. Therefore the length is
+// always known beforehand.
+//
+// Expected use:
+//
+//     configure write* (reset configure write*)*
+//
+final class StringWriter {
+
+    private static final int NEW            = 0;
+    private static final int CONFIGURED     = 1;
+    private static final int LENGTH_WRITTEN = 2;
+    private static final int DONE           = 4;
+
+    private final IntegerWriter intWriter = new IntegerWriter();
+    private final Huffman.Writer huffmanWriter = new Huffman.Writer();
+    private final ISO_8859_1.Writer plainWriter = new ISO_8859_1.Writer();
+
+    private int state = NEW;
+    private boolean huffman;
+
+    StringWriter configure(CharSequence input, boolean huffman) {
+        return configure(input, 0, input.length(), huffman);
+    }
+
+    StringWriter configure(CharSequence input,
+                           int start,
+                           int end,
+                           boolean huffman) {
+        if (start < 0 || end < 0 || end > input.length() || start > end) {
+            throw new IndexOutOfBoundsException(
+                    String.format("input.length()=%s, start=%s, end=%s",
+                            input.length(), start, end));
+        }
+        if (!huffman) {
+            plainWriter.configure(input, start, end);
+            intWriter.configure(end - start, 7, 0b0000_0000);
+        } else {
+            huffmanWriter.from(input, start, end);
+            intWriter.configure(Huffman.INSTANCE.lengthOf(input, start, end),
+                    7, 0b1000_0000);
+        }
+
+        this.huffman = huffman;
+        state = CONFIGURED;
+        return this;
+    }
+
+    boolean write(ByteBuffer output) {
+        if (state == DONE) {
+            return true;
+        }
+        if (state == NEW) {
+            throw new IllegalStateException("Configure first");
+        }
+        if (!output.hasRemaining()) {
+            return false;
+        }
+        if (state == CONFIGURED) {
+            if (intWriter.write(output)) {
+                state = LENGTH_WRITTEN;
+            } else {
+                return false;
+            }
+        }
+        if (state == LENGTH_WRITTEN) {
+            boolean written = huffman
+                    ? huffmanWriter.write(output)
+                    : plainWriter.write(output);
+            if (written) {
+                state = DONE;
+                return true;
+            } else {
+                return false;
+            }
+        }
+        throw new InternalError(Arrays.toString(new Object[]{state, huffman}));
+    }
+
+    void reset() {
+        intWriter.reset();
+        if (huffman) {
+            huffmanWriter.reset();
+        } else {
+            plainWriter.reset();
+        }
+        state = NEW;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/package-info.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+/**
+ * HPACK (Header Compression for HTTP/2) implementation conforming to
+ * <a href="https://tools.ietf.org/html/rfc7541">RFC&nbsp;7541</a>.
+ *
+ * <p> Headers can be decoded and encoded by {@link jdk.internal.net.http.hpack.Decoder}
+ * and {@link jdk.internal.net.http.hpack.Encoder} respectively.
+ *
+ * <p> Instances of these classes are not safe for use by multiple threads.
+ */
+package jdk.internal.net.http.hpack;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/BuilderImpl.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,149 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.net.http.WebSocket.Builder;
+import java.net.http.WebSocket.Listener;
+import jdk.internal.net.http.common.Pair;
+
+import java.net.ProxySelector;
+import java.net.URI;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.common.Pair.pair;
+
+public final class BuilderImpl implements Builder {
+
+    private final HttpClient client;
+    private URI uri;
+    private Listener listener;
+    private final Optional<ProxySelector> proxySelector;
+    private final Collection<Pair<String, String>> headers;
+    private final Collection<String> subprotocols;
+    private Duration timeout;
+
+    public BuilderImpl(HttpClient client, ProxySelector proxySelector)
+    {
+        this(client, null, null, Optional.ofNullable(proxySelector),
+             new LinkedList<>(), new LinkedList<>(), null);
+    }
+
+    private BuilderImpl(HttpClient client,
+                        URI uri,
+                        Listener listener,
+                        Optional<ProxySelector> proxySelector,
+                        Collection<Pair<String, String>> headers,
+                        Collection<String> subprotocols,
+                        Duration timeout) {
+        this.client = client;
+        this.uri = uri;
+        this.listener = listener;
+        this.proxySelector = proxySelector;
+        // If a proxy selector was supplied by the user, it should be present
+        // on the client and should be the same that what we got as an argument
+        assert !client.proxy().isPresent()
+                || client.proxy().equals(proxySelector);
+        this.headers = headers;
+        this.subprotocols = subprotocols;
+        this.timeout = timeout;
+    }
+
+    @Override
+    public Builder header(String name, String value) {
+        requireNonNull(name, "name");
+        requireNonNull(value, "value");
+        headers.add(pair(name, value));
+        return this;
+    }
+
+    @Override
+    public Builder subprotocols(String mostPreferred, String... lesserPreferred)
+    {
+        requireNonNull(mostPreferred, "mostPreferred");
+        requireNonNull(lesserPreferred, "lesserPreferred");
+        List<String> subprotocols = new LinkedList<>();
+        subprotocols.add(mostPreferred);
+        for (int i = 0; i < lesserPreferred.length; i++) {
+            String p = lesserPreferred[i];
+            requireNonNull(p, "lesserPreferred[" + i + "]");
+            subprotocols.add(p);
+        }
+        this.subprotocols.clear();
+        this.subprotocols.addAll(subprotocols);
+        return this;
+    }
+
+    @Override
+    public Builder connectTimeout(Duration timeout) {
+        this.timeout = requireNonNull(timeout, "timeout");
+        return this;
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> buildAsync(URI uri, Listener listener) {
+        this.uri = requireNonNull(uri, "uri");
+        this.listener = requireNonNull(listener, "listener");
+        // A snapshot of builder inaccessible for further modification
+        // from the outside
+        BuilderImpl copy = immutableCopy();
+        return WebSocketImpl.newInstanceAsync(copy);
+    }
+
+    HttpClient getClient() { return client; }
+
+    URI getUri() { return uri; }
+
+    Listener getListener() { return listener; }
+
+    Collection<Pair<String, String>> getHeaders() { return headers; }
+
+    Collection<String> getSubprotocols() { return subprotocols; }
+
+    Duration getConnectTimeout() { return timeout; }
+
+    Optional<ProxySelector> getProxySelector() { return proxySelector; }
+
+    private BuilderImpl immutableCopy() {
+        @SuppressWarnings({"unchecked", "rawtypes"})
+        BuilderImpl copy = new BuilderImpl(
+                client,
+                uri,
+                listener,
+                proxySelector,
+                List.of(this.headers.toArray(new Pair[0])),
+                List.of(this.subprotocols.toArray(new String[0])),
+                timeout);
+        return copy;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/CheckFailedException.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,40 @@
+/*
+ * 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.internal.net.http.websocket;
+
+/*
+ * Used as a context-neutral exception which can be wrapped into (for example)
+ * a `ProtocolException` or an `IllegalArgumentException` depending on who's
+ * doing the check.
+ */
+final class CheckFailedException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    CheckFailedException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/FailWebSocketException.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,59 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import static jdk.internal.net.http.websocket.StatusCodes.PROTOCOL_ERROR;
+
+/*
+ * Used as a marker for protocol issues in the incoming data, so that the
+ * implementation could "Fail the WebSocket Connection" with a status code in
+ * the Close message that fits the situation the most.
+ *
+ *     https://tools.ietf.org/html/rfc6455#section-7.1.7
+ */
+final class FailWebSocketException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+    private final int statusCode;
+
+    FailWebSocketException(String detail) {
+        this(detail, PROTOCOL_ERROR);
+    }
+
+    FailWebSocketException(String detail, int statusCode) {
+        super(detail);
+        this.statusCode = statusCode;
+    }
+
+    int getStatusCode() {
+        return statusCode;
+    }
+
+    @Override
+    public FailWebSocketException initCause(Throwable cause) {
+        return (FailWebSocketException) super.initCause(cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/Frame.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,499 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import jdk.internal.vm.annotation.Stable;
+
+import java.nio.ByteBuffer;
+
+import static jdk.internal.net.http.common.Utils.dump;
+import static jdk.internal.net.http.websocket.Frame.Opcode.ofCode;
+
+/*
+ * A collection of utilities for reading, writing, and masking frames.
+ */
+final class Frame {
+
+    private Frame() { }
+
+    static final int MAX_HEADER_SIZE_BYTES = 2 + 8 + 4;
+
+    enum Opcode {
+
+        CONTINUATION   (0x0),
+        TEXT           (0x1),
+        BINARY         (0x2),
+        NON_CONTROL_0x3(0x3),
+        NON_CONTROL_0x4(0x4),
+        NON_CONTROL_0x5(0x5),
+        NON_CONTROL_0x6(0x6),
+        NON_CONTROL_0x7(0x7),
+        CLOSE          (0x8),
+        PING           (0x9),
+        PONG           (0xA),
+        CONTROL_0xB    (0xB),
+        CONTROL_0xC    (0xC),
+        CONTROL_0xD    (0xD),
+        CONTROL_0xE    (0xE),
+        CONTROL_0xF    (0xF);
+
+        @Stable
+        private static final Opcode[] opcodes;
+
+        static {
+            Opcode[] values = values();
+            opcodes = new Opcode[values.length];
+            for (Opcode c : values) {
+                opcodes[c.code] = c;
+            }
+        }
+
+        private final byte code;
+
+        Opcode(int code) {
+            this.code = (byte) code;
+        }
+
+        boolean isControl() {
+            return (code & 0x8) != 0;
+        }
+
+        static Opcode ofCode(int code) {
+            return opcodes[code & 0xF];
+        }
+    }
+
+    /*
+     * A utility for masking frame payload data.
+     */
+    static final class Masker {
+
+        // Exploiting ByteBuffer's ability to read/write multi-byte integers
+        private final ByteBuffer acc = ByteBuffer.allocate(8);
+        private final int[] maskBytes = new int[4];
+        private int offset;
+        private long maskLong;
+
+        /*
+         * Reads all remaining bytes from the given input buffer, masks them
+         * with the supplied mask and writes the resulting bytes to the given
+         * output buffer.
+         *
+         * The source and the destination buffers may be the same instance.
+         */
+        static void transferMasking(ByteBuffer src, ByteBuffer dst, int mask) {
+            if (src.remaining() > dst.remaining()) {
+                throw new IllegalArgumentException(dump(src, dst));
+            }
+            new Masker().mask(mask).transferMasking(src, dst);
+        }
+
+        /*
+         * Clears this instance's state and sets the mask.
+         *
+         * The behaviour is as if the mask was set on a newly created instance.
+         */
+        Masker mask(int value) {
+            acc.clear().putInt(value).putInt(value).flip();
+            for (int i = 0; i < maskBytes.length; i++) {
+                maskBytes[i] = acc.get(i);
+            }
+            offset = 0;
+            maskLong = acc.getLong(0);
+            return this;
+        }
+
+        /*
+         * Reads as many remaining bytes as possible from the given input
+         * buffer, masks them with the previously set mask and writes the
+         * resulting bytes to the given output buffer.
+         *
+         * The source and the destination buffers may be the same instance. If
+         * the mask hasn't been previously set it is assumed to be 0.
+         */
+        Masker transferMasking(ByteBuffer src, ByteBuffer dst) {
+            begin(src, dst);
+            loop(src, dst);
+            end(src, dst);
+            return this;
+        }
+
+        /*
+         * Applies up to 3 remaining from the previous pass bytes of the mask.
+         */
+        private void begin(ByteBuffer src, ByteBuffer dst) {
+            if (offset == 0) { // No partially applied mask from the previous invocation
+                return;
+            }
+            int i = src.position(), j = dst.position();
+            final int srcLim = src.limit(), dstLim = dst.limit();
+            for (; offset < 4 && i < srcLim && j < dstLim; i++, j++, offset++)
+            {
+                dst.put(j, (byte) (src.get(i) ^ maskBytes[offset]));
+            }
+            offset &= 3; // Will become 0 if the mask has been fully applied
+            src.position(i);
+            dst.position(j);
+        }
+
+        /*
+         * Gallops one long (mask + mask) at a time.
+         */
+        private void loop(ByteBuffer src, ByteBuffer dst) {
+            int i = src.position();
+            int j = dst.position();
+            final int srcLongLim = src.limit() - 7, dstLongLim = dst.limit() - 7;
+            for (; i < srcLongLim && j < dstLongLim; i += 8, j += 8) {
+                dst.putLong(j, src.getLong(i) ^ maskLong);
+            }
+            if (i > src.limit()) {
+                src.position(i - 8);
+            } else {
+                src.position(i);
+            }
+            if (j > dst.limit()) {
+                dst.position(j - 8);
+            } else {
+                dst.position(j);
+            }
+        }
+
+        /*
+         * Applies up to 7 remaining from the "galloping" phase bytes of the
+         * mask.
+         */
+        private void end(ByteBuffer src, ByteBuffer dst) {
+            assert Math.min(src.remaining(), dst.remaining()) < 8;
+            final int srcLim = src.limit(), dstLim = dst.limit();
+            int i = src.position(), j = dst.position();
+            for (; i < srcLim && j < dstLim;
+                 i++, j++, offset = (offset + 1) & 3) // offset cycles through 0..3
+            {
+                dst.put(j, (byte) (src.get(i) ^ maskBytes[offset]));
+            }
+            src.position(i);
+            dst.position(j);
+        }
+    }
+
+    /*
+     * A builder-style writer of frame headers.
+     *
+     * The writer does not enforce any protocol-level rules, it simply writes a
+     * header structure to the given buffer. The order of calls to intermediate
+     * methods is NOT significant.
+     */
+    static final class HeaderWriter {
+
+        private char firstChar;
+        private long payloadLen;
+        private int maskingKey;
+        private boolean mask;
+
+        HeaderWriter fin(boolean value) {
+            if (value) {
+                firstChar |=  0b10000000_00000000;
+            } else {
+                firstChar &= ~0b10000000_00000000;
+            }
+            return this;
+        }
+
+        HeaderWriter rsv1(boolean value) {
+            if (value) {
+                firstChar |=  0b01000000_00000000;
+            } else {
+                firstChar &= ~0b01000000_00000000;
+            }
+            return this;
+        }
+
+        HeaderWriter rsv2(boolean value) {
+            if (value) {
+                firstChar |=  0b00100000_00000000;
+            } else {
+                firstChar &= ~0b00100000_00000000;
+            }
+            return this;
+        }
+
+        HeaderWriter rsv3(boolean value) {
+            if (value) {
+                firstChar |=  0b00010000_00000000;
+            } else {
+                firstChar &= ~0b00010000_00000000;
+            }
+            return this;
+        }
+
+        HeaderWriter opcode(Opcode value) {
+            firstChar = (char) ((firstChar & 0xF0FF) | (value.code << 8));
+            return this;
+        }
+
+        HeaderWriter payloadLen(long value) {
+            if (value < 0) {
+                throw new IllegalArgumentException("Negative: " + value);
+            }
+            payloadLen = value;
+            firstChar &= 0b11111111_10000000; // Clear previous payload length leftovers
+            if (payloadLen < 126) {
+                firstChar |= payloadLen;
+            } else if (payloadLen < 65536) {
+                firstChar |= 126;
+            } else {
+                firstChar |= 127;
+            }
+            return this;
+        }
+
+        HeaderWriter mask(int value) {
+            firstChar |= 0b00000000_10000000;
+            maskingKey = value;
+            mask = true;
+            return this;
+        }
+
+        HeaderWriter noMask() {
+            firstChar &= ~0b00000000_10000000;
+            mask = false;
+            return this;
+        }
+
+        /*
+         * Writes the header to the given buffer.
+         *
+         * The buffer must have at least MAX_HEADER_SIZE_BYTES remaining. The
+         * buffer's position is incremented by the number of bytes written.
+         */
+        void write(ByteBuffer buffer) {
+            buffer.putChar(firstChar);
+            if (payloadLen >= 126) {
+                if (payloadLen < 65536) {
+                    buffer.putChar((char) payloadLen);
+                } else {
+                    buffer.putLong(payloadLen);
+                }
+            }
+            if (mask) {
+                buffer.putInt(maskingKey);
+            }
+        }
+    }
+
+    /*
+     * A consumer of frame parts.
+     *
+     * Frame.Reader invokes the consumer's methods in the following order:
+     *
+     *     fin rsv1 rsv2 rsv3 opcode mask payloadLength maskingKey? payloadData+ endFrame
+     */
+    interface Consumer {
+
+        void fin(boolean value);
+
+        void rsv1(boolean value);
+
+        void rsv2(boolean value);
+
+        void rsv3(boolean value);
+
+        void opcode(Opcode value);
+
+        void mask(boolean value);
+
+        void payloadLen(long value);
+
+        void maskingKey(int value);
+
+        /*
+         * Called by the Frame.Reader when a part of the (or a complete) payload
+         * is ready to be consumed.
+         *
+         * The sum of numbers of bytes consumed in each invocation of this
+         * method corresponding to the given frame WILL be equal to
+         * 'payloadLen', reported to `void payloadLen(long value)` before that.
+         *
+         * In particular, if `payloadLen` is 0, then there WILL be a single
+         * invocation to this method.
+         *
+         * No unmasking is done.
+         */
+        void payloadData(ByteBuffer data);
+
+        void endFrame();
+    }
+
+    /*
+     * A Reader of frames.
+     *
+     * No protocol-level rules are checked.
+     */
+    static final class Reader {
+
+        private static final int AWAITING_FIRST_BYTE  =  1;
+        private static final int AWAITING_SECOND_BYTE =  2;
+        private static final int READING_16_LENGTH    =  4;
+        private static final int READING_64_LENGTH    =  8;
+        private static final int READING_MASK         = 16;
+        private static final int READING_PAYLOAD      = 32;
+
+        // Exploiting ByteBuffer's ability to read multi-byte integers
+        private final ByteBuffer accumulator = ByteBuffer.allocate(8);
+        private int state = AWAITING_FIRST_BYTE;
+        private boolean mask;
+        private long remainingPayloadLength;
+
+        /*
+         * Reads at most one frame from the given buffer invoking the consumer's
+         * methods corresponding to the frame parts found.
+         *
+         * As much of the frame's payload, if any, is read. The buffer's
+         * position is updated to reflect the number of bytes read.
+         *
+         * Throws FailWebSocketException if detects the frame is malformed.
+         */
+        void readFrame(ByteBuffer input, Consumer consumer) {
+            loop:
+            while (true) {
+                byte b;
+                switch (state) {
+                    case AWAITING_FIRST_BYTE:
+                        if (!input.hasRemaining()) {
+                            break loop;
+                        }
+                        b = input.get();
+                        consumer.fin( (b & 0b10000000) != 0);
+                        consumer.rsv1((b & 0b01000000) != 0);
+                        consumer.rsv2((b & 0b00100000) != 0);
+                        consumer.rsv3((b & 0b00010000) != 0);
+                        consumer.opcode(ofCode(b));
+                        state = AWAITING_SECOND_BYTE;
+                        continue loop;
+                    case AWAITING_SECOND_BYTE:
+                        if (!input.hasRemaining()) {
+                            break loop;
+                        }
+                        b = input.get();
+                        consumer.mask(mask = (b & 0b10000000) != 0);
+                        byte p1 = (byte) (b & 0b01111111);
+                        if (p1 < 126) {
+                            assert p1 >= 0 : p1;
+                            consumer.payloadLen(remainingPayloadLength = p1);
+                            state = mask ? READING_MASK : READING_PAYLOAD;
+                        } else if (p1 < 127) {
+                            state = READING_16_LENGTH;
+                        } else {
+                            state = READING_64_LENGTH;
+                        }
+                        continue loop;
+                    case READING_16_LENGTH:
+                        if (!input.hasRemaining()) {
+                            break loop;
+                        }
+                        b = input.get();
+                        if (accumulator.put(b).position() < 2) {
+                            continue loop;
+                        }
+                        remainingPayloadLength = accumulator.flip().getChar();
+                        if (remainingPayloadLength < 126) {
+                            throw notMinimalEncoding(remainingPayloadLength);
+                        }
+                        consumer.payloadLen(remainingPayloadLength);
+                        accumulator.clear();
+                        state = mask ? READING_MASK : READING_PAYLOAD;
+                        continue loop;
+                    case READING_64_LENGTH:
+                        if (!input.hasRemaining()) {
+                            break loop;
+                        }
+                        b = input.get();
+                        if (accumulator.put(b).position() < 8) {
+                            continue loop;
+                        }
+                        remainingPayloadLength = accumulator.flip().getLong();
+                        if (remainingPayloadLength < 0) {
+                            throw negativePayload(remainingPayloadLength);
+                        } else if (remainingPayloadLength < 65536) {
+                            throw notMinimalEncoding(remainingPayloadLength);
+                        }
+                        consumer.payloadLen(remainingPayloadLength);
+                        accumulator.clear();
+                        state = mask ? READING_MASK : READING_PAYLOAD;
+                        continue loop;
+                    case READING_MASK:
+                        if (!input.hasRemaining()) {
+                            break loop;
+                        }
+                        b = input.get();
+                        if (accumulator.put(b).position() != 4) {
+                            continue loop;
+                        }
+                        consumer.maskingKey(accumulator.flip().getInt());
+                        accumulator.clear();
+                        state = READING_PAYLOAD;
+                        continue loop;
+                    case READING_PAYLOAD:
+                        // This state does not require any bytes to be available
+                        // in the input buffer in order to proceed
+                        int deliverable = (int) Math.min(remainingPayloadLength,
+                                                         input.remaining());
+                        int oldLimit = input.limit();
+                        input.limit(input.position() + deliverable);
+                        if (deliverable != 0 || remainingPayloadLength == 0) {
+                            consumer.payloadData(input);
+                        }
+                        int consumed = deliverable - input.remaining();
+                        if (consumed < 0) {
+                            // Consumer cannot consume more than there was available
+                            throw new InternalError();
+                        }
+                        input.limit(oldLimit);
+                        remainingPayloadLength -= consumed;
+                        if (remainingPayloadLength == 0) {
+                            consumer.endFrame();
+                            state = AWAITING_FIRST_BYTE;
+                        }
+                        break loop;
+                    default:
+                        throw new InternalError(String.valueOf(state));
+                }
+            }
+        }
+
+        private static FailWebSocketException negativePayload(long payloadLength)
+        {
+            return new FailWebSocketException(
+                    "Negative payload length: " + payloadLength);
+        }
+
+        private static FailWebSocketException notMinimalEncoding(long payloadLength)
+        {
+            return new FailWebSocketException(
+                    "Not minimally-encoded payload length:" + payloadLength);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/FrameConsumer.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,288 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.WebSocket.MessagePart;
+import jdk.internal.net.http.websocket.Frame.Opcode;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.common.Utils.dump;
+import static jdk.internal.net.http.websocket.StatusCodes.NO_STATUS_CODE;
+import static jdk.internal.net.http.websocket.StatusCodes.isLegalToReceiveFromServer;
+
+/*
+ * Consumes frame parts and notifies a message consumer, when there is
+ * sufficient data to produce a message, or part thereof.
+ *
+ * Data consumed but not yet translated is accumulated until it's sufficient to
+ * form a message.
+ */
+/* Non-final for testing purposes only */
+class FrameConsumer implements Frame.Consumer {
+
+    private final static boolean DEBUG = false;
+    private final MessageStreamConsumer output;
+    private final UTF8AccumulatingDecoder decoder = new UTF8AccumulatingDecoder();
+    private boolean fin;
+    private Opcode opcode, originatingOpcode;
+    private MessagePart part = MessagePart.WHOLE;
+    private long payloadLen;
+    private long unconsumedPayloadLen;
+    private ByteBuffer binaryData;
+
+    FrameConsumer(MessageStreamConsumer output) {
+        this.output = requireNonNull(output);
+    }
+
+    /* Exposed for testing purposes only */
+    MessageStreamConsumer getOutput() {
+        return output;
+    }
+
+    @Override
+    public void fin(boolean value) {
+        if (DEBUG) {
+            System.out.printf("Reading fin: %s%n", value);
+        }
+        fin = value;
+    }
+
+    @Override
+    public void rsv1(boolean value) {
+        if (DEBUG) {
+            System.out.printf("Reading rsv1: %s%n", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Unexpected rsv1 bit");
+        }
+    }
+
+    @Override
+    public void rsv2(boolean value) {
+        if (DEBUG) {
+            System.out.printf("Reading rsv2: %s%n", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Unexpected rsv2 bit");
+        }
+    }
+
+    @Override
+    public void rsv3(boolean value) {
+        if (DEBUG) {
+            System.out.printf("Reading rsv3: %s%n", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Unexpected rsv3 bit");
+        }
+    }
+
+    @Override
+    public void opcode(Opcode v) {
+        if (DEBUG) {
+            System.out.printf("Reading opcode: %s%n", v);
+        }
+        if (v == Opcode.PING || v == Opcode.PONG || v == Opcode.CLOSE) {
+            if (!fin) {
+                throw new FailWebSocketException("Fragmented control frame  " + v);
+            }
+            opcode = v;
+        } else if (v == Opcode.TEXT || v == Opcode.BINARY) {
+            if (originatingOpcode != null) {
+                throw new FailWebSocketException(
+                        format("Unexpected frame %s (fin=%s)", v, fin));
+            }
+            opcode = v;
+            if (!fin) {
+                originatingOpcode = v;
+            }
+        } else if (v == Opcode.CONTINUATION) {
+            if (originatingOpcode == null) {
+                throw new FailWebSocketException(
+                        format("Unexpected frame %s (fin=%s)", v, fin));
+            }
+            opcode = v;
+        } else {
+            throw new FailWebSocketException("Unexpected opcode " + v);
+        }
+    }
+
+    @Override
+    public void mask(boolean value) {
+        if (DEBUG) {
+            System.out.printf("Reading mask: %s%n", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Masked frame received");
+        }
+    }
+
+    @Override
+    public void payloadLen(long value) {
+        if (DEBUG) {
+            System.out.printf("Reading payloadLen: %s%n", value);
+        }
+        if (opcode.isControl()) {
+            if (value > 125) {
+                throw new FailWebSocketException(
+                        format("%s's payload length %s", opcode, value));
+            }
+            assert Opcode.CLOSE.isControl();
+            if (opcode == Opcode.CLOSE && value == 1) {
+                throw new FailWebSocketException("Incomplete status code");
+            }
+        }
+        payloadLen = value;
+        unconsumedPayloadLen = value;
+    }
+
+    @Override
+    public void maskingKey(int value) {
+        // `FrameConsumer.mask(boolean)` is where a masked frame is detected and
+        // reported on; `FrameConsumer.mask(boolean)` MUST be invoked before
+        // this method;
+        // So this method (`maskingKey`) is not supposed to be invoked while
+        // reading a frame that has came from the server. If this method is
+        // invoked, then it's an error in implementation, thus InternalError
+        throw new InternalError();
+    }
+
+    @Override
+    public void payloadData(ByteBuffer data) {
+        if (DEBUG) {
+            System.out.printf("Reading payloadData: %s%n", data);
+        }
+        unconsumedPayloadLen -= data.remaining();
+        boolean isLast = unconsumedPayloadLen == 0;
+        if (opcode.isControl()) {
+            if (binaryData != null) { // An intermediate or the last chunk
+                binaryData.put(data);
+            } else if (!isLast) { // The first chunk
+                int remaining = data.remaining();
+                // It shouldn't be 125, otherwise the next chunk will be of size
+                // 0, which is not what Reader promises to deliver (eager
+                // reading)
+                assert remaining < 125 : dump(remaining);
+                binaryData = ByteBuffer.allocate(125).put(data);
+            } else { // The only chunk
+                binaryData = ByteBuffer.allocate(data.remaining()).put(data);
+            }
+        } else {
+            part = determinePart(isLast);
+            boolean text = opcode == Opcode.TEXT || originatingOpcode == Opcode.TEXT;
+            if (!text) {
+                output.onBinary(data.slice(), part);
+                data.position(data.limit()); // Consume
+            } else {
+                boolean binaryNonEmpty = data.hasRemaining();
+                CharBuffer textData;
+                try {
+                    textData = decoder.decode(data, part == MessagePart.WHOLE || part == MessagePart.LAST);
+                } catch (CharacterCodingException e) {
+                    throw new FailWebSocketException(
+                            "Invalid UTF-8 in frame " + opcode, StatusCodes.NOT_CONSISTENT)
+                            .initCause(e);
+                }
+                if (!(binaryNonEmpty && !textData.hasRemaining())) {
+                    // If there's a binary data, that result in no text, then we
+                    // don't deliver anything
+                    output.onText(textData, part);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void endFrame() {
+        if (DEBUG) {
+            System.out.println("End frame");
+        }
+        if (opcode.isControl()) {
+            binaryData.flip();
+        }
+        switch (opcode) {
+            case CLOSE:
+                char statusCode = NO_STATUS_CODE;
+                String reason = "";
+                if (payloadLen != 0) {
+                    int len = binaryData.remaining();
+                    assert 2 <= len && len <= 125 : dump(len, payloadLen);
+                    statusCode = binaryData.getChar();
+                    if (!isLegalToReceiveFromServer(statusCode)) {
+                        throw new FailWebSocketException(
+                                "Illegal status code: " + statusCode);
+                    }
+                    try {
+                        reason = UTF_8.newDecoder().decode(binaryData).toString();
+                    } catch (CharacterCodingException e) {
+                        throw new FailWebSocketException("Illegal close reason")
+                                .initCause(e);
+                    }
+                }
+                output.onClose(statusCode, reason);
+                break;
+            case PING:
+                output.onPing(binaryData);
+                binaryData = null;
+                break;
+            case PONG:
+                output.onPong(binaryData);
+                binaryData = null;
+                break;
+            default:
+                assert opcode == Opcode.TEXT || opcode == Opcode.BINARY
+                        || opcode == Opcode.CONTINUATION : dump(opcode);
+                if (fin) {
+                    // It is always the last chunk:
+                    // either TEXT(FIN=TRUE)/BINARY(FIN=TRUE) or CONT(FIN=TRUE)
+                    originatingOpcode = null;
+                }
+                break;
+        }
+        payloadLen = 0;
+        opcode = null;
+    }
+
+    private MessagePart determinePart(boolean isLast) {
+        boolean lastChunk = fin && isLast;
+        switch (part) {
+            case LAST:
+            case WHOLE:
+                return lastChunk ? MessagePart.WHOLE : MessagePart.FIRST;
+            case FIRST:
+            case PART:
+                return lastChunk ? MessagePart.LAST : MessagePart.PART;
+            default:
+                throw new InternalError(String.valueOf(part));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageStreamConsumer.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,54 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.WebSocket.MessagePart;
+
+import java.nio.ByteBuffer;
+
+/*
+ * A callback for consuming messages and related events on the stream.
+ */
+interface MessageStreamConsumer {
+
+    void onText(CharSequence data, MessagePart part);
+
+    void onBinary(ByteBuffer data, MessagePart part);
+
+    void onPing(ByteBuffer data);
+
+    void onPong(ByteBuffer data);
+
+    void onClose(int statusCode, CharSequence reason);
+
+    /*
+     * Indicates the end of stream has been reached and there will be no further
+     * messages.
+     */
+    void onComplete();
+
+    void onError(Throwable e);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OpeningHandshake.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,392 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.WebSocketHandshakeException;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Pair;
+import jdk.internal.net.http.common.Utils;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLPermission;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedAction;
+import java.security.SecureRandom;
+import java.time.Duration;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.String.format;
+import static jdk.internal.net.http.common.Utils.isValidName;
+import static jdk.internal.net.http.common.Utils.permissionForProxy;
+import static jdk.internal.net.http.common.Utils.stringOf;
+
+public class OpeningHandshake {
+
+    private static final String HEADER_CONNECTION = "Connection";
+    private static final String HEADER_UPGRADE    = "Upgrade";
+    private static final String HEADER_ACCEPT     = "Sec-WebSocket-Accept";
+    private static final String HEADER_EXTENSIONS = "Sec-WebSocket-Extensions";
+    private static final String HEADER_KEY        = "Sec-WebSocket-Key";
+    private static final String HEADER_PROTOCOL   = "Sec-WebSocket-Protocol";
+    private static final String HEADER_VERSION    = "Sec-WebSocket-Version";
+    private static final String VERSION           = "13";  // WebSocket's lucky number
+
+    private static final Set<String> ILLEGAL_HEADERS;
+
+    static {
+        ILLEGAL_HEADERS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+        ILLEGAL_HEADERS.addAll(List.of(HEADER_ACCEPT,
+                                       HEADER_EXTENSIONS,
+                                       HEADER_KEY,
+                                       HEADER_PROTOCOL,
+                                       HEADER_VERSION));
+    }
+
+    private static final SecureRandom random = new SecureRandom();
+
+    private final MessageDigest sha1;
+    private final HttpClient client;
+
+    {
+        try {
+            sha1 = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            // Shouldn't happen: SHA-1 must be available in every Java platform
+            // implementation
+            throw new InternalError("Minimum requirements", e);
+        }
+    }
+
+    private final HttpRequest request;
+    private final Collection<String> subprotocols;
+    private final String nonce;
+
+    public OpeningHandshake(BuilderImpl b) {
+        checkURI(b.getUri());
+        Proxy proxy = proxyFor(b.getProxySelector(), b.getUri());
+        checkPermissions(b, proxy);
+        this.client = b.getClient();
+        URI httpURI = createRequestURI(b.getUri());
+        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(httpURI);
+        Duration connectTimeout = b.getConnectTimeout();
+        if (connectTimeout != null) {
+            requestBuilder.timeout(connectTimeout);
+        }
+        for (Pair<String, String> p : b.getHeaders()) {
+            if (ILLEGAL_HEADERS.contains(p.first)) {
+                throw illegal("Illegal header: " + p.first);
+            }
+            requestBuilder.header(p.first, p.second);
+        }
+        this.subprotocols = createRequestSubprotocols(b.getSubprotocols());
+        if (!this.subprotocols.isEmpty()) {
+            String p = this.subprotocols.stream().collect(Collectors.joining(", "));
+            requestBuilder.header(HEADER_PROTOCOL, p);
+        }
+        requestBuilder.header(HEADER_VERSION, VERSION);
+        this.nonce = createNonce();
+        requestBuilder.header(HEADER_KEY, this.nonce);
+        // Setting request version to HTTP/1.1 forcibly, since it's not possible
+        // to upgrade from HTTP/2 to WebSocket (as of August 2016):
+        //
+        //     https://tools.ietf.org/html/draft-hirano-httpbis-websocket-over-http2-00
+        this.request = requestBuilder.version(Version.HTTP_1_1).GET().build();
+        WebSocketRequest r = (WebSocketRequest) this.request;
+        r.isWebSocket(true);
+        r.setSystemHeader(HEADER_UPGRADE, "websocket");
+        r.setSystemHeader(HEADER_CONNECTION, "Upgrade");
+        r.setProxy(proxy);
+    }
+
+    private static Collection<String> createRequestSubprotocols(
+            Collection<String> subprotocols)
+    {
+        LinkedHashSet<String> sp = new LinkedHashSet<>(subprotocols.size(), 1);
+        for (String s : subprotocols) {
+            if (s.trim().isEmpty() || !isValidName(s)) {
+                throw illegal("Bad subprotocol syntax: " + s);
+            }
+            if (!sp.add(s)) {
+                throw illegal("Duplicating subprotocol: " + s);
+            }
+        }
+        return Collections.unmodifiableCollection(sp);
+    }
+
+    /*
+     * Checks the given URI for being a WebSocket URI and translates it into a
+     * target HTTP URI for the Opening Handshake.
+     *
+     * https://tools.ietf.org/html/rfc6455#section-3
+     */
+    static URI createRequestURI(URI uri) {
+        String s = uri.getScheme();
+        assert "ws".equalsIgnoreCase(s) || "wss".equalsIgnoreCase(s);
+        String scheme = "ws".equalsIgnoreCase(s) ? "http" : "https";
+        try {
+            return new URI(scheme,
+                           uri.getUserInfo(),
+                           uri.getHost(),
+                           uri.getPort(),
+                           uri.getPath(),
+                           uri.getQuery(),
+                           null); // No fragment
+        } catch (URISyntaxException e) {
+            // Shouldn't happen: URI invariant
+            throw new InternalError(e);
+        }
+    }
+
+    public CompletableFuture<Result> send() {
+        PrivilegedAction<CompletableFuture<Result>> pa = () ->
+                client.sendAsync(this.request, BodyHandler.discard())
+                      .thenCompose(this::resultFrom);
+        return AccessController.doPrivileged(pa);
+    }
+
+    /*
+     * The result of the opening handshake.
+     */
+    static final class Result {
+
+        final String subprotocol;
+        final TransportFactory transport;
+
+        private Result(String subprotocol, TransportFactory transport) {
+            this.subprotocol = subprotocol;
+            this.transport = transport;
+        }
+    }
+
+    private CompletableFuture<Result> resultFrom(HttpResponse<?> response) {
+        // Do we need a special treatment for SSLHandshakeException?
+        // Namely, invoking
+        //
+        //     Listener.onClose(StatusCodes.TLS_HANDSHAKE_FAILURE, "")
+        //
+        // See https://tools.ietf.org/html/rfc6455#section-7.4.1
+        Result result = null;
+        Exception exception = null;
+        try {
+            result = handleResponse(response);
+        } catch (IOException e) {
+            exception = e;
+        } catch (Exception e) {
+            exception = new WebSocketHandshakeException(response).initCause(e);
+        }
+        if (exception == null) {
+            return MinimalFuture.completedFuture(result);
+        }
+        try {
+            ((RawChannel.Provider) response).rawChannel().close();
+        } catch (IOException e) {
+            exception.addSuppressed(e);
+        }
+        return MinimalFuture.failedFuture(exception);
+    }
+
+    private Result handleResponse(HttpResponse<?> response) throws IOException {
+        // By this point all redirects, authentications, etc. (if any) MUST have
+        // been done by the HttpClient used by the WebSocket; so only 101 is
+        // expected
+        int c = response.statusCode();
+        if (c != 101) {
+            throw checkFailed("Unexpected HTTP response status code " + c);
+        }
+        HttpHeaders headers = response.headers();
+        String upgrade = requireSingle(headers, HEADER_UPGRADE);
+        if (!upgrade.equalsIgnoreCase("websocket")) {
+            throw checkFailed("Bad response field: " + HEADER_UPGRADE);
+        }
+        String connection = requireSingle(headers, HEADER_CONNECTION);
+        if (!connection.equalsIgnoreCase("Upgrade")) {
+            throw checkFailed("Bad response field: " + HEADER_CONNECTION);
+        }
+        Optional<String> version = requireAtMostOne(headers, HEADER_VERSION);
+        if (version.isPresent() && !version.get().equals(VERSION)) {
+            throw checkFailed("Bad response field: " + HEADER_VERSION);
+        }
+        requireAbsent(headers, HEADER_EXTENSIONS);
+        String x = this.nonce + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+        this.sha1.update(x.getBytes(StandardCharsets.ISO_8859_1));
+        String expected = Base64.getEncoder().encodeToString(this.sha1.digest());
+        String actual = requireSingle(headers, HEADER_ACCEPT);
+        if (!actual.trim().equals(expected)) {
+            throw checkFailed("Bad " + HEADER_ACCEPT);
+        }
+        String subprotocol = checkAndReturnSubprotocol(headers);
+        RawChannel channel = ((RawChannel.Provider) response).rawChannel();
+        return new Result(subprotocol, new TransportFactoryImpl(channel));
+    }
+
+    private String checkAndReturnSubprotocol(HttpHeaders responseHeaders)
+            throws CheckFailedException
+    {
+        Optional<String> opt = responseHeaders.firstValue(HEADER_PROTOCOL);
+        if (!opt.isPresent()) {
+            // If there is no such header in the response, then the server
+            // doesn't want to use any subprotocol
+            return "";
+        }
+        String s = requireSingle(responseHeaders, HEADER_PROTOCOL);
+        // An empty string as a subprotocol's name is not allowed by the spec
+        // and the check below will detect such responses too
+        if (this.subprotocols.contains(s)) {
+            return s;
+        } else {
+            throw checkFailed("Unexpected subprotocol: " + s);
+        }
+    }
+
+    private static void requireAbsent(HttpHeaders responseHeaders,
+                                      String headerName)
+    {
+        List<String> values = responseHeaders.allValues(headerName);
+        if (!values.isEmpty()) {
+            throw checkFailed(format("Response field '%s' present: %s",
+                                     headerName,
+                                     stringOf(values)));
+        }
+    }
+
+    private static Optional<String> requireAtMostOne(HttpHeaders responseHeaders,
+                                                     String headerName)
+    {
+        List<String> values = responseHeaders.allValues(headerName);
+        if (values.size() > 1) {
+            throw checkFailed(format("Response field '%s' multivalued: %s",
+                                     headerName,
+                                     stringOf(values)));
+        }
+        return values.stream().findFirst();
+    }
+
+    private static String requireSingle(HttpHeaders responseHeaders,
+                                        String headerName)
+    {
+        List<String> values = responseHeaders.allValues(headerName);
+        if (values.isEmpty()) {
+            throw checkFailed("Response field missing: " + headerName);
+        } else if (values.size() > 1) {
+            throw checkFailed(format("Response field '%s' multivalued: %s",
+                                     headerName,
+                                     stringOf(values)));
+        }
+        return values.get(0);
+    }
+
+    private static String createNonce() {
+        byte[] bytes = new byte[16];
+        OpeningHandshake.random.nextBytes(bytes);
+        return Base64.getEncoder().encodeToString(bytes);
+    }
+
+    private static CheckFailedException checkFailed(String message) {
+        throw new CheckFailedException(message);
+    }
+
+    private static URI checkURI(URI uri) {
+        String scheme = uri.getScheme();
+        if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
+            throw illegal("invalid URI scheme: " + scheme);
+        if (uri.getHost() == null)
+            throw illegal("URI must contain a host: " + uri);
+        if (uri.getFragment() != null)
+            throw illegal("URI must not contain a fragment: " + uri);
+        return uri;
+    }
+
+    private static IllegalArgumentException illegal(String message) {
+        return new IllegalArgumentException(message);
+    }
+
+    /**
+     * Returns the proxy for the given URI when sent through the given client,
+     * or {@code null} if none is required or applicable.
+     */
+    private static Proxy proxyFor(Optional<ProxySelector> selector, URI uri) {
+        if (!selector.isPresent()) {
+            return null;
+        }
+        URI requestURI = createRequestURI(uri); // Based on the HTTP scheme
+        List<Proxy> pl = selector.get().select(requestURI);
+        if (pl.isEmpty()) {
+            return null;
+        }
+        Proxy proxy = pl.get(0);
+        if (proxy.type() != Proxy.Type.HTTP) {
+            return null;
+        }
+        return proxy;
+    }
+
+    /**
+     * Performs the necessary security permissions checks to connect ( possibly
+     * through a proxy ) to the builders WebSocket URI.
+     *
+     * @throws SecurityException if the security manager denies access
+     */
+    static void checkPermissions(BuilderImpl b, Proxy proxy) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm == null) {
+            return;
+        }
+        Stream<String> headers = b.getHeaders().stream().map(p -> p.first).distinct();
+        URLPermission perm1 = Utils.permissionForServer(b.getUri(), "", headers);
+        sm.checkPermission(perm1);
+        if (proxy == null) {
+            return;
+        }
+        URLPermission perm2 = permissionForProxy((InetSocketAddress) proxy.address());
+        if (perm2 != null) {
+            sm.checkPermission(perm2);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/OutgoingMessage.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,296 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import jdk.internal.net.http.websocket.Frame.Opcode;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.security.SecureRandom;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.common.Utils.EMPTY_BYTEBUFFER;
+import static jdk.internal.net.http.websocket.Frame.MAX_HEADER_SIZE_BYTES;
+import static jdk.internal.net.http.websocket.Frame.Opcode.BINARY;
+import static jdk.internal.net.http.websocket.Frame.Opcode.CLOSE;
+import static jdk.internal.net.http.websocket.Frame.Opcode.CONTINUATION;
+import static jdk.internal.net.http.websocket.Frame.Opcode.PING;
+import static jdk.internal.net.http.websocket.Frame.Opcode.PONG;
+import static jdk.internal.net.http.websocket.Frame.Opcode.TEXT;
+
+/*
+ * A stateful object that represents a WebSocket message being sent to the
+ * channel.
+ *
+ * Data provided to the constructors is copied. Otherwise we would have to deal
+ * with mutability, security, masking/unmasking, readonly status, etc. So
+ * copying greatly simplifies the implementation.
+ *
+ * In the case of memory-sensitive environments an alternative implementation
+ * could use an internal pool of buffers though at the cost of extra complexity
+ * and possible performance degradation.
+ */
+abstract class OutgoingMessage {
+
+    // Share per WebSocket?
+    private static final SecureRandom maskingKeys = new SecureRandom();
+
+    protected ByteBuffer[] frame;
+    protected int offset;
+
+    /*
+     * Performs contextualization. This method is not a part of the constructor
+     * so it would be possible to defer the work it does until the most
+     * convenient moment (up to the point where sentTo is invoked).
+     */
+    protected boolean contextualize(Context context) {
+        // masking and charset decoding should be performed here rather than in
+        // the constructor (as of today)
+        if (context.isCloseSent()) {
+            throw new IllegalStateException("Close sent");
+        }
+        return true;
+    }
+
+    protected boolean sendTo(RawChannel channel) throws IOException {
+        while ((offset = nextUnwrittenIndex()) != -1) {
+            long n = channel.write(frame, offset, frame.length - offset);
+            if (n == 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private int nextUnwrittenIndex() {
+        for (int i = offset; i < frame.length; i++) {
+            if (frame[i].hasRemaining()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    static final class Text extends OutgoingMessage {
+
+        private final ByteBuffer payload;
+        private final boolean isLast;
+
+        Text(CharSequence characters, boolean isLast) {
+            CharsetEncoder encoder = UTF_8.newEncoder(); // Share per WebSocket?
+            try {
+                payload = encoder.encode(CharBuffer.wrap(characters));
+            } catch (CharacterCodingException e) {
+                throw new IllegalArgumentException(
+                        "Malformed UTF-8 text message");
+            }
+            this.isLast = isLast;
+        }
+
+        @Override
+        protected boolean contextualize(Context context) {
+            super.contextualize(context);
+            if (context.isPreviousBinary() && !context.isPreviousLast()) {
+                throw new IllegalStateException("Unexpected text message");
+            }
+            frame = getDataMessageBuffers(
+                    TEXT, context.isPreviousLast(), isLast, payload, payload);
+            context.setPreviousBinary(false);
+            context.setPreviousText(true);
+            context.setPreviousLast(isLast);
+            return true;
+        }
+    }
+
+    static final class Binary extends OutgoingMessage {
+
+        private final ByteBuffer payload;
+        private final boolean isLast;
+
+        Binary(ByteBuffer payload, boolean isLast) {
+            this.payload = requireNonNull(payload);
+            this.isLast = isLast;
+        }
+
+        @Override
+        protected boolean contextualize(Context context) {
+            super.contextualize(context);
+            if (context.isPreviousText() && !context.isPreviousLast()) {
+                throw new IllegalStateException("Unexpected binary message");
+            }
+            ByteBuffer newBuffer = ByteBuffer.allocate(payload.remaining());
+            frame = getDataMessageBuffers(
+                    BINARY, context.isPreviousLast(), isLast, payload, newBuffer);
+            context.setPreviousText(false);
+            context.setPreviousBinary(true);
+            context.setPreviousLast(isLast);
+            return true;
+        }
+    }
+
+    static final class Ping extends OutgoingMessage {
+
+        Ping(ByteBuffer payload) {
+            frame = getControlMessageBuffers(PING, payload);
+        }
+    }
+
+    static final class Pong extends OutgoingMessage {
+
+        Pong(ByteBuffer payload) {
+            frame = getControlMessageBuffers(PONG, payload);
+        }
+    }
+
+    static final class Close extends OutgoingMessage {
+
+        Close() {
+            frame = getControlMessageBuffers(CLOSE, EMPTY_BYTEBUFFER);
+        }
+
+        Close(int statusCode, CharSequence reason) {
+            ByteBuffer payload = ByteBuffer.allocate(125)
+                    .putChar((char) statusCode);
+            CoderResult result = UTF_8.newEncoder()
+                    .encode(CharBuffer.wrap(reason),
+                            payload,
+                            true);
+            if (result.isOverflow()) {
+                throw new IllegalArgumentException("Long reason");
+            } else if (result.isError()) {
+                try {
+                    result.throwException();
+                } catch (CharacterCodingException e) {
+                    throw new IllegalArgumentException(
+                            "Malformed UTF-8 reason", e);
+                }
+            }
+            payload.flip();
+            frame = getControlMessageBuffers(CLOSE, payload);
+        }
+
+        @Override
+        protected boolean contextualize(Context context) {
+            if (context.isCloseSent()) {
+                return false;
+            } else {
+                context.setCloseSent();
+                return true;
+            }
+        }
+    }
+
+    private static ByteBuffer[] getControlMessageBuffers(Opcode opcode,
+                                                         ByteBuffer payload) {
+        assert opcode.isControl() : opcode;
+        int remaining = payload.remaining();
+        if (remaining > 125) {
+            throw new IllegalArgumentException
+                    ("Long message: " + remaining);
+        }
+        ByteBuffer frame = ByteBuffer.allocate(MAX_HEADER_SIZE_BYTES + remaining);
+        int mask = maskingKeys.nextInt();
+        new Frame.HeaderWriter()
+                .fin(true)
+                .opcode(opcode)
+                .payloadLen(remaining)
+                .mask(mask)
+                .write(frame);
+        Frame.Masker.transferMasking(payload, frame, mask);
+        frame.flip();
+        return new ByteBuffer[]{frame};
+    }
+
+    private static ByteBuffer[] getDataMessageBuffers(Opcode type,
+                                                      boolean isPreviousLast,
+                                                      boolean isLast,
+                                                      ByteBuffer payloadSrc,
+                                                      ByteBuffer payloadDst) {
+        assert !type.isControl() && type != CONTINUATION : type;
+        ByteBuffer header = ByteBuffer.allocate(MAX_HEADER_SIZE_BYTES);
+        int mask = maskingKeys.nextInt();
+        new Frame.HeaderWriter()
+                .fin(isLast)
+                .opcode(isPreviousLast ? type : CONTINUATION)
+                .payloadLen(payloadDst.remaining())
+                .mask(mask)
+                .write(header);
+        header.flip();
+        Frame.Masker.transferMasking(payloadSrc, payloadDst, mask);
+        payloadDst.flip();
+        return new ByteBuffer[]{header, payloadDst};
+    }
+
+    /*
+     * An instance of this class is passed sequentially between messages, so
+     * every message in a sequence can check the context it is in and update it
+     * if necessary.
+     */
+    public static class Context {
+
+        boolean previousLast = true;
+        boolean previousBinary;
+        boolean previousText;
+        boolean closeSent;
+
+        private boolean isPreviousText() {
+            return this.previousText;
+        }
+
+        private void setPreviousText(boolean value) {
+            this.previousText = value;
+        }
+
+        private boolean isPreviousBinary() {
+            return this.previousBinary;
+        }
+
+        private void setPreviousBinary(boolean value) {
+            this.previousBinary = value;
+        }
+
+        private boolean isPreviousLast() {
+            return this.previousLast;
+        }
+
+        private void setPreviousLast(boolean value) {
+            this.previousLast = value;
+        }
+
+        private boolean isCloseSent() {
+            return closeSent;
+        }
+
+        private void setCloseSent() {
+            closeSent = true;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/RawChannel.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,128 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+
+/*
+ * I/O abstraction used to implement WebSocket.
+ *
+ * @since 9
+ */
+public interface RawChannel extends Closeable {
+
+    interface Provider {
+
+        RawChannel rawChannel() throws IOException;
+    }
+
+    interface RawEvent {
+
+        /*
+         * Returns the selector op flags this event is interested in.
+         */
+        int interestOps();
+
+        /*
+         * Called when event occurs.
+         */
+        void handle();
+    }
+
+    /*
+     * Registers given event whose callback will be called once only (i.e.
+     * register new event for each callback).
+     *
+     * Memory consistency effects: actions in a thread calling registerEvent
+     * happen-before any subsequent actions in the thread calling event.handle
+     */
+    void registerEvent(RawEvent event) throws IOException;
+
+    /**
+     * Hands over the initial bytes. Once the bytes have been returned they are
+     * no longer available and the method will throw an {@link
+     * IllegalStateException} on each subsequent invocation.
+     *
+     * @return the initial bytes
+     * @throws IllegalStateException
+     *         if the method has been already invoked
+     */
+    ByteBuffer initialByteBuffer() throws IllegalStateException;
+
+    /*
+     * Returns a ByteBuffer with the data read or null if EOF is reached. Has no
+     * remaining bytes if no data available at the moment.
+     */
+    ByteBuffer read() throws IOException;
+
+    /*
+     * Writes a sequence of bytes to this channel from a subsequence of the
+     * given buffers.
+     */
+    long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
+
+    /**
+     * Shutdown the connection for reading without closing the channel.
+     *
+     * <p> Once shutdown for reading then further reads on the channel will
+     * return {@code null}, the end-of-stream indication. If the input side of
+     * the connection is already shutdown then invoking this method has no
+     * effect.
+     *
+     * @throws ClosedChannelException
+     *         If this channel is closed
+     * @throws IOException
+     *         If some other I/O error occurs
+     */
+    void shutdownInput() throws IOException;
+
+    /**
+     * Shutdown the connection for writing without closing the channel.
+     *
+     * <p> Once shutdown for writing then further attempts to write to the
+     * channel will throw {@link ClosedChannelException}. If the output side of
+     * the connection is already shutdown then invoking this method has no
+     * effect.
+     *
+     * @throws ClosedChannelException
+     *         If this channel is closed
+     * @throws IOException
+     *         If some other I/O error occurs
+     */
+    void shutdownOutput() throws IOException;
+
+    /**
+     * Closes this channel.
+     *
+     * @throws IOException
+     *         If an I/O error occurs
+     */
+    @Override
+    void close() throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/StatusCodes.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,96 @@
+/*
+ * 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.internal.net.http.websocket;
+
+/*
+ * Utilities for WebSocket status codes.
+ *
+ *     1. https://tools.ietf.org/html/rfc6455#section-7.4
+ *     2. http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
+ */
+final class StatusCodes {
+
+    static final int PROTOCOL_ERROR    = 1002;
+    static final int NO_STATUS_CODE    = 1005;
+    static final int CLOSED_ABNORMALLY = 1006;
+    static final int NOT_CONSISTENT    = 1007;
+
+    private StatusCodes() { }
+
+    static boolean isLegalToSendFromClient(int code) {
+        if (!isLegal(code)) {
+            return false;
+        }
+        // Codes from unreserved range
+        if (code > 4999) {
+            return false;
+        }
+        // Codes below are not allowed to be sent using a WebSocket client API
+        switch (code) {
+            case PROTOCOL_ERROR:
+            case NOT_CONSISTENT:
+            case 1003:
+            case 1009:
+            case 1010:
+            case 1012:  // code sent by servers
+            case 1013:  // code sent by servers
+            case 1014:  // code sent by servers
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    static boolean isLegalToReceiveFromServer(int code) {
+        if (!isLegal(code)) {
+            return false;
+        }
+        return code != 1010;  // code sent by clients
+    }
+
+    private static boolean isLegal(int code) {
+        // 2-byte unsigned integer excluding first 1000 numbers from the range
+        // [0, 999] which are never used
+        if (code < 1000 || code > 65535) {
+            return false;
+        }
+        // Codes from the range below has no known meaning under the WebSocket
+        // specification (i.e. unassigned/undefined)
+        if ((code >= 1016 && code <= 2999) || code == 1004) {
+            return false;
+        }
+        // Codes below cannot appear on the wire. It's always an error either
+        // to send a frame with such a code or to receive one.
+        switch (code) {
+            case NO_STATUS_CODE:
+            case CLOSED_ABNORMALLY:
+            case 1015:
+                return false;
+            default:
+                return true;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/Transport.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+
+/*
+ * Transport needs some way to asynchronously notify the send operation has been
+ * completed. It can have several different designs each of which has its own
+ * pros and cons:
+ *
+ *     (1) void sendMessage(..., Callback)
+ *     (2) CompletableFuture<T> sendMessage(...)
+ *     (3) CompletableFuture<T> sendMessage(..., Callback)
+ *     (4) boolean sendMessage(..., Callback) throws IOException
+ *     ...
+ *
+ * If Transport's users use CFs, (1) forces these users to create CFs and pass
+ * them to the callback. If any additional (dependant) action needs to be
+ * attached to the returned CF, this means an extra object (CF) must be created
+ * in (2). (3) and (4) solves both issues, however (4) does not abstract out
+ * when exactly the operation has been performed. So the handling code needs to
+ * be repeated twice. And that leads to 2 different code paths (more bugs).
+ * Unless designed for this, the user should not assume any specific order of
+ * completion in (3) (e.g. callback first and then the returned CF).
+ *
+ * The only parametrization of Transport<T> used is Transport<WebSocket>. The
+ * type parameter T was introduced solely to avoid circular dependency between
+ * Transport and WebSocket. After all, instances of T are used solely to
+ * complete CompletableFutures. Transport doesn't care about the exact type of
+ * T.
+ *
+ * This way the Transport is fully in charge of creating CompletableFutures.
+ * On the one hand, Transport may use it to cache/reuse CompletableFutures. On
+ * the other hand, the class that uses Transport, may benefit by not converting
+ * from CompletableFuture<K> returned from Transport, to CompletableFuture<V>
+ * needed by the said class.
+ */
+public interface Transport<T> {
+
+    CompletableFuture<T> sendText(CharSequence message, boolean isLast);
+
+    CompletableFuture<T> sendBinary(ByteBuffer message, boolean isLast);
+
+    CompletableFuture<T> sendPing(ByteBuffer message);
+
+    CompletableFuture<T> sendPong(ByteBuffer message);
+
+    CompletableFuture<T> sendClose(int statusCode, String reason);
+
+    void request(long n);
+
+    /*
+     * Why is this method needed? Since Receiver operates through callbacks
+     * this method allows to abstract out what constitutes as a message being
+     * received (i.e. to decide outside this type when exactly one should
+     * decrement the demand).
+     */
+    void acknowledgeReception();
+
+    void closeOutput() throws IOException;
+
+    void closeInput() throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportFactory.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,10 @@
+package jdk.internal.net.http.websocket;
+
+import java.util.function.Supplier;
+
+@FunctionalInterface
+public interface TransportFactory {
+
+    <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                     MessageStreamConsumer consumer);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportFactoryImpl.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.util.function.Supplier;
+
+public class TransportFactoryImpl implements TransportFactory {
+
+    private final RawChannel channel;
+
+    public TransportFactoryImpl(RawChannel channel) {
+        this.channel = channel;
+    }
+
+    @Override
+    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                            MessageStreamConsumer consumer) {
+        return new TransportImpl<T>(sendResultSupplier, consumer, channel);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportImpl.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,383 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Pair;
+import jdk.internal.net.http.common.SequentialScheduler;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.common.MinimalFuture.failedFuture;
+import static jdk.internal.net.http.common.Pair.pair;
+
+public class TransportImpl<T> implements Transport<T> {
+
+    /* This flag is used solely for assertions */
+    private final AtomicBoolean busy = new AtomicBoolean();
+    private OutgoingMessage message;
+    private Consumer<Exception> completionHandler;
+    private final RawChannel channel;
+    private final RawChannel.RawEvent writeEvent = createWriteEvent();
+    private final SequentialScheduler sendScheduler = new SequentialScheduler(new SendTask());
+    private final Queue<Pair<OutgoingMessage, CompletableFuture<T>>>
+            queue = new ConcurrentLinkedQueue<>();
+    private final OutgoingMessage.Context context = new OutgoingMessage.Context();
+    private final Supplier<T> resultSupplier;
+
+    private final MessageStreamConsumer messageConsumer;
+    private final FrameConsumer frameConsumer;
+    private final Frame.Reader reader = new Frame.Reader();
+    private final RawChannel.RawEvent readEvent = createReadEvent();
+    private final Demand demand = new Demand();
+    private final SequentialScheduler receiveScheduler;
+
+    private ByteBuffer data;
+    private volatile int state;
+
+    private static final int UNREGISTERED = 0;
+    private static final int AVAILABLE    = 1;
+    private static final int WAITING      = 2;
+
+    private final Object lock = new Object();
+    private boolean inputClosed;
+    private boolean outputClosed;
+
+    public TransportImpl(Supplier<T> sendResultSupplier,
+                         MessageStreamConsumer consumer,
+                         RawChannel channel) {
+        this.resultSupplier = sendResultSupplier;
+        this.messageConsumer = consumer;
+        this.channel = channel;
+        this.frameConsumer = new FrameConsumer(this.messageConsumer);
+        this.data = channel.initialByteBuffer();
+        // To ensure the initial non-final `data` will be visible
+        // (happens-before) when `readEvent.handle()` invokes `receiveScheduler`
+        // the following assignment is done last:
+        receiveScheduler = new SequentialScheduler(new ReceiveTask());
+    }
+
+    /**
+     * The supplied handler may be invoked in the calling thread.
+     * A {@code StackOverflowError} may thus occur if there's a possibility
+     * that this method is called again by the supplied handler.
+     */
+    public void send(OutgoingMessage message,
+                     Consumer<Exception> completionHandler) {
+        requireNonNull(message);
+        requireNonNull(completionHandler);
+        if (!busy.compareAndSet(false, true)) {
+            throw new IllegalStateException();
+        }
+        send0(message, completionHandler);
+    }
+
+    private RawChannel.RawEvent createWriteEvent() {
+        return new RawChannel.RawEvent() {
+
+            @Override
+            public int interestOps() {
+                return SelectionKey.OP_WRITE;
+            }
+
+            @Override
+            public void handle() {
+                // registerEvent(e) happens-before subsequent e.handle(), so
+                // we're fine reading the stored message and the completionHandler
+                send0(message, completionHandler);
+            }
+        };
+    }
+
+    private void send0(OutgoingMessage message, Consumer<Exception> handler) {
+        boolean b = busy.get();
+        assert b; // Please don't inline this, as busy.get() has memory
+                  // visibility effects and we don't want the program behaviour
+                  // to depend on whether the assertions are turned on
+                  // or turned off
+        try {
+            boolean sent = message.sendTo(channel);
+            if (sent) {
+                busy.set(false);
+                handler.accept(null);
+            } else {
+                // The message has not been fully sent, the transmitter needs to
+                // remember the message until it can continue with sending it
+                this.message = message;
+                this.completionHandler = handler;
+                try {
+                    channel.registerEvent(writeEvent);
+                } catch (IOException e) {
+                    this.message = null;
+                    this.completionHandler = null;
+                    busy.set(false);
+                    handler.accept(e);
+                }
+            }
+        } catch (IOException e) {
+            busy.set(false);
+            handler.accept(e);
+        }
+    }
+
+    public CompletableFuture<T> sendText(CharSequence message,
+                                         boolean isLast) {
+        OutgoingMessage.Text m;
+        try {
+            m = new OutgoingMessage.Text(message, isLast);
+        } catch (IllegalArgumentException e) {
+            return failedFuture(e);
+        }
+        return enqueue(m);
+    }
+
+    public CompletableFuture<T> sendBinary(ByteBuffer message,
+                                           boolean isLast) {
+        return enqueue(new OutgoingMessage.Binary(message, isLast));
+    }
+
+    public CompletableFuture<T> sendPing(ByteBuffer message) {
+        OutgoingMessage.Ping m;
+        try {
+            m = new OutgoingMessage.Ping(message);
+        } catch (IllegalArgumentException e) {
+            return failedFuture(e);
+        }
+        return enqueue(m);
+    }
+
+    public CompletableFuture<T> sendPong(ByteBuffer message) {
+        OutgoingMessage.Pong m;
+        try {
+            m = new OutgoingMessage.Pong(message);
+        } catch (IllegalArgumentException e) {
+            return failedFuture(e);
+        }
+        return enqueue(m);
+    }
+
+    public CompletableFuture<T> sendClose(int statusCode, String reason) {
+        OutgoingMessage.Close m;
+        try {
+            m = new OutgoingMessage.Close(statusCode, reason);
+        } catch (IllegalArgumentException e) {
+            return failedFuture(e);
+        }
+        return enqueue(m);
+    }
+
+    private CompletableFuture<T> enqueue(OutgoingMessage m) {
+        CompletableFuture<T> cf = new MinimalFuture<>();
+        boolean added = queue.add(pair(m, cf));
+        if (!added) {
+            // The queue is supposed to be unbounded
+            throw new InternalError();
+        }
+        sendScheduler.runOrSchedule();
+        return cf;
+    }
+
+    /*
+     * This is a message sending task. It pulls messages from the queue one by
+     * one and sends them. It may be run in different threads, but never
+     * concurrently.
+     */
+    private class SendTask implements SequentialScheduler.RestartableTask {
+
+        @Override
+        public void run(SequentialScheduler.DeferredCompleter taskCompleter) {
+            Pair<OutgoingMessage, CompletableFuture<T>> p = queue.poll();
+            if (p == null) {
+                taskCompleter.complete();
+                return;
+            }
+            OutgoingMessage message = p.first;
+            CompletableFuture<T> cf = p.second;
+            try {
+                if (!message.contextualize(context)) { // Do not send the message
+                    cf.complete(resultSupplier.get());
+                    repeat(taskCompleter);
+                    return;
+                }
+                Consumer<Exception> h = e -> {
+                    if (e == null) {
+                        cf.complete(resultSupplier.get());
+                    } else {
+                        cf.completeExceptionally(e);
+                    }
+                    repeat(taskCompleter);
+                };
+                send(message, h);
+            } catch (Throwable t) {
+                cf.completeExceptionally(t);
+                repeat(taskCompleter);
+            }
+        }
+
+        private void repeat(SequentialScheduler.DeferredCompleter taskCompleter) {
+            taskCompleter.complete();
+            // More than a single message may have been enqueued while
+            // the task has been busy with the current message, but
+            // there is only a single signal recorded
+            sendScheduler.runOrSchedule();
+        }
+    }
+
+    private RawChannel.RawEvent createReadEvent() {
+        return new RawChannel.RawEvent() {
+
+            @Override
+            public int interestOps() {
+                return SelectionKey.OP_READ;
+            }
+
+            @Override
+            public void handle() {
+                state = AVAILABLE;
+                receiveScheduler.runOrSchedule();
+            }
+        };
+    }
+
+    @Override
+    public void request(long n) {
+        if (demand.increase(n)) {
+            receiveScheduler.runOrSchedule();
+        }
+    }
+
+    @Override
+    public void acknowledgeReception() {
+        boolean decremented = demand.tryDecrement();
+        if (!decremented) {
+            throw new InternalError();
+        }
+    }
+
+    private class ReceiveTask extends SequentialScheduler.CompleteRestartableTask {
+
+        @Override
+        public void run() {
+            while (!receiveScheduler.isStopped()) {
+                if (data.hasRemaining()) {
+                    if (!demand.isFulfilled()) {
+                        try {
+                            int oldPos = data.position();
+                            reader.readFrame(data, frameConsumer);
+                            int newPos = data.position();
+                            assert oldPos != newPos : data; // reader always consumes bytes
+                        } catch (Throwable e) {
+                            receiveScheduler.stop();
+                            messageConsumer.onError(e);
+                        }
+                        continue;
+                    }
+                    break;
+                }
+                switch (state) {
+                    case WAITING:
+                        return;
+                    case UNREGISTERED:
+                        try {
+                            state = WAITING;
+                            channel.registerEvent(readEvent);
+                        } catch (Throwable e) {
+                            receiveScheduler.stop();
+                            messageConsumer.onError(e);
+                        }
+                        return;
+                    case AVAILABLE:
+                        try {
+                            data = channel.read();
+                        } catch (Throwable e) {
+                            receiveScheduler.stop();
+                            messageConsumer.onError(e);
+                            return;
+                        }
+                        if (data == null) { // EOF
+                            receiveScheduler.stop();
+                            messageConsumer.onComplete();
+                            return;
+                        } else if (!data.hasRemaining()) {
+                            // No data at the moment Pretty much a "goto",
+                            // reusing the existing code path for registration
+                            state = UNREGISTERED;
+                        }
+                        continue;
+                    default:
+                        throw new InternalError(String.valueOf(state));
+                }
+            }
+        }
+    }
+
+    /*
+     * Permanently stops reading from the channel and delivering messages
+     * regardless of the current demand and data availability.
+     */
+    @Override
+    public void closeInput() throws IOException {
+        synchronized (lock) {
+            if (!inputClosed) {
+                inputClosed = true;
+                try {
+                    receiveScheduler.stop();
+                    channel.shutdownInput();
+                } finally {
+                    if (outputClosed) {
+                        channel.close();
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void closeOutput() throws IOException {
+        synchronized (lock) {
+            if (!outputClosed) {
+                outputClosed = true;
+                try {
+                    channel.shutdownOutput();
+                } finally {
+                    if (inputClosed) {
+                        channel.close();
+                    }
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/UTF8AccumulatingDecoder.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,98 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import jdk.internal.net.http.common.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static jdk.internal.net.http.common.Utils.EMPTY_BYTEBUFFER;
+
+final class UTF8AccumulatingDecoder {
+
+    private final CharsetDecoder decoder = UTF_8.newDecoder();
+
+    {
+        decoder.onMalformedInput(CodingErrorAction.REPORT);
+        decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+    }
+
+    private ByteBuffer leftovers = EMPTY_BYTEBUFFER;
+
+    CharBuffer decode(ByteBuffer in, boolean endOfInput)
+            throws CharacterCodingException
+    {
+        ByteBuffer b;
+        int rem = leftovers.remaining();
+        if (rem != 0) {
+            // We won't need this wasteful allocation & copying when JDK-8155222
+            // has been resolved
+            b = ByteBuffer.allocate(rem + in.remaining());
+            b.put(leftovers).put(in).flip();
+        } else {
+            b = in;
+        }
+        CharBuffer out = CharBuffer.allocate(b.remaining());
+        CoderResult r = decoder.decode(b, out, endOfInput);
+        if (r.isError()) {
+            r.throwException();
+        }
+        if (b.hasRemaining()) {
+            leftovers = ByteBuffer.allocate(b.remaining()).put(b).flip();
+        } else {
+            leftovers = EMPTY_BYTEBUFFER;
+        }
+        // Since it's UTF-8, the assumption is leftovers.remaining() < 4
+        // (i.e. small). Otherwise a shared buffer should be used
+        if (!(leftovers.remaining() < 4)) {
+            Log.logError("The size of decoding leftovers is greater than expected: {0}",
+                         leftovers.remaining());
+        }
+        b.position(b.limit()); // As if we always read to the end
+        // Decoder promises that in the case of endOfInput == true:
+        // "...any remaining undecoded input will be treated as being
+        // malformed"
+        assert !(endOfInput && leftovers.hasRemaining()) : endOfInput + ", " + leftovers;
+        if (endOfInput) {
+            r = decoder.flush(out);
+            decoder.reset();
+            if (r.isOverflow()) {
+                // FIXME: for now I know flush() does nothing. But the
+                // implementation of UTF8 decoder might change. And if now
+                // flush() is a no-op, it is not guaranteed to remain so in
+                // the future
+                throw new InternalError("Not yet implemented");
+            }
+        }
+        return out.flip();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketImpl.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,533 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.WebSocket;
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.websocket.OpeningHandshake.Result;
+
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.net.ProtocolException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+import static java.util.Objects.requireNonNull;
+import static jdk.internal.net.http.common.MinimalFuture.failedFuture;
+import static jdk.internal.net.http.websocket.StatusCodes.CLOSED_ABNORMALLY;
+import static jdk.internal.net.http.websocket.StatusCodes.NO_STATUS_CODE;
+import static jdk.internal.net.http.websocket.StatusCodes.isLegalToSendFromClient;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.BINARY;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.CLOSE;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.ERROR;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.IDLE;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.OPEN;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.PING;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.PONG;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.TEXT;
+import static jdk.internal.net.http.websocket.WebSocketImpl.State.WAITING;
+
+/*
+ * A WebSocket client.
+ */
+public final class WebSocketImpl implements WebSocket {
+
+    enum State {
+        OPEN,
+        IDLE,
+        WAITING,
+        TEXT,
+        BINARY,
+        PING,
+        PONG,
+        CLOSE,
+        ERROR;
+    }
+
+    private volatile boolean inputClosed;
+    private volatile boolean outputClosed;
+
+    private final AtomicReference<State> state = new AtomicReference<>(OPEN);
+
+    /* Components of calls to Listener's methods */
+    private MessagePart part;
+    private ByteBuffer binaryData;
+    private CharSequence text;
+    private int statusCode;
+    private String reason;
+    private final AtomicReference<Throwable> error = new AtomicReference<>();
+
+    private final URI uri;
+    private final String subprotocol;
+    private final Listener listener;
+
+    private final AtomicBoolean outstandingSend = new AtomicBoolean();
+    private final Transport<WebSocket> transport;
+    private final SequentialScheduler receiveScheduler = new SequentialScheduler(new ReceiveTask());
+    private final Demand demand = new Demand();
+
+    public static CompletableFuture<WebSocket> newInstanceAsync(BuilderImpl b) {
+        Function<Result, WebSocket> newWebSocket = r -> {
+            WebSocket ws = newInstance(b.getUri(),
+                                       r.subprotocol,
+                                       b.getListener(),
+                                       r.transport);
+            // Make sure we don't release the builder until this lambda
+            // has been executed. The builder has a strong reference to
+            // the HttpClientFacade, and we want to keep that live until
+            // after the raw channel is created and passed to WebSocketImpl.
+            Reference.reachabilityFence(b);
+            return ws;
+        };
+        OpeningHandshake h;
+        try {
+            h = new OpeningHandshake(b);
+        } catch (Throwable e) {
+            return failedFuture(e);
+        }
+        return h.send().thenApply(newWebSocket);
+    }
+
+    /* Exposed for testing purposes */
+    static WebSocketImpl newInstance(URI uri,
+                                     String subprotocol,
+                                     Listener listener,
+                                     TransportFactory transport) {
+        WebSocketImpl ws = new WebSocketImpl(uri, subprotocol, listener, transport);
+        // This initialisation is outside of the constructor for the sake of
+        // safe publication of WebSocketImpl.this
+        ws.signalOpen();
+        return ws;
+    }
+
+    private WebSocketImpl(URI uri,
+                          String subprotocol,
+                          Listener listener,
+                          TransportFactory transportFactory) {
+        this.uri = requireNonNull(uri);
+        this.subprotocol = requireNonNull(subprotocol);
+        this.listener = requireNonNull(listener);
+        this.transport = transportFactory.createTransport(
+                () -> WebSocketImpl.this, // What about escape of WebSocketImpl.this?
+                new SignallingMessageConsumer());
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendText(CharSequence message,
+                                                 boolean isLast) {
+        Objects.requireNonNull(message);
+        if (!outstandingSend.compareAndSet(false, true)) {
+            return failedFuture(new IllegalStateException("Send pending"));
+        }
+        CompletableFuture<WebSocket> cf = transport.sendText(message, isLast);
+        return cf.whenComplete((r, e) -> outstandingSend.set(false));
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendBinary(ByteBuffer message,
+                                                   boolean isLast) {
+        Objects.requireNonNull(message);
+        if (!outstandingSend.compareAndSet(false, true)) {
+            return failedFuture(new IllegalStateException("Send pending"));
+        }
+        CompletableFuture<WebSocket> cf = transport.sendBinary(message, isLast);
+        // Optimize?
+        //     if (cf.isDone()) {
+        //         outstandingSend.set(false);
+        //     } else {
+        //         cf.whenComplete((r, e) -> outstandingSend.set(false));
+        //     }
+        return cf.whenComplete((r, e) -> outstandingSend.set(false));
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendPing(ByteBuffer message) {
+        return transport.sendPing(message);
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendPong(ByteBuffer message) {
+        return transport.sendPong(message);
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendClose(int statusCode, String reason) {
+        Objects.requireNonNull(reason);
+        if (!isLegalToSendFromClient(statusCode)) {
+            return failedFuture(new IllegalArgumentException("statusCode"));
+        }
+        return sendClose0(statusCode, reason);
+    }
+
+    /*
+     * Sends a Close message, then shuts down the output since no more
+     * messages are expected to be sent after this.
+     */
+    private CompletableFuture<WebSocket> sendClose0(int statusCode, String reason ) {
+        outputClosed = true;
+        return transport.sendClose(statusCode, reason)
+                .whenComplete((result, error) -> {
+                    try {
+                        transport.closeOutput();
+                    } catch (IOException e) {
+                        Log.logError(e);
+                    }
+                    Throwable cause = Utils.getCompletionCause(error);
+                    if (cause instanceof TimeoutException) {
+                        try {
+                            transport.closeInput();
+                        } catch (IOException e) {
+                            Log.logError(e);
+                        }
+                    }
+                });
+    }
+
+    @Override
+    public void request(long n) {
+        if (demand.increase(n)) {
+            receiveScheduler.runOrSchedule();
+        }
+    }
+
+    @Override
+    public String getSubprotocol() {
+        return subprotocol;
+    }
+
+    @Override
+    public boolean isOutputClosed() {
+        return outputClosed;
+    }
+
+    @Override
+    public boolean isInputClosed() {
+        return inputClosed;
+    }
+
+    @Override
+    public void abort() {
+        inputClosed = true;
+        outputClosed = true;
+        receiveScheduler.stop();
+        close();
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()
+                + "[uri=" + uri
+                + (!subprotocol.isEmpty() ? ", subprotocol=" + subprotocol : "")
+                + "]";
+    }
+
+    /*
+     * The assumptions about order is as follows:
+     *
+     *     - state is never changed more than twice inside the `run` method:
+     *       x --(1)--> IDLE --(2)--> y (otherwise we're loosing events, or
+     *       overwriting parts of messages creating a mess since there's no
+     *       queueing)
+     *     - OPEN is always the first state
+     *     - no messages are requested/delivered before onOpen is called (this
+     *       is implemented by making WebSocket instance accessible first in
+     *       onOpen)
+     *     - after the state has been observed as CLOSE/ERROR, the scheduler
+     *       is stopped
+     */
+    private class ReceiveTask extends SequentialScheduler.CompleteRestartableTask {
+
+        // Transport only asked here and nowhere else because we must make sure
+        // onOpen is invoked first and no messages become pending before onOpen
+        // finishes
+
+        @Override
+        public void run() {
+            while (true) {
+                State s = state.get();
+                try {
+                    switch (s) {
+                        case OPEN:
+                            processOpen();
+                            tryChangeState(OPEN, IDLE);
+                            break;
+                        case TEXT:
+                            processText();
+                            tryChangeState(TEXT, IDLE);
+                            break;
+                        case BINARY:
+                            processBinary();
+                            tryChangeState(BINARY, IDLE);
+                            break;
+                        case PING:
+                            processPing();
+                            tryChangeState(PING, IDLE);
+                            break;
+                        case PONG:
+                            processPong();
+                            tryChangeState(PONG, IDLE);
+                            break;
+                        case CLOSE:
+                            processClose();
+                            return;
+                        case ERROR:
+                            processError();
+                            return;
+                        case IDLE:
+                            if (demand.tryDecrement()
+                                    && tryChangeState(IDLE, WAITING)) {
+                                transport.request(1);
+                            }
+                            return;
+                        case WAITING:
+                            // For debugging spurious signalling: when there was a
+                            // signal, but apparently nothing has changed
+                            return;
+                        default:
+                            throw new InternalError(String.valueOf(s));
+                    }
+                } catch (Throwable t) {
+                    signalError(t);
+                }
+            }
+        }
+
+        private void processError() throws IOException {
+            transport.closeInput();
+            receiveScheduler.stop();
+            Throwable err = error.get();
+            if (err instanceof FailWebSocketException) {
+                int code1 = ((FailWebSocketException) err).getStatusCode();
+                err = new ProtocolException().initCause(err);
+                sendClose0(code1, "")
+                        .whenComplete(
+                                (r, e) -> {
+                                    if (e != null) {
+                                        Log.logError(e);
+                                    }
+                                });
+            }
+            listener.onError(WebSocketImpl.this, err);
+        }
+
+        private void processClose() throws IOException {
+            transport.closeInput();
+            receiveScheduler.stop();
+            CompletionStage<?> readyToClose;
+            readyToClose = listener.onClose(WebSocketImpl.this, statusCode, reason);
+            if (readyToClose == null) {
+                readyToClose = MinimalFuture.completedFuture(null);
+            }
+            int code;
+            if (statusCode == NO_STATUS_CODE || statusCode == CLOSED_ABNORMALLY) {
+                code = NORMAL_CLOSURE;
+            } else {
+                code = statusCode;
+            }
+            readyToClose.whenComplete((r, e) -> {
+                sendClose0(code, "")
+                        .whenComplete((r1, e1) -> {
+                            if (e1 != null) {
+                                Log.logError(e1);
+                            }
+                        });
+            });
+        }
+
+        private void processPong() {
+            listener.onPong(WebSocketImpl.this, binaryData);
+        }
+
+        private void processPing() {
+            // Let's make a full copy of this tiny data. What we want here
+            // is to rule out a possibility the shared data we send might be
+            // corrupted by processing in the listener.
+            ByteBuffer slice = binaryData.slice();
+            ByteBuffer copy = ByteBuffer.allocate(binaryData.remaining())
+                    .put(binaryData)
+                    .flip();
+            // Non-exclusive send;
+            CompletableFuture<WebSocket> pongSent = transport.sendPong(copy);
+            pongSent.whenComplete(
+                    (r, e) -> {
+                        if (e != null) {
+                            signalError(Utils.getCompletionCause(e));
+                        }
+                    }
+            );
+            listener.onPing(WebSocketImpl.this, slice);
+        }
+
+        private void processBinary() {
+            listener.onBinary(WebSocketImpl.this, binaryData, part);
+        }
+
+        private void processText() {
+            listener.onText(WebSocketImpl.this, text, part);
+        }
+
+        private void processOpen() {
+            listener.onOpen(WebSocketImpl.this);
+        }
+    }
+
+    private void signalOpen() {
+        receiveScheduler.runOrSchedule();
+    }
+
+    private void signalError(Throwable error) {
+        inputClosed = true;
+        outputClosed = true;
+        if (!this.error.compareAndSet(null, error) || !trySetState(ERROR)) {
+            Log.logError(error);
+        } else {
+            close();
+        }
+    }
+
+    private void close() {
+        try {
+            try {
+                transport.closeInput();
+            } finally {
+                transport.closeOutput();
+            }
+        } catch (Throwable t) {
+            Log.logError(t);
+        }
+    }
+
+    /*
+     * Signals a Close event (might not correspond to anything happened on the
+     * channel, i.e. might be synthetic).
+     */
+    private void signalClose(int statusCode, String reason) {
+        inputClosed = true;
+        this.statusCode = statusCode;
+        this.reason = reason;
+        if (!trySetState(CLOSE)) {
+            Log.logTrace("Close: {0}, ''{1}''", statusCode, reason);
+        } else {
+            try {
+                transport.closeInput();
+            } catch (Throwable t) {
+                Log.logError(t);
+            }
+        }
+    }
+
+    private class SignallingMessageConsumer implements MessageStreamConsumer {
+
+        @Override
+        public void onText(CharSequence data, MessagePart part) {
+            transport.acknowledgeReception();
+            text = data;
+            WebSocketImpl.this.part = part;
+            tryChangeState(WAITING, TEXT);
+        }
+
+        @Override
+        public void onBinary(ByteBuffer data, MessagePart part) {
+            transport.acknowledgeReception();
+            binaryData = data;
+            WebSocketImpl.this.part = part;
+            tryChangeState(WAITING, BINARY);
+        }
+
+        @Override
+        public void onPing(ByteBuffer data) {
+            transport.acknowledgeReception();
+            binaryData = data;
+            tryChangeState(WAITING, PING);
+        }
+
+        @Override
+        public void onPong(ByteBuffer data) {
+            transport.acknowledgeReception();
+            binaryData = data;
+            tryChangeState(WAITING, PONG);
+        }
+
+        @Override
+        public void onClose(int statusCode, CharSequence reason) {
+            transport.acknowledgeReception();
+            signalClose(statusCode, reason.toString());
+        }
+
+        @Override
+        public void onComplete() {
+            transport.acknowledgeReception();
+            signalClose(CLOSED_ABNORMALLY, "");
+        }
+
+        @Override
+        public void onError(Throwable error) {
+            signalError(error);
+        }
+    }
+
+    private boolean trySetState(State newState) {
+        while (true) {
+            State currentState = state.get();
+            if (currentState == ERROR || currentState == CLOSE) {
+                return false;
+            } else if (state.compareAndSet(currentState, newState)) {
+                receiveScheduler.runOrSchedule();
+                return true;
+            }
+        }
+    }
+
+    private boolean tryChangeState(State expectedState, State newState) {
+        State witness = state.compareAndExchange(expectedState, newState);
+        if (witness == expectedState) {
+            receiveScheduler.runOrSchedule();
+            return true;
+        }
+        // This should be the only reason for inability to change the state from
+        // IDLE to WAITING: the state has changed to terminal
+        if (witness != ERROR && witness != CLOSE) {
+            throw new InternalError();
+        }
+        return false;
+    }
+
+    /* Exposed for testing purposes */
+    protected final Transport<WebSocket> transport() {
+        return transport;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketRequest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,51 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.Proxy;
+
+/*
+ * https://tools.ietf.org/html/rfc6455#section-4.1
+ */
+public interface WebSocketRequest {
+
+    /*
+     * If set to `true` and a proxy is used, instructs the implementation that
+     * a TCP tunnel must be opened.
+     */
+    void isWebSocket(boolean flag);
+
+    /*
+     * Needed for setting "Connection" and "Upgrade" headers as required by the
+     * WebSocket specification.
+     */
+    void setSystemHeader(String name, String value);
+
+    /*
+     * Sets the proxy for this request.
+     */
+    void setProxy(Proxy proxy);
+}
--- a/test/jdk/java/net/httpclient/ConcurrentResponses.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/ConcurrentResponses.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @summary Buffers given to response body subscribers should not contain
  *          unprocessed HTTP data
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary http2/server
--- a/test/jdk/java/net/httpclient/CustomRequestPublisher.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/CustomRequestPublisher.java	Wed Feb 07 21:45:37 2018 +0000
@@ -25,9 +25,9 @@
  * @test
  * @summary Checks correct handling of Publishers that call onComplete without demand
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary http2/server
--- a/test/jdk/java/net/httpclient/CustomResponseSubscriber.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/CustomResponseSubscriber.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm CustomResponseSubscriber
  */
 
--- a/test/jdk/java/net/httpclient/DigestEchoClient.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/DigestEchoClient.java	Wed Feb 07 21:45:37 2018 +0000
@@ -63,9 +63,9 @@
  * @bug 8087112
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters DigestEchoServer DigestEchoClient
- * @modules java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          java.base/sun.net.www.http
  *          java.base/sun.net.www
--- a/test/jdk/java/net/httpclient/DigestEchoClientSSL.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/DigestEchoClientSSL.java	Wed Feb 07 21:45:37 2018 +0000
@@ -28,9 +28,9 @@
  *          headers directly when connecting with a server over SSL.
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient DigestEchoClientSSL
- * @modules java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          java.base/sun.net.www.http
  *          java.base/sun.net.www
--- a/test/jdk/java/net/httpclient/FlowAdapterPublisherTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/FlowAdapterPublisherTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -61,9 +61,9 @@
  * @test
  * @summary Basic tests for Flow adapter Publishers
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary http2/server
--- a/test/jdk/java/net/httpclient/FlowAdapterSubscriberTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/FlowAdapterSubscriberTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -64,9 +64,9 @@
  * @test
  * @summary Basic tests for Flow adapter Subscribers
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary http2/server
--- a/test/jdk/java/net/httpclient/HttpServerAdapters.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/HttpServerAdapters.java	Wed Feb 07 21:45:37 2018 +0000
@@ -28,7 +28,7 @@
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
 import java.net.http.HttpClient.Version;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
--- a/test/jdk/java/net/httpclient/ImmutableFlowItems.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/ImmutableFlowItems.java	Wed Feb 07 21:45:37 2018 +0000
@@ -28,9 +28,9 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm ImmutableFlowItems
  */
 
--- a/test/jdk/java/net/httpclient/LineBodyHandlerTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/LineBodyHandlerTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -75,9 +75,9 @@
  *          the BodyHandlers returned by BodyHandler::fromLineSubscriber
  *          and BodyHandler::asLines
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary http2/server
--- a/test/jdk/java/net/httpclient/MappedResponseSubscriber.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/MappedResponseSubscriber.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm MappedResponseSubscriber
  */
 
--- a/test/jdk/java/net/httpclient/NoBodyPartOne.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/NoBodyPartOne.java	Wed Feb 07 21:45:37 2018 +0000
@@ -28,9 +28,9 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=all NoBodyPartOne
  */
 
--- a/test/jdk/java/net/httpclient/NoBodyPartTwo.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/NoBodyPartTwo.java	Wed Feb 07 21:45:37 2018 +0000
@@ -28,9 +28,9 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=all NoBodyPartTwo
  */
 
--- a/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java	Wed Feb 07 21:45:37 2018 +0000
@@ -30,9 +30,9 @@
  * @bug 8087112
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient ProxyAuthDisabledSchemes
- * @modules java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          java.base/sun.net.www.http
  *          java.base/sun.net.www
--- a/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.java	Wed Feb 07 21:45:37 2018 +0000
@@ -30,9 +30,9 @@
  *          net properties.
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient ProxyAuthDisabledSchemesSSL
- * @modules java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.logging
  *          java.base/sun.net.www.http
  *          java.base/sun.net.www
--- a/test/jdk/java/net/httpclient/SmallTimeout.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/SmallTimeout.java	Wed Feb 07 21:45:37 2018 +0000
@@ -40,7 +40,7 @@
  * @test
  * @bug 8178147
  * @summary Ensures that small timeouts do not cause hangs due to race conditions
- * @run main/othervm -Djava.net.http.internal.common.DEBUG=true SmallTimeout
+ * @run main/othervm -Djdk.internal.net.http.common.DEBUG=true SmallTimeout
  */
 
 // To enable logging use. Not enabled by default as it changes the dynamics
--- a/test/jdk/java/net/httpclient/http2/BasicTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/BasicTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm -Djdk.httpclient.HttpClient.log=ssl,requests,responses,errors BasicTest
  */
 
--- a/test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -25,9 +25,9 @@
  * @test
  * @summary Test for CONTINUATION frame handling
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @library /lib/testlibrary server
  * @build Http2TestServer
  * @build jdk.testlibrary.SimpleSSLContext
@@ -47,11 +47,11 @@
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.frame.ContinuationFrame;
-import java.net.http.internal.frame.HeaderFrame;
-import java.net.http.internal.frame.HeadersFrame;
-import java.net.http.internal.frame.Http2Frame;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.frame.ContinuationFrame;
+import jdk.internal.net.http.frame.HeaderFrame;
+import jdk.internal.net.http.frame.HeadersFrame;
+import jdk.internal.net.http.frame.Http2Frame;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
--- a/test/jdk/java/net/httpclient/http2/ErrorTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/ErrorTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  *          java.security.jgss
  * @run testng/othervm/timeout=60 -Djavax.net.debug=ssl -Djdk.httpclient.HttpClient.log=all ErrorTest
  * @summary check exception thrown when bad TLS parameters selected
--- a/test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm -Djdk.httpclient.HttpClient.log=ssl,requests,responses,errors FixedThreadPoolTest
  */
 
--- a/test/jdk/java/net/httpclient/http2/HpackBinaryTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/HpackBinaryTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=java.net.http java/net/http/internal/hpack/SpecHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/TestHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm java.net.http/java.net.http.internal.hpack.BinaryPrimitivesTest
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/SpecHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/TestHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/BuffersTestingKit.java
+ * @run testng/othervm java.net.http/jdk.internal.net.http.hpack.BinaryPrimitivesTest
  */
 public class HpackBinaryTestDriver { }
--- a/test/jdk/java/net/httpclient/http2/HpackCircularBufferDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/HpackCircularBufferDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=java.net.http java/net/http/internal/hpack/SpecHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/TestHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm java.net.http/java.net.http.internal.hpack.CircularBufferTest
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/SpecHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/TestHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/BuffersTestingKit.java
+ * @run testng/othervm java.net.http/jdk.internal.net.http.hpack.CircularBufferTest
  */
 public class HpackCircularBufferDriver { }
--- a/test/jdk/java/net/httpclient/http2/HpackDecoderDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/HpackDecoderDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=java.net.http java/net/http/internal/hpack/SpecHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/TestHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm java.net.http/java.net.http.internal.hpack.DecoderTest
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/SpecHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/TestHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/BuffersTestingKit.java
+ * @run testng/othervm java.net.http/jdk.internal.net.http.hpack.DecoderTest
  */
 public class HpackDecoderDriver { }
--- a/test/jdk/java/net/httpclient/http2/HpackEncoderDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/HpackEncoderDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=java.net.http java/net/http/internal/hpack/SpecHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/TestHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm java.net.http/java.net.http.internal.hpack.EncoderTest
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/SpecHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/TestHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/BuffersTestingKit.java
+ * @run testng/othervm java.net.http/jdk.internal.net.http.hpack.EncoderTest
  */
 public class HpackEncoderDriver { }
--- a/test/jdk/java/net/httpclient/http2/HpackHeaderTableDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/HpackHeaderTableDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,12 +24,12 @@
 /*
  * @test
  * @bug 8153353
- * @modules java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  *          jdk.localedata
  * @key randomness
- * @compile/module=java.net.http java/net/http/internal/hpack/SpecHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/TestHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm java.net.http/java.net.http.internal.hpack.HeaderTableTest
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/SpecHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/TestHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/BuffersTestingKit.java
+ * @run testng/othervm java.net.http/jdk.internal.net.http.hpack.HeaderTableTest
  */
 public class HpackHeaderTableDriver { }
--- a/test/jdk/java/net/httpclient/http2/HpackHuffmanDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/HpackHuffmanDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=java.net.http java/net/http/internal/hpack/SpecHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/TestHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm java.net.http/java.net.http.internal.hpack.HuffmanTest
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/SpecHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/TestHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/BuffersTestingKit.java
+ * @run testng/othervm java.net.http/jdk.internal.net.http.hpack.HuffmanTest
  */
 public class HpackHuffmanDriver { }
--- a/test/jdk/java/net/httpclient/http2/HpackTestHelper.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/HpackTestHelper.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules java.net.http/java.net.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=java.net.http java/net/http/internal/hpack/SpecHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/TestHelper.java
- * @compile/module=java.net.http java/net/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm java.net.http/java.net.http.internal.hpack.TestHelper
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/SpecHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/TestHelper.java
+ * @compile/module=java.net.http jdk/internal/net/http/hpack/BuffersTestingKit.java
+ * @run testng/othervm java.net.http/jdk.internal.net.http.hpack.TestHelper
  */
 public class HpackTestHelperDriver { }
--- a/test/jdk/java/net/httpclient/http2/ImplicitPushCancel.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/ImplicitPushCancel.java	Wed Feb 07 21:45:37 2018 +0000
@@ -26,9 +26,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=errors,requests,responses,trace ImplicitPushCancel
  */
 
@@ -48,7 +48,7 @@
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandler;
 import java.net.http.HttpResponse.PushPromiseHandler;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
--- a/test/jdk/java/net/httpclient/http2/ProxyTest2.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/ProxyTest2.java	Wed Feb 07 21:45:37 2018 +0000
@@ -63,9 +63,9 @@
  * @modules java.net.http
  * @library /lib/testlibrary server
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @build jdk.testlibrary.SimpleSSLContext ProxyTest2
  * @run main/othervm ProxyTest2
  * @author danielfuchs
--- a/test/jdk/java/net/httpclient/http2/RedirectTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/RedirectTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm -Djdk.httpclient.HttpClient.log=frames,ssl,requests,responses,errors RedirectTest
  */
 
--- a/test/jdk/java/net/httpclient/http2/ServerPush.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/ServerPush.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm -Djdk.httpclient.HttpClient.log=errors,requests,responses ServerPush
  */
 
--- a/test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java	Wed Feb 07 21:45:37 2018 +0000
@@ -26,9 +26,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run testng/othervm
  *       -Djdk.internal.httpclient.debug=true
  *       -Djdk.httpclient.HttpClient.log=errors,requests,responses
@@ -45,7 +45,7 @@
 import java.net.http.HttpResponse.BodySubscriber;
 import java.util.*;
 import java.util.concurrent.*;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 import org.testng.annotations.Test;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.testng.Assert.assertEquals;
--- a/test/jdk/java/net/httpclient/http2/TLSConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/TLSConnection.java	Wed Feb 07 21:45:37 2018 +0000
@@ -41,9 +41,9 @@
  * @library server
  * @summary Checks that SSL parameters can be set for HTTP/2 connection
  * @modules java.base/sun.net.www.http
- *          java.net.http/java.net.http.internal.common
- *          java.net.http/java.net.http.internal.frame
- *          java.net.http/java.net.http.internal.hpack
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
  * @run main/othervm
  *       -Djdk.internal.httpclient.debug=true
  *       -Djdk.httpclient.HttpClient.log=all
@@ -251,5 +251,4 @@
             return true;
         }
     }
-
-}
\ No newline at end of file
+}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/BinaryPrimitivesTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,369 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.fail;
-import static java.net.http.internal.hpack.BuffersTestingKit.*;
-import static java.net.http.internal.hpack.TestHelper.newRandom;
-
-//
-// Some of the tests below overlap in what they test. This allows to diagnose
-// bugs quicker and with less pain by simply ruling out common working bits.
-//
-public final class BinaryPrimitivesTest {
-
-    private final Random rnd = newRandom();
-
-    @Test
-    public void integerRead1() {
-        verifyRead(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
-    }
-
-    @Test
-    public void integerRead2() {
-        verifyRead(bytes(0b00001010), 10, 5);
-    }
-
-    @Test
-    public void integerRead3() {
-        verifyRead(bytes(0b00101010), 42, 8);
-    }
-
-    @Test
-    public void integerWrite1() {
-        verifyWrite(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
-    }
-
-    @Test
-    public void integerWrite2() {
-        verifyWrite(bytes(0b00001010), 10, 5);
-    }
-
-    @Test
-    public void integerWrite3() {
-        verifyWrite(bytes(0b00101010), 42, 8);
-    }
-
-    //
-    // Since readInteger(x) is the inverse of writeInteger(x), thus:
-    //
-    // for all x: readInteger(writeInteger(x)) == x
-    //
-    @Test
-    public void integerIdentity() throws IOException {
-        final int MAX_VALUE = 1 << 22;
-        int totalCases = 0;
-        int maxFilling = 0;
-        IntegerReader r = new IntegerReader();
-        IntegerWriter w = new IntegerWriter();
-        ByteBuffer buf = ByteBuffer.allocate(8);
-        for (int N = 1; N < 9; N++) {
-            for (int expected = 0; expected <= MAX_VALUE; expected++) {
-                w.reset().configure(expected, N, 1).write(buf);
-                buf.flip();
-                totalCases++;
-                maxFilling = Math.max(maxFilling, buf.remaining());
-                r.reset().configure(N).read(buf);
-                assertEquals(r.get(), expected);
-                buf.clear();
-            }
-        }
-        System.out.printf("totalCases: %,d, maxFilling: %,d, maxValue: %,d%n",
-                totalCases, maxFilling, MAX_VALUE);
-    }
-
-    @Test
-    public void integerReadChunked() {
-        final int NUM_TESTS = 1024;
-        IntegerReader r = new IntegerReader();
-        ByteBuffer bb = ByteBuffer.allocate(8);
-        IntegerWriter w = new IntegerWriter();
-        for (int i = 0; i < NUM_TESTS; i++) {
-            final int N = 1 + rnd.nextInt(8);
-            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
-            w.reset().configure(expected, N, rnd.nextInt()).write(bb);
-            bb.flip();
-
-            forEachSplit(bb,
-                    (buffers) -> {
-                        Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
-                        r.configure(N);
-                        for (ByteBuffer b : buf) {
-                            try {
-                                r.read(b);
-                            } catch (IOException e) {
-                                throw new UncheckedIOException(e);
-                            }
-                        }
-                        assertEquals(r.get(), expected);
-                        r.reset();
-                    });
-            bb.clear();
-        }
-    }
-
-    // FIXME: use maxValue in the test
-
-    @Test
-    // FIXME: tune values for better coverage
-    public void integerWriteChunked() {
-        ByteBuffer bb = ByteBuffer.allocate(6);
-        IntegerWriter w = new IntegerWriter();
-        IntegerReader r = new IntegerReader();
-        for (int i = 0; i < 1024; i++) { // number of tests
-            final int N = 1 + rnd.nextInt(8);
-            final int payload = rnd.nextInt(255);
-            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
-
-            forEachSplit(bb,
-                    (buffers) -> {
-                        List<ByteBuffer> buf = new ArrayList<>();
-                        relocateBuffers(injectEmptyBuffers(buffers)).forEach(buf::add);
-                        boolean written = false;
-                        w.configure(expected, N, payload); // TODO: test for payload it can be read after written
-                        for (ByteBuffer b : buf) {
-                            int pos = b.position();
-                            written = w.write(b);
-                            b.position(pos);
-                        }
-                        if (!written) {
-                            fail("please increase bb size");
-                        }
-                        try {
-                            r.configure(N).read(concat(buf));
-                        } catch (IOException e) {
-                            throw new UncheckedIOException(e);
-                        }
-                        // TODO: check payload here
-                        assertEquals(r.get(), expected);
-                        w.reset();
-                        r.reset();
-                        bb.clear();
-                    });
-        }
-    }
-
-
-    //
-    // Since readString(x) is the inverse of writeString(x), thus:
-    //
-    // for all x: readString(writeString(x)) == x
-    //
-    @Test
-    public void stringIdentity() throws IOException {
-        final int MAX_STRING_LENGTH = 4096;
-        ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6); // it takes 6 bytes to encode string length of Integer.MAX_VALUE
-        CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
-        StringReader reader = new StringReader();
-        StringWriter writer = new StringWriter();
-        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
-            for (int i = 0; i < 64; i++) {
-                // not so much "test in isolation", I know... we're testing .reset() as well
-                bytes.clear();
-                chars.clear();
-
-                byte[] b = new byte[len];
-                rnd.nextBytes(b);
-
-                String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
-
-                boolean written = writer
-                        .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
-                        .write(bytes);
-
-                if (!written) {
-                    fail("please increase 'bytes' size");
-                }
-                bytes.flip();
-                reader.read(bytes, chars);
-                chars.flip();
-                assertEquals(chars.toString(), expected);
-                reader.reset();
-                writer.reset();
-            }
-        }
-    }
-
-//    @Test
-//    public void huffmanStringWriteChunked() {
-//        fail();
-//    }
-//
-//    @Test
-//    public void huffmanStringReadChunked() {
-//        fail();
-//    }
-
-    @Test
-    public void stringWriteChunked() {
-        final int MAX_STRING_LENGTH = 8;
-        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
-        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
-        final StringReader reader = new StringReader();
-        final StringWriter writer = new StringWriter();
-        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
-
-            byte[] b = new byte[len];
-            rnd.nextBytes(b);
-
-            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
-
-            forEachSplit(bytes, (buffers) -> {
-                writer.configure(expected, 0, expected.length(), false);
-                boolean written = false;
-                for (ByteBuffer buf : buffers) {
-                    int p0 = buf.position();
-                    written = writer.write(buf);
-                    buf.position(p0);
-                }
-                if (!written) {
-                    fail("please increase 'bytes' size");
-                }
-                try {
-                    reader.read(concat(buffers), chars);
-                } catch (IOException e) {
-                    throw new UncheckedIOException(e);
-                }
-                chars.flip();
-                assertEquals(chars.toString(), expected);
-                reader.reset();
-                writer.reset();
-                chars.clear();
-                bytes.clear();
-            });
-        }
-    }
-
-    @Test
-    public void stringReadChunked() {
-        final int MAX_STRING_LENGTH = 16;
-        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
-        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
-        final StringReader reader = new StringReader();
-        final StringWriter writer = new StringWriter();
-        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
-
-            byte[] b = new byte[len];
-            rnd.nextBytes(b);
-
-            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
-
-            boolean written = writer
-                    .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
-                    .write(bytes);
-            writer.reset();
-
-            if (!written) {
-                fail("please increase 'bytes' size");
-            }
-            bytes.flip();
-
-            forEachSplit(bytes, (buffers) -> {
-                for (ByteBuffer buf : buffers) {
-                    int p0 = buf.position();
-                    try {
-                        reader.read(buf, chars);
-                    } catch (IOException e) {
-                        throw new UncheckedIOException(e);
-                    }
-                    buf.position(p0);
-                }
-                chars.flip();
-                assertEquals(chars.toString(), expected);
-                reader.reset();
-                chars.clear();
-            });
-
-            bytes.clear();
-        }
-    }
-
-//    @Test
-//    public void test_Huffman_String_Identity() {
-//        StringWriter writer = new StringWriter();
-//        StringReader reader = new StringReader();
-//        // 256 * 8 gives 2048 bits in case of plain 8 bit coding
-//        // 256 * 30 gives you 7680 bits or 960 bytes in case of almost
-//        //          improbable event of 256 30 bits symbols in a row
-//        ByteBuffer binary = ByteBuffer.allocate(960);
-//        CharBuffer text = CharBuffer.allocate(960 / 5); // 5 = minimum code length
-//        for (int len = 0; len < 128; len++) {
-//            for (int i = 0; i < 256; i++) {
-//                // not so much "test in isolation", I know...
-//                binary.clear();
-//
-//                byte[] bytes = new byte[len];
-//                rnd.nextBytes(bytes);
-//
-//                String s = new String(bytes, StandardCharsets.ISO_8859_1);
-//
-//                writer.write(CharBuffer.wrap(s), binary, true);
-//                binary.flip();
-//                reader.read(binary, text);
-//                text.flip();
-//                assertEquals(text.toString(), s);
-//            }
-//        }
-//    }
-
-    // TODO: atomic failures: e.g. readonly/overflow
-
-    private static byte[] bytes(int... data) {
-        byte[] bytes = new byte[data.length];
-        for (int i = 0; i < data.length; i++) {
-            bytes[i] = (byte) data[i];
-        }
-        return bytes;
-    }
-
-    private static void verifyRead(byte[] data, int expected, int N) {
-        ByteBuffer buf = ByteBuffer.wrap(data, 0, data.length);
-        IntegerReader reader = new IntegerReader();
-        try {
-            reader.configure(N).read(buf);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        assertEquals(expected, reader.get());
-    }
-
-    private void verifyWrite(byte[] expected, int data, int N) {
-        IntegerWriter w = new IntegerWriter();
-        ByteBuffer buf = ByteBuffer.allocate(2 * expected.length);
-        w.configure(data, N, 1).write(buf);
-        buf.flip();
-        assertEquals(ByteBuffer.wrap(expected), buf);
-    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/BuffersTestingKit.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,210 +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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-import java.util.*;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-import static java.nio.ByteBuffer.allocate;
-
-public final class BuffersTestingKit {
-
-    /**
-     * Relocates a {@code [position, limit)} region of the given buffer to
-     * corresponding region in a new buffer starting with provided {@code
-     * newPosition}.
-     *
-     * <p> Might be useful to make sure ByteBuffer's users do not rely on any
-     * absolute positions, but solely on what's reported by position(), limit().
-     *
-     * <p> The contents between the given buffer and the returned one are not
-     * shared.
-     */
-    public static ByteBuffer relocate(ByteBuffer buffer, int newPosition,
-                                      int newCapacity) {
-        int oldPosition = buffer.position();
-        int oldLimit = buffer.limit();
-
-        if (newPosition + oldLimit - oldPosition > newCapacity) {
-            throw new IllegalArgumentException();
-        }
-
-        ByteBuffer result;
-        if (buffer.isDirect()) {
-            result = ByteBuffer.allocateDirect(newCapacity);
-        } else {
-            result = allocate(newCapacity);
-        }
-
-        result.position(newPosition);
-        result.put(buffer).limit(result.position()).position(newPosition);
-        buffer.position(oldPosition);
-
-        if (buffer.isReadOnly()) {
-            return result.asReadOnlyBuffer();
-        }
-        return result;
-    }
-
-    public static Iterable<? extends ByteBuffer> relocateBuffers(
-            Iterable<? extends ByteBuffer> source) {
-        return () ->
-                new Iterator<ByteBuffer>() {
-
-                    private final Iterator<? extends ByteBuffer> it = source.iterator();
-
-                    @Override
-                    public boolean hasNext() {
-                        return it.hasNext();
-                    }
-
-                    @Override
-                    public ByteBuffer next() {
-                        ByteBuffer buf = it.next();
-                        int remaining = buf.remaining();
-                        int newCapacity = remaining + random.nextInt(17);
-                        int newPosition = random.nextInt(newCapacity - remaining + 1);
-                        return relocate(buf, newPosition, newCapacity);
-                    }
-                };
-    }
-
-    // TODO: not always of size 0 (it's fine for buffer to report !b.hasRemaining())
-    public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
-            Iterable<? extends ByteBuffer> source) {
-        return injectEmptyBuffers(source, () -> allocate(0));
-    }
-
-    public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
-            Iterable<? extends ByteBuffer> source,
-            Supplier<? extends ByteBuffer> emptyBufferFactory) {
-
-        return () ->
-                new Iterator<ByteBuffer>() {
-
-                    private final Iterator<? extends ByteBuffer> it = source.iterator();
-                    private ByteBuffer next = calculateNext();
-
-                    private ByteBuffer calculateNext() {
-                        if (random.nextBoolean()) {
-                            return emptyBufferFactory.get();
-                        } else if (it.hasNext()) {
-                            return it.next();
-                        } else {
-                            return null;
-                        }
-                    }
-
-                    @Override
-                    public boolean hasNext() {
-                        return next != null;
-                    }
-
-                    @Override
-                    public ByteBuffer next() {
-                        if (!hasNext()) {
-                            throw new NoSuchElementException();
-                        }
-                        ByteBuffer next = this.next;
-                        this.next = calculateNext();
-                        return next;
-                    }
-                };
-    }
-
-    public static ByteBuffer concat(Iterable<? extends ByteBuffer> split) {
-        return concat(split, ByteBuffer::allocate);
-    }
-
-    public static ByteBuffer concat(Iterable<? extends ByteBuffer> split,
-                                    Function<? super Integer, ? extends ByteBuffer> concatBufferFactory) {
-        int size = 0;
-        for (ByteBuffer bb : split) {
-            size += bb.remaining();
-        }
-
-        ByteBuffer result = concatBufferFactory.apply(size);
-        for (ByteBuffer bb : split) {
-            result.put(bb);
-        }
-
-        result.flip();
-        return result;
-    }
-
-    public static void forEachSplit(ByteBuffer bb,
-                                    Consumer<? super Iterable<? extends ByteBuffer>> action) {
-        forEachSplit(bb.remaining(),
-                (lengths) -> {
-                    int end = bb.position();
-                    List<ByteBuffer> buffers = new LinkedList<>();
-                    for (int len : lengths) {
-                        ByteBuffer d = bb.duplicate();
-                        d.position(end);
-                        d.limit(end + len);
-                        end += len;
-                        buffers.add(d);
-                    }
-                    action.accept(buffers);
-                });
-    }
-
-    private static void forEachSplit(int n, Consumer<? super Iterable<? extends Integer>> action) {
-        forEachSplit(n, new Stack<>(), action);
-    }
-
-    private static void forEachSplit(int n, Stack<Integer> path,
-                                     Consumer<? super Iterable<? extends Integer>> action) {
-        if (n == 0) {
-            action.accept(path);
-        } else {
-            for (int i = 1; i <= n; i++) {
-                path.push(i);
-                forEachSplit(n - i, path, action);
-                path.pop();
-            }
-        }
-    }
-
-    private static final Random random = new Random();
-
-    private BuffersTestingKit() {
-        throw new InternalError();
-    }
-
-//    public static void main(String[] args) {
-//
-//        List<ByteBuffer> buffers = Arrays.asList(
-//                (ByteBuffer) allocate(3).position(1).limit(2),
-//                allocate(0),
-//                allocate(7));
-//
-//        Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
-//        List<ByteBuffer> result = new ArrayList<>();
-//        buf.forEach(result::add);
-//        System.out.println(result);
-//    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/CircularBufferTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package java.net.http.internal.hpack;
-
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-import java.net.http.internal.hpack.HeaderTable.CircularBuffer;
-
-import java.util.Queue;
-import java.util.Random;
-import java.util.concurrent.ArrayBlockingQueue;
-
-import static org.testng.Assert.assertEquals;
-import static java.net.http.internal.hpack.TestHelper.newRandom;
-
-public final class CircularBufferTest {
-
-    private final Random r = newRandom();
-
-    @BeforeClass
-    public void setUp() {
-        r.setSeed(System.currentTimeMillis());
-    }
-
-    @Test
-    public void queue() {
-        for (int capacity = 1; capacity <= 2048; capacity++) {
-            queueOnce(capacity, 32);
-        }
-    }
-
-    @Test
-    public void resize() {
-        for (int capacity = 1; capacity <= 4096; capacity++) {
-            resizeOnce(capacity);
-        }
-    }
-
-    @Test
-    public void downSizeEmptyBuffer() {
-        CircularBuffer<Integer> buffer = new CircularBuffer<>(16);
-        buffer.resize(15);
-    }
-
-    private void resizeOnce(int capacity) {
-
-        int nextNumberToPut = 0;
-
-        Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
-        CircularBuffer<Integer> buffer = new CircularBuffer<>(capacity);
-
-        // Fill full, so the next add will wrap
-        for (int i = 0; i < capacity; i++, nextNumberToPut++) {
-            buffer.add(nextNumberToPut);
-            referenceQueue.add(nextNumberToPut);
-        }
-        int gets = r.nextInt(capacity); // [0, capacity)
-        for (int i = 0; i < gets; i++) {
-            referenceQueue.poll();
-            buffer.remove();
-        }
-        int puts = r.nextInt(gets + 1); // [0, gets]
-        for (int i = 0; i < puts; i++, nextNumberToPut++) {
-            buffer.add(nextNumberToPut);
-            referenceQueue.add(nextNumberToPut);
-        }
-
-        Integer[] expected = referenceQueue.toArray(new Integer[0]);
-        buffer.resize(expected.length);
-
-        assertEquals(buffer.elements, expected);
-    }
-
-    private void queueOnce(int capacity, int numWraps) {
-
-        Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
-        CircularBuffer<Integer> buffer = new CircularBuffer<>(capacity);
-
-        int nextNumberToPut = 0;
-        int totalPuts = 0;
-        int putsLimit = capacity * numWraps;
-        int remainingCapacity = capacity;
-        int size = 0;
-
-        while (totalPuts < putsLimit) {
-            assert remainingCapacity + size == capacity;
-            int puts = r.nextInt(remainingCapacity + 1); // [0, remainingCapacity]
-            remainingCapacity -= puts;
-            size += puts;
-            for (int i = 0; i < puts; i++, nextNumberToPut++) {
-                referenceQueue.add(nextNumberToPut);
-                buffer.add(nextNumberToPut);
-            }
-            totalPuts += puts;
-            int gets = r.nextInt(size + 1); // [0, size]
-            size -= gets;
-            remainingCapacity += gets;
-            for (int i = 0; i < gets; i++) {
-                Integer expected = referenceQueue.poll();
-                Integer actual = buffer.remove();
-                assertEquals(actual, expected);
-            }
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/DecoderTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,685 +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 java.net.http.internal.hpack;
-
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static java.net.http.internal.hpack.TestHelper.*;
-
-//
-// Tests whose names start with "testX" are the ones captured from real HPACK
-// use cases
-//
-public final class DecoderTest {
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
-    //
-    @Test
-    public void example1() {
-        // @formatter:off
-        test("400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
-             "746f 6d2d 6865 6164 6572",
-
-             "[  1] (s =  55) custom-key: custom-header\n" +
-             "      Table size:  55",
-
-             "custom-key: custom-header");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
-    //
-    @Test
-    public void example2() {
-        // @formatter:off
-        test("040c 2f73 616d 706c 652f 7061 7468",
-             "empty.",
-             ":path: /sample/path");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
-    //
-    @Test
-    public void example3() {
-        // @formatter:off
-        test("1008 7061 7373 776f 7264 0673 6563 7265\n" +
-             "74",
-             "empty.",
-             "password: secret");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
-    //
-    @Test
-    public void example4() {
-        // @formatter:off
-        test("82",
-             "empty.",
-             ":method: GET");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.3
-    //
-    @Test
-    public void example5() {
-        // @formatter:off
-        Decoder d = new Decoder(256);
-
-        test(d, "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
-                "2e63 6f6d",
-
-                "[  1] (s =  57) :authority: www.example.com\n" +
-                "      Table size:  57",
-
-                ":method: GET\n" +
-                ":scheme: http\n" +
-                ":path: /\n" +
-                ":authority: www.example.com");
-
-        test(d, "8286 84be 5808 6e6f 2d63 6163 6865",
-
-                "[  1] (s =  53) cache-control: no-cache\n" +
-                "[  2] (s =  57) :authority: www.example.com\n" +
-                "      Table size: 110",
-
-                ":method: GET\n" +
-                ":scheme: http\n" +
-                ":path: /\n" +
-                ":authority: www.example.com\n" +
-                "cache-control: no-cache");
-
-        test(d, "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
-                "0c63 7573 746f 6d2d 7661 6c75 65",
-
-                "[  1] (s =  54) custom-key: custom-value\n" +
-                "[  2] (s =  53) cache-control: no-cache\n" +
-                "[  3] (s =  57) :authority: www.example.com\n" +
-                "      Table size: 164",
-
-                ":method: GET\n" +
-                ":scheme: https\n" +
-                ":path: /index.html\n" +
-                ":authority: www.example.com\n" +
-                "custom-key: custom-value");
-
-        // @formatter:on
-    }
-
-    @Test
-    public void example5AllSplits() {
-        // @formatter:off
-        testAllSplits(
-                "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
-                "2e63 6f6d",
-
-                "[  1] (s =  57) :authority: www.example.com\n" +
-                "      Table size:  57",
-
-                ":method: GET\n" +
-                ":scheme: http\n" +
-                ":path: /\n" +
-                ":authority: www.example.com");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.4
-    //
-    @Test
-    public void example6() {
-        // @formatter:off
-        Decoder d = new Decoder(256);
-
-        test(d, "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
-                "ff",
-
-                "[  1] (s =  57) :authority: www.example.com\n" +
-                "      Table size:  57",
-
-                ":method: GET\n" +
-                ":scheme: http\n" +
-                ":path: /\n" +
-                ":authority: www.example.com");
-
-        test(d, "8286 84be 5886 a8eb 1064 9cbf",
-
-                "[  1] (s =  53) cache-control: no-cache\n" +
-                "[  2] (s =  57) :authority: www.example.com\n" +
-                "      Table size: 110",
-
-                ":method: GET\n" +
-                ":scheme: http\n" +
-                ":path: /\n" +
-                ":authority: www.example.com\n" +
-                "cache-control: no-cache");
-
-        test(d, "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
-                "a849 e95b b8e8 b4bf",
-
-                "[  1] (s =  54) custom-key: custom-value\n" +
-                "[  2] (s =  53) cache-control: no-cache\n" +
-                "[  3] (s =  57) :authority: www.example.com\n" +
-                "      Table size: 164",
-
-                ":method: GET\n" +
-                ":scheme: https\n" +
-                ":path: /index.html\n" +
-                ":authority: www.example.com\n" +
-                "custom-key: custom-value");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.5
-    //
-    @Test
-    public void example7() {
-        // @formatter:off
-        Decoder d = new Decoder(256);
-
-        test(d, "4803 3330 3258 0770 7269 7661 7465 611d\n" +
-                "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
-                "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
-                "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
-                "6c65 2e63 6f6d",
-
-                "[  1] (s =  63) location: https://www.example.com\n" +
-                "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "[  3] (s =  52) cache-control: private\n" +
-                "[  4] (s =  42) :status: 302\n" +
-                "      Table size: 222",
-
-                ":status: 302\n" +
-                "cache-control: private\n" +
-                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "location: https://www.example.com");
-
-        test(d, "4803 3330 37c1 c0bf",
-
-                "[  1] (s =  42) :status: 307\n" +
-                "[  2] (s =  63) location: https://www.example.com\n" +
-                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "[  4] (s =  52) cache-control: private\n" +
-                "      Table size: 222",
-
-                ":status: 307\n" +
-                "cache-control: private\n" +
-                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "location: https://www.example.com");
-
-        test(d, "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
-                "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
-                "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
-                "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
-                "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
-                "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
-                "3d31",
-
-                "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
-                "[  2] (s =  52) content-encoding: gzip\n" +
-                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
-                "      Table size: 215",
-
-                ":status: 200\n" +
-                "cache-control: private\n" +
-                "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
-                "location: https://www.example.com\n" +
-                "content-encoding: gzip\n" +
-                "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.6
-    //
-    @Test
-    public void example8() {
-        // @formatter:off
-        Decoder d = new Decoder(256);
-
-        test(d, "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
-                "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
-                "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
-                "e9ae 82ae 43d3",
-
-                "[  1] (s =  63) location: https://www.example.com\n" +
-                "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "[  3] (s =  52) cache-control: private\n" +
-                "[  4] (s =  42) :status: 302\n" +
-                "      Table size: 222",
-
-                ":status: 302\n" +
-                "cache-control: private\n" +
-                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "location: https://www.example.com");
-
-        test(d, "4883 640e ffc1 c0bf",
-
-                "[  1] (s =  42) :status: 307\n" +
-                "[  2] (s =  63) location: https://www.example.com\n" +
-                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "[  4] (s =  52) cache-control: private\n" +
-                "      Table size: 222",
-
-                ":status: 307\n" +
-                "cache-control: private\n" +
-                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-                "location: https://www.example.com");
-
-        test(d, "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
-                "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
-                "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
-                "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
-                "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
-
-                "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
-                "[  2] (s =  52) content-encoding: gzip\n" +
-                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
-                "      Table size: 215",
-
-                ":status: 200\n" +
-                "cache-control: private\n" +
-                "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
-                "location: https://www.example.com\n" +
-                "content-encoding: gzip\n" +
-                "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
-        // @formatter:on
-    }
-
-    @Test
-    // One of responses from Apache Server that helped to catch a bug
-    public void testX() {
-        Decoder d = new Decoder(4096);
-        // @formatter:off
-        test(d, "3fe1 1f88 6196 d07a be94 03ea 693f 7504\n" +
-                "00b6 a05c b827 2e32 fa98 b46f 769e 86b1\n" +
-                "9272 b025 da5c 2ea9 fd70 a8de 7fb5 3556\n" +
-                "5ab7 6ece c057 02e2 2ad2 17bf 6c96 d07a\n" +
-                "be94 0854 cb6d 4a08 0075 40bd 71b6 6e05\n" +
-                "a531 68df 0f13 8efe 4522 cd32 21b6 5686\n" +
-                "eb23 781f cf52 848f d24a 8f0f 0d02 3435\n" +
-                "5f87 497c a589 d34d 1f",
-
-                "[  1] (s =  53) content-type: text/html\n" +
-                "[  2] (s =  50) accept-ranges: bytes\n" +
-                "[  3] (s =  74) last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
-                "[  4] (s =  77) server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
-                "[  5] (s =  65) date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
-                "      Table size: 319",
-
-                ":status: 200\n" +
-                "date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
-                "server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
-                "last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
-                "etag: \"2d-432a5e4a73a80\"\n" +
-                "accept-ranges: bytes\n" +
-                "content-length: 45\n" +
-                "content-type: text/html");
-        // @formatter:on
-    }
-
-    @Test
-    public void testX1() {
-        // Supplier of a decoder with a particular state
-        Supplier<Decoder> s = () -> {
-            Decoder d = new Decoder(4096);
-            // @formatter:off
-            test(d, "88 76 92 ca 54 a7 d7 f4 fa ec af ed 6d da 61 d7 bb 1e ad ff" +
-                    "df 61 97 c3 61 be 94 13 4a 65 b6 a5 04 00 b8 a0 5a b8 db 77" +
-                    "1b 71 4c 5a 37 ff 0f 0d 84 08 00 00 03",
-
-                    "[  1] (s =  65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
-                    "[  2] (s =  59) server: Jetty(9.3.z-SNAPSHOT)\n" +
-                    "      Table size: 124",
-
-                    ":status: 200\n" +
-                    "server: Jetty(9.3.z-SNAPSHOT)\n" +
-                    "date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
-                    "content-length: 100000"
-            );
-            // @formatter:on
-            return d;
-        };
-        // For all splits of the following data fed to the supplied decoder we
-        // must get what's expected
-        // @formatter:off
-        testAllSplits(s,
-                "88 bf be 0f 0d 84 08 00 00 03",
-
-                "[  1] (s =  65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
-                "[  2] (s =  59) server: Jetty(9.3.z-SNAPSHOT)\n" +
-                "      Table size: 124",
-
-                ":status: 200\n" +
-                "server: Jetty(9.3.z-SNAPSHOT)\n" +
-                "date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
-                "content-length: 100000");
-        // @formatter:on
-    }
-
-    //
-    // This test is missing in the spec
-    //
-    @Test
-    public void sizeUpdate() throws IOException {
-        Decoder d = new Decoder(4096);
-        assertEquals(d.getTable().maxSize(), 4096);
-        d.decode(ByteBuffer.wrap(new byte[]{0b00111110}), true, nopCallback()); // newSize = 30
-        assertEquals(d.getTable().maxSize(), 30);
-    }
-
-    @Test
-    public void incorrectSizeUpdate() {
-        ByteBuffer b = ByteBuffer.allocate(8);
-        Encoder e = new Encoder(8192) {
-            @Override
-            protected int calculateCapacity(int maxCapacity) {
-                return maxCapacity;
-            }
-        };
-        e.header("a", "b");
-        e.encode(b);
-        b.flip();
-        {
-            Decoder d = new Decoder(4096);
-            assertVoidThrows(IOException.class,
-                    () -> d.decode(b, true, (name, value) -> { }));
-        }
-        b.flip();
-        {
-            Decoder d = new Decoder(4096);
-            assertVoidThrows(IOException.class,
-                    () -> d.decode(b, false, (name, value) -> { }));
-        }
-    }
-
-    @Test
-    public void corruptedHeaderBlockInteger() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                (byte) 0b11111111, // indexed
-                (byte) 0b10011010  // 25 + ...
-        });
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-        assertExceptionMessageContains(e, "Unexpected end of header block");
-    }
-
-    // 5.1.  Integer Representation
-    // ...
-    // Integer encodings that exceed implementation limits -- in value or octet
-    // length -- MUST be treated as decoding errors. Different limits can
-    // be set for each of the different uses of integers, based on
-    // implementation constraints.
-    @Test
-    public void headerBlockIntegerNoOverflow() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                (byte) 0b11111111, // indexed + 127
-                // Integer.MAX_VALUE - 127 (base 128, little-endian):
-                (byte) 0b10000000,
-                (byte) 0b11111111,
-                (byte) 0b11111111,
-                (byte) 0b11111111,
-                (byte) 0b00000111
-        });
-
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-
-        assertExceptionMessageContains(e.getCause(), "index=2147483647");
-    }
-
-    @Test
-    public void headerBlockIntegerOverflow() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                (byte) 0b11111111, // indexed + 127
-                // Integer.MAX_VALUE - 127 + 1 (base 128, little endian):
-                (byte) 0b10000001,
-                (byte) 0b11111111,
-                (byte) 0b11111111,
-                (byte) 0b11111111,
-                (byte) 0b00000111
-        });
-
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-
-        assertExceptionMessageContains(e, "Integer overflow");
-    }
-
-    @Test
-    public void corruptedHeaderBlockString1() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                0b00001111, // literal, index=15
-                0b00000000,
-                0b00001000, // huffman=false, length=8
-                0b00000000, // \
-                0b00000000, //  but only 3 octets available...
-                0b00000000  // /
-        });
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-        assertExceptionMessageContains(e, "Unexpected end of header block");
-    }
-
-    @Test
-    public void corruptedHeaderBlockString2() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                0b00001111, // literal, index=15
-                0b00000000,
-                (byte) 0b10001000, // huffman=true, length=8
-                0b00000000, // \
-                0b00000000, //  \
-                0b00000000, //   but only 5 octets available...
-                0b00000000, //  /
-                0b00000000  // /
-        });
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-        assertExceptionMessageContains(e, "Unexpected end of header block");
-    }
-
-    // 5.2.  String Literal Representation
-    // ...A Huffman-encoded string literal containing the EOS symbol MUST be
-    // treated as a decoding error...
-    @Test
-    public void corruptedHeaderBlockHuffmanStringEOS() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                0b00001111, // literal, index=15
-                0b00000000,
-                (byte) 0b10000110, // huffman=true, length=6
-                0b00011001, 0b01001101, (byte) 0b11111111,
-                (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111100
-        });
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-
-        assertExceptionMessageContains(e, "Encountered EOS");
-    }
-
-    // 5.2.  String Literal Representation
-    // ...A padding strictly longer than 7 bits MUST be treated as a decoding
-    // error...
-    @Test
-    public void corruptedHeaderBlockHuffmanStringLongPadding1() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                0b00001111, // literal, index=15
-                0b00000000,
-                (byte) 0b10000011, // huffman=true, length=3
-                0b00011001, 0b01001101, (byte) 0b11111111
-                // len("aei") + len(padding) = (5 + 5 + 5) + (9)
-        });
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-
-        assertExceptionMessageContains(e, "Padding is too long", "len=9");
-    }
-
-    @Test
-    public void corruptedHeaderBlockHuffmanStringLongPadding2() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                0b00001111, // literal, index=15
-                0b00000000,
-                (byte) 0b10000011, // huffman=true, length=3
-                0b00011001, 0b01111010, (byte) 0b11111111
-                // len("aek") + len(padding) = (5 + 5 + 7) + (7)
-        });
-        assertVoidDoesNotThrow(() -> d.decode(data, true, nopCallback()));
-    }
-
-    // 5.2.  String Literal Representation
-    // ...A padding not corresponding to the most significant bits of the code
-    // for the EOS symbol MUST be treated as a decoding error...
-    @Test
-    public void corruptedHeaderBlockHuffmanStringNotEOSPadding() {
-        Decoder d = new Decoder(4096);
-        ByteBuffer data = ByteBuffer.wrap(new byte[]{
-                0b00001111, // literal, index=15
-                0b00000000,
-                (byte) 0b10000011, // huffman=true, length=3
-                0b00011001, 0b01111010, (byte) 0b11111110
-        });
-        IOException e = assertVoidThrows(IOException.class,
-                () -> d.decode(data, true, nopCallback()));
-
-        assertExceptionMessageContains(e, "Not a EOS prefix");
-    }
-
-    @Test
-    public void argsTestBiConsumerIsNull() {
-        Decoder decoder = new Decoder(4096);
-        assertVoidThrows(NullPointerException.class,
-                () -> decoder.decode(ByteBuffer.allocate(16), true, null));
-    }
-
-    @Test
-    public void argsTestByteBufferIsNull() {
-        Decoder decoder = new Decoder(4096);
-        assertVoidThrows(NullPointerException.class,
-                () -> decoder.decode(null, true, nopCallback()));
-    }
-
-    @Test
-    public void argsTestBothAreNull() {
-        Decoder decoder = new Decoder(4096);
-        assertVoidThrows(NullPointerException.class,
-                () -> decoder.decode(null, true, null));
-    }
-
-    private static void test(String hexdump,
-                             String headerTable, String headerList) {
-        test(new Decoder(4096), hexdump, headerTable, headerList);
-    }
-
-    private static void testAllSplits(String hexdump,
-                                      String expectedHeaderTable,
-                                      String expectedHeaderList) {
-        testAllSplits(() -> new Decoder(256), hexdump, expectedHeaderTable, expectedHeaderList);
-    }
-
-    private static void testAllSplits(Supplier<Decoder> supplier, String hexdump,
-                                      String expectedHeaderTable, String expectedHeaderList) {
-        ByteBuffer source = SpecHelper.toBytes(hexdump);
-
-        BuffersTestingKit.forEachSplit(source, iterable -> {
-            List<String> actual = new LinkedList<>();
-            Iterator<? extends ByteBuffer> i = iterable.iterator();
-            if (!i.hasNext()) {
-                return;
-            }
-            Decoder d = supplier.get();
-            do {
-                ByteBuffer n = i.next();
-                try {
-                    d.decode(n, !i.hasNext(), (name, value) -> {
-                        if (value == null) {
-                            actual.add(name.toString());
-                        } else {
-                            actual.add(name + ": " + value);
-                        }
-                    });
-                } catch (IOException e) {
-                    throw new UncheckedIOException(e);
-                }
-            } while (i.hasNext());
-            assertEquals(d.getTable().getStateString(), expectedHeaderTable);
-            assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
-        });
-    }
-
-    //
-    // Sometimes we need to keep the same decoder along several runs,
-    // as it models the same connection
-    //
-    private static void test(Decoder d, String hexdump,
-                             String expectedHeaderTable, String expectedHeaderList) {
-
-        ByteBuffer source = SpecHelper.toBytes(hexdump);
-
-        List<String> actual = new LinkedList<>();
-        try {
-            d.decode(source, true, (name, value) -> {
-                if (value == null) {
-                    actual.add(name.toString());
-                } else {
-                    actual.add(name + ": " + value);
-                }
-            });
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-
-        assertEquals(d.getTable().getStateString(), expectedHeaderTable);
-        assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
-    }
-
-    private static DecodingCallback nopCallback() {
-        return (t, u) -> { };
-    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/EncoderTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,693 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import static java.net.http.internal.hpack.BuffersTestingKit.concat;
-import static java.net.http.internal.hpack.BuffersTestingKit.forEachSplit;
-import static java.net.http.internal.hpack.SpecHelper.toHexdump;
-import static java.net.http.internal.hpack.TestHelper.assertVoidThrows;
-import static java.util.Arrays.asList;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-// TODO: map textual representation of commands from the spec to actual
-// calls to encoder (actually, this is a good idea for decoder as well)
-public final class EncoderTest {
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
-    //
-    @Test
-    public void example1() {
-
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        e.literalWithIndexing("custom-key", false, "custom-header", false);
-        // @formatter:off
-        test(e,
-
-             "400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
-             "746f 6d2d 6865 6164 6572",
-
-             "[  1] (s =  55) custom-key: custom-header\n" +
-             "      Table size:  55");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
-    //
-    @Test
-    public void example2() {
-
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        e.literal(4, "/sample/path", false);
-        // @formatter:off
-        test(e,
-
-             "040c 2f73 616d 706c 652f 7061 7468",
-
-             "empty.");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
-    //
-    @Test
-    public void example3() {
-
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        e.literalNeverIndexed("password", false, "secret", false);
-        // @formatter:off
-        test(e,
-
-             "1008 7061 7373 776f 7264 0673 6563 7265\n" +
-             "74",
-
-             "empty.");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
-    //
-    @Test
-    public void example4() {
-
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        e.indexed(2);
-        // @formatter:off
-        test(e,
-
-             "82",
-
-             "empty.");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.3
-    //
-    @Test
-    public void example5() {
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        ByteBuffer output = ByteBuffer.allocate(64);
-        e.indexed(2);
-        e.encode(output);
-        e.indexed(6);
-        e.encode(output);
-        e.indexed(4);
-        e.encode(output);
-        e.literalWithIndexing(1, "www.example.com", false);
-        e.encode(output);
-
-        output.flip();
-
-        // @formatter:off
-        test(e, output,
-
-             "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
-             "2e63 6f6d",
-
-             "[  1] (s =  57) :authority: www.example.com\n" +
-             "      Table size:  57");
-
-        output.clear();
-
-        e.indexed( 2);
-        e.encode(output);
-        e.indexed( 6);
-        e.encode(output);
-        e.indexed( 4);
-        e.encode(output);
-        e.indexed(62);
-        e.encode(output);
-        e.literalWithIndexing(24, "no-cache", false);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "8286 84be 5808 6e6f 2d63 6163 6865",
-
-             "[  1] (s =  53) cache-control: no-cache\n" +
-             "[  2] (s =  57) :authority: www.example.com\n" +
-             "      Table size: 110");
-
-        output.clear();
-
-        e.indexed( 2);
-        e.encode(output);
-        e.indexed( 7);
-        e.encode(output);
-        e.indexed( 5);
-        e.encode(output);
-        e.indexed(63);
-        e.encode(output);
-        e.literalWithIndexing("custom-key", false, "custom-value", false);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
-             "0c63 7573 746f 6d2d 7661 6c75 65",
-
-             "[  1] (s =  54) custom-key: custom-value\n" +
-             "[  2] (s =  53) cache-control: no-cache\n" +
-             "[  3] (s =  57) :authority: www.example.com\n" +
-             "      Table size: 164");
-        // @formatter:on
-    }
-
-    @Test
-    public void example5AllSplits() {
-
-        List<Consumer<Encoder>> actions = new LinkedList<>();
-        actions.add(e -> e.indexed(2));
-        actions.add(e -> e.indexed(6));
-        actions.add(e -> e.indexed(4));
-        actions.add(e -> e.literalWithIndexing(1, "www.example.com", false));
-
-        encodeAllSplits(
-                actions,
-
-                "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
-                "2e63 6f6d",
-
-                "[  1] (s =  57) :authority: www.example.com\n" +
-                "      Table size:  57");
-    }
-
-    private static void encodeAllSplits(Iterable<Consumer<Encoder>> consumers,
-                                        String expectedHexdump,
-                                        String expectedTableState) {
-        ByteBuffer buffer = SpecHelper.toBytes(expectedHexdump);
-        erase(buffer); // Zeroed buffer of size needed to hold the encoding
-        forEachSplit(buffer, iterable -> {
-            List<ByteBuffer> copy = new LinkedList<>();
-            iterable.forEach(b -> copy.add(ByteBuffer.allocate(b.remaining())));
-            Iterator<ByteBuffer> output = copy.iterator();
-            if (!output.hasNext()) {
-                throw new IllegalStateException("No buffers to encode to");
-            }
-            Encoder e = newCustomEncoder(256); // FIXME: pull up (as a parameter)
-            drainInitialUpdate(e);
-            boolean encoded;
-            ByteBuffer b = output.next();
-            for (Consumer<Encoder> c : consumers) {
-                c.accept(e);
-                do {
-                    encoded = e.encode(b);
-                    if (!encoded) {
-                        if (output.hasNext()) {
-                            b = output.next();
-                        } else {
-                            throw new IllegalStateException("No room for encoding");
-                        }
-                    }
-                }
-                while (!encoded);
-            }
-            copy.forEach(Buffer::flip);
-            ByteBuffer data = concat(copy);
-            test(e, data, expectedHexdump, expectedTableState);
-        });
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.4
-    //
-    @Test
-    public void example6() {
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        ByteBuffer output = ByteBuffer.allocate(64);
-        e.indexed(2);
-        e.encode(output);
-        e.indexed(6);
-        e.encode(output);
-        e.indexed(4);
-        e.encode(output);
-        e.literalWithIndexing(1, "www.example.com", true);
-        e.encode(output);
-
-        output.flip();
-
-        // @formatter:off
-        test(e, output,
-
-             "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
-             "ff",
-
-             "[  1] (s =  57) :authority: www.example.com\n" +
-             "      Table size:  57");
-
-        output.clear();
-
-        e.indexed( 2);
-        e.encode(output);
-        e.indexed( 6);
-        e.encode(output);
-        e.indexed( 4);
-        e.encode(output);
-        e.indexed(62);
-        e.encode(output);
-        e.literalWithIndexing(24, "no-cache", true);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "8286 84be 5886 a8eb 1064 9cbf",
-
-             "[  1] (s =  53) cache-control: no-cache\n" +
-             "[  2] (s =  57) :authority: www.example.com\n" +
-             "      Table size: 110");
-
-        output.clear();
-
-        e.indexed( 2);
-        e.encode(output);
-        e.indexed( 7);
-        e.encode(output);
-        e.indexed( 5);
-        e.encode(output);
-        e.indexed(63);
-        e.encode(output);
-        e.literalWithIndexing("custom-key", true, "custom-value", true);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
-             "a849 e95b b8e8 b4bf",
-
-             "[  1] (s =  54) custom-key: custom-value\n" +
-             "[  2] (s =  53) cache-control: no-cache\n" +
-             "[  3] (s =  57) :authority: www.example.com\n" +
-             "      Table size: 164");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.5
-    //
-    @Test
-    public void example7() {
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        ByteBuffer output = ByteBuffer.allocate(128);
-        // @formatter:off
-        e.literalWithIndexing( 8, "302", false);
-        e.encode(output);
-        e.literalWithIndexing(24, "private", false);
-        e.encode(output);
-        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", false);
-        e.encode(output);
-        e.literalWithIndexing(46, "https://www.example.com", false);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "4803 3330 3258 0770 7269 7661 7465 611d\n" +
-             "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
-             "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
-             "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
-             "6c65 2e63 6f6d",
-
-             "[  1] (s =  63) location: https://www.example.com\n" +
-             "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-             "[  3] (s =  52) cache-control: private\n" +
-             "[  4] (s =  42) :status: 302\n" +
-             "      Table size: 222");
-
-        output.clear();
-
-        e.literalWithIndexing( 8, "307", false);
-        e.encode(output);
-        e.indexed(65);
-        e.encode(output);
-        e.indexed(64);
-        e.encode(output);
-        e.indexed(63);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "4803 3330 37c1 c0bf",
-
-             "[  1] (s =  42) :status: 307\n" +
-             "[  2] (s =  63) location: https://www.example.com\n" +
-             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-             "[  4] (s =  52) cache-control: private\n" +
-             "      Table size: 222");
-
-        output.clear();
-
-        e.indexed( 8);
-        e.encode(output);
-        e.indexed(65);
-        e.encode(output);
-        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", false);
-        e.encode(output);
-        e.indexed(64);
-        e.encode(output);
-        e.literalWithIndexing(26, "gzip", false);
-        e.encode(output);
-        e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", false);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
-             "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
-             "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
-             "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
-             "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
-             "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
-             "3d31",
-
-             "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
-             "[  2] (s =  52) content-encoding: gzip\n" +
-             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
-             "      Table size: 215");
-        // @formatter:on
-    }
-
-    //
-    // http://tools.ietf.org/html/rfc7541#appendix-C.6
-    //
-    @Test
-    public void example8() {
-        Encoder e = newCustomEncoder(256);
-        drainInitialUpdate(e);
-
-        ByteBuffer output = ByteBuffer.allocate(128);
-        // @formatter:off
-        e.literalWithIndexing( 8, "302", true);
-        e.encode(output);
-        e.literalWithIndexing(24, "private", true);
-        e.encode(output);
-        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", true);
-        e.encode(output);
-        e.literalWithIndexing(46, "https://www.example.com", true);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
-             "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
-             "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
-             "e9ae 82ae 43d3",
-
-             "[  1] (s =  63) location: https://www.example.com\n" +
-             "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-             "[  3] (s =  52) cache-control: private\n" +
-             "[  4] (s =  42) :status: 302\n" +
-             "      Table size: 222");
-
-        output.clear();
-
-        e.literalWithIndexing( 8, "307", true);
-        e.encode(output);
-        e.indexed(65);
-        e.encode(output);
-        e.indexed(64);
-        e.encode(output);
-        e.indexed(63);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "4883 640e ffc1 c0bf",
-
-             "[  1] (s =  42) :status: 307\n" +
-             "[  2] (s =  63) location: https://www.example.com\n" +
-             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
-             "[  4] (s =  52) cache-control: private\n" +
-             "      Table size: 222");
-
-        output.clear();
-
-        e.indexed( 8);
-        e.encode(output);
-        e.indexed(65);
-        e.encode(output);
-        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", true);
-        e.encode(output);
-        e.indexed(64);
-        e.encode(output);
-        e.literalWithIndexing(26, "gzip", true);
-        e.encode(output);
-        e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", true);
-        e.encode(output);
-
-        output.flip();
-
-        test(e, output,
-
-             "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
-             "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
-             "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
-             "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
-             "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
-
-             "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
-             "[  2] (s =  52) content-encoding: gzip\n" +
-             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
-             "      Table size: 215");
-        // @formatter:on
-    }
-
-    @Test
-    public void initialSizeUpdateDefaultEncoder() throws IOException {
-        Function<Integer, Encoder> e = Encoder::new;
-        testSizeUpdate(e, 1024, asList(), asList(0));
-        testSizeUpdate(e, 1024, asList(1024), asList(0));
-        testSizeUpdate(e, 1024, asList(1024, 1024), asList(0));
-        testSizeUpdate(e, 1024, asList(1024, 512), asList(0));
-        testSizeUpdate(e, 1024, asList(512, 1024), asList(0));
-        testSizeUpdate(e, 1024, asList(512, 2048), asList(0));
-    }
-
-    @Test
-    public void initialSizeUpdateCustomEncoder() throws IOException {
-        Function<Integer, Encoder> e = EncoderTest::newCustomEncoder;
-        testSizeUpdate(e, 1024, asList(), asList(1024));
-        testSizeUpdate(e, 1024, asList(1024), asList(1024));
-        testSizeUpdate(e, 1024, asList(1024, 1024), asList(1024));
-        testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
-        testSizeUpdate(e, 1024, asList(512, 1024), asList(1024));
-        testSizeUpdate(e, 1024, asList(512, 2048), asList(2048));
-    }
-
-    @Test
-    public void seriesOfSizeUpdatesDefaultEncoder() throws IOException {
-        Function<Integer, Encoder> e = c -> {
-            Encoder encoder = new Encoder(c);
-            drainInitialUpdate(encoder);
-            return encoder;
-        };
-        testSizeUpdate(e, 0, asList(0), asList());
-        testSizeUpdate(e, 1024, asList(1024), asList());
-        testSizeUpdate(e, 1024, asList(2048), asList());
-        testSizeUpdate(e, 1024, asList(512), asList());
-        testSizeUpdate(e, 1024, asList(1024, 1024), asList());
-        testSizeUpdate(e, 1024, asList(1024, 2048), asList());
-        testSizeUpdate(e, 1024, asList(2048, 1024), asList());
-        testSizeUpdate(e, 1024, asList(1024, 512), asList());
-        testSizeUpdate(e, 1024, asList(512, 1024), asList());
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#section-4.2
-    //
-    @Test
-    public void seriesOfSizeUpdatesCustomEncoder() throws IOException {
-        Function<Integer, Encoder> e = c -> {
-            Encoder encoder = newCustomEncoder(c);
-            drainInitialUpdate(encoder);
-            return encoder;
-        };
-        testSizeUpdate(e, 0, asList(0), asList());
-        testSizeUpdate(e, 1024, asList(1024), asList());
-        testSizeUpdate(e, 1024, asList(2048), asList(2048));
-        testSizeUpdate(e, 1024, asList(512), asList(512));
-        testSizeUpdate(e, 1024, asList(1024, 1024), asList());
-        testSizeUpdate(e, 1024, asList(1024, 2048), asList(2048));
-        testSizeUpdate(e, 1024, asList(2048, 1024), asList());
-        testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
-        testSizeUpdate(e, 1024, asList(512, 1024), asList(512, 1024));
-    }
-
-    @Test
-    public void callSequenceViolations() {
-        {   // Hasn't set up a header
-            Encoder e = new Encoder(0);
-            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
-        }
-        {   // Can't set up header while there's an unfinished encoding
-            Encoder e = new Encoder(0);
-            e.indexed(32);
-            assertVoidThrows(IllegalStateException.class, () -> e.indexed(32));
-        }
-        {   // Can't setMaxCapacity while there's an unfinished encoding
-            Encoder e = new Encoder(0);
-            e.indexed(32);
-            assertVoidThrows(IllegalStateException.class, () -> e.setMaxCapacity(512));
-        }
-        {   // Hasn't set up a header
-            Encoder e = new Encoder(0);
-            e.setMaxCapacity(256);
-            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
-        }
-        {   // Hasn't set up a header after the previous encoding
-            Encoder e = new Encoder(0);
-            e.indexed(0);
-            boolean encoded = e.encode(ByteBuffer.allocate(16));
-            assertTrue(encoded); // assumption
-            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
-        }
-    }
-
-    private static void test(Encoder encoder,
-                             String expectedTableState,
-                             String expectedHexdump) {
-
-        ByteBuffer b = ByteBuffer.allocate(128);
-        encoder.encode(b);
-        b.flip();
-        test(encoder, b, expectedTableState, expectedHexdump);
-    }
-
-    private static void test(Encoder encoder,
-                             ByteBuffer output,
-                             String expectedHexdump,
-                             String expectedTableState) {
-
-        String actualTableState = encoder.getHeaderTable().getStateString();
-        assertEquals(actualTableState, expectedTableState);
-
-        String actualHexdump = toHexdump(output);
-        assertEquals(actualHexdump, expectedHexdump.replaceAll("\\n", " "));
-    }
-
-    // initial size - the size encoder is constructed with
-    // updates      - a sequence of values for consecutive calls to encoder.setMaxCapacity
-    // expected     - a sequence of values expected to be decoded by a decoder
-    private void testSizeUpdate(Function<Integer, Encoder> encoder,
-                                int initialSize,
-                                List<Integer> updates,
-                                List<Integer> expected) throws IOException {
-        Encoder e = encoder.apply(initialSize);
-        updates.forEach(e::setMaxCapacity);
-        ByteBuffer b = ByteBuffer.allocate(64);
-        e.header("a", "b");
-        e.encode(b);
-        b.flip();
-        Decoder d = new Decoder(updates.isEmpty() ? initialSize : Collections.max(updates));
-        List<Integer> actual = new ArrayList<>();
-        d.decode(b, true, new DecodingCallback() {
-            @Override
-            public void onDecoded(CharSequence name, CharSequence value) { }
-
-            @Override
-            public void onSizeUpdate(int capacity) {
-                actual.add(capacity);
-            }
-        });
-        assertEquals(actual, expected);
-    }
-
-    //
-    // Default encoder does not need any table, therefore a subclass that
-    // behaves differently is needed
-    //
-    private static Encoder newCustomEncoder(int maxCapacity) {
-        return new Encoder(maxCapacity) {
-            @Override
-            protected int calculateCapacity(int maxCapacity) {
-                return maxCapacity;
-            }
-        };
-    }
-
-    private static void drainInitialUpdate(Encoder e) {
-        ByteBuffer b = ByteBuffer.allocate(4);
-        e.header("a", "b");
-        boolean done;
-        do {
-            done = e.encode(b);
-            b.flip();
-        } while (!done);
-    }
-
-    private static void erase(ByteBuffer buffer) {
-        buffer.clear();
-        while (buffer.hasRemaining()) {
-            buffer.put((byte) 0);
-        }
-        buffer.clear();
-    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/HeaderTableTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,409 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import org.testng.annotations.Test;
-import java.net.http.internal.hpack.HeaderTable.HeaderField;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Random;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static java.lang.String.format;
-import static org.testng.Assert.assertEquals;
-import static java.net.http.internal.hpack.TestHelper.assertExceptionMessageContains;
-import static java.net.http.internal.hpack.TestHelper.assertThrows;
-import static java.net.http.internal.hpack.TestHelper.assertVoidThrows;
-import static java.net.http.internal.hpack.TestHelper.newRandom;
-
-public class HeaderTableTest {
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-A
-    //
-    // @formatter:off
-    private static final String SPEC =
-       "          | 1     | :authority                  |               |\n" +
-       "          | 2     | :method                     | GET           |\n" +
-       "          | 3     | :method                     | POST          |\n" +
-       "          | 4     | :path                       | /             |\n" +
-       "          | 5     | :path                       | /index.html   |\n" +
-       "          | 6     | :scheme                     | http          |\n" +
-       "          | 7     | :scheme                     | https         |\n" +
-       "          | 8     | :status                     | 200           |\n" +
-       "          | 9     | :status                     | 204           |\n" +
-       "          | 10    | :status                     | 206           |\n" +
-       "          | 11    | :status                     | 304           |\n" +
-       "          | 12    | :status                     | 400           |\n" +
-       "          | 13    | :status                     | 404           |\n" +
-       "          | 14    | :status                     | 500           |\n" +
-       "          | 15    | accept-charset              |               |\n" +
-       "          | 16    | accept-encoding             | gzip, deflate |\n" +
-       "          | 17    | accept-language             |               |\n" +
-       "          | 18    | accept-ranges               |               |\n" +
-       "          | 19    | accept                      |               |\n" +
-       "          | 20    | access-control-allow-origin |               |\n" +
-       "          | 21    | age                         |               |\n" +
-       "          | 22    | allow                       |               |\n" +
-       "          | 23    | authorization               |               |\n" +
-       "          | 24    | cache-control               |               |\n" +
-       "          | 25    | content-disposition         |               |\n" +
-       "          | 26    | content-encoding            |               |\n" +
-       "          | 27    | content-language            |               |\n" +
-       "          | 28    | content-length              |               |\n" +
-       "          | 29    | content-location            |               |\n" +
-       "          | 30    | content-range               |               |\n" +
-       "          | 31    | content-type                |               |\n" +
-       "          | 32    | cookie                      |               |\n" +
-       "          | 33    | date                        |               |\n" +
-       "          | 34    | etag                        |               |\n" +
-       "          | 35    | expect                      |               |\n" +
-       "          | 36    | expires                     |               |\n" +
-       "          | 37    | from                        |               |\n" +
-       "          | 38    | host                        |               |\n" +
-       "          | 39    | if-match                    |               |\n" +
-       "          | 40    | if-modified-since           |               |\n" +
-       "          | 41    | if-none-match               |               |\n" +
-       "          | 42    | if-range                    |               |\n" +
-       "          | 43    | if-unmodified-since         |               |\n" +
-       "          | 44    | last-modified               |               |\n" +
-       "          | 45    | link                        |               |\n" +
-       "          | 46    | location                    |               |\n" +
-       "          | 47    | max-forwards                |               |\n" +
-       "          | 48    | proxy-authenticate          |               |\n" +
-       "          | 49    | proxy-authorization         |               |\n" +
-       "          | 50    | range                       |               |\n" +
-       "          | 51    | referer                     |               |\n" +
-       "          | 52    | refresh                     |               |\n" +
-       "          | 53    | retry-after                 |               |\n" +
-       "          | 54    | server                      |               |\n" +
-       "          | 55    | set-cookie                  |               |\n" +
-       "          | 56    | strict-transport-security   |               |\n" +
-       "          | 57    | transfer-encoding           |               |\n" +
-       "          | 58    | user-agent                  |               |\n" +
-       "          | 59    | vary                        |               |\n" +
-       "          | 60    | via                         |               |\n" +
-       "          | 61    | www-authenticate            |               |\n";
-    // @formatter:on
-
-    private static final int STATIC_TABLE_LENGTH = createStaticEntries().size();
-    private final Random rnd = newRandom();
-
-    @Test
-    public void staticData() {
-        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
-        Map<Integer, HeaderField> staticHeaderFields = createStaticEntries();
-
-        Map<String, Integer> minimalIndexes = new HashMap<>();
-
-        for (Map.Entry<Integer, HeaderField> e : staticHeaderFields.entrySet()) {
-            Integer idx = e.getKey();
-            String hName = e.getValue().name;
-            Integer midx = minimalIndexes.get(hName);
-            if (midx == null) {
-                minimalIndexes.put(hName, idx);
-            } else {
-                minimalIndexes.put(hName, Math.min(idx, midx));
-            }
-        }
-
-        staticHeaderFields.entrySet().forEach(
-                e -> {
-                    // lookup
-                    HeaderField actualHeaderField = table.get(e.getKey());
-                    HeaderField expectedHeaderField = e.getValue();
-                    assertEquals(actualHeaderField, expectedHeaderField);
-
-                    // reverse lookup (name, value)
-                    String hName = expectedHeaderField.name;
-                    String hValue = expectedHeaderField.value;
-                    int expectedIndex = e.getKey();
-                    int actualIndex = table.indexOf(hName, hValue);
-
-                    assertEquals(actualIndex, expectedIndex);
-
-                    // reverse lookup (name)
-                    int expectedMinimalIndex = minimalIndexes.get(hName);
-                    int actualMinimalIndex = table.indexOf(hName, "blah-blah");
-
-                    assertEquals(-actualMinimalIndex, expectedMinimalIndex);
-                }
-        );
-    }
-
-    @Test
-    public void constructorSetsMaxSize() {
-        int size = rnd.nextInt(64);
-        HeaderTable t = new HeaderTable(size, HPACK.getLogger());
-        assertEquals(t.size(), 0);
-        assertEquals(t.maxSize(), size);
-    }
-
-    @Test
-    public void negativeMaximumSize() {
-        int maxSize = -(rnd.nextInt(100) + 1); // [-100, -1]
-        IllegalArgumentException e =
-                assertVoidThrows(IllegalArgumentException.class,
-                        () -> new HeaderTable(0, HPACK.getLogger()).setMaxSize(maxSize));
-        assertExceptionMessageContains(e, "maxSize");
-    }
-
-    @Test
-    public void zeroMaximumSize() {
-        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
-        table.setMaxSize(0);
-        assertEquals(table.maxSize(), 0);
-    }
-
-    @Test
-    public void negativeIndex() {
-        int idx = -(rnd.nextInt(256) + 1); // [-256, -1]
-        IndexOutOfBoundsException e =
-                assertVoidThrows(IndexOutOfBoundsException.class,
-                        () -> new HeaderTable(0, HPACK.getLogger()).get(idx));
-        assertExceptionMessageContains(e, "index");
-    }
-
-    @Test
-    public void zeroIndex() {
-        IndexOutOfBoundsException e =
-                assertThrows(IndexOutOfBoundsException.class,
-                        () -> new HeaderTable(0, HPACK.getLogger()).get(0));
-        assertExceptionMessageContains(e, "index");
-    }
-
-    @Test
-    public void length() {
-        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
-        assertEquals(table.length(), STATIC_TABLE_LENGTH);
-    }
-
-    @Test
-    public void indexOutsideStaticRange() {
-        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
-        int idx = table.length() + (rnd.nextInt(256) + 1);
-        IndexOutOfBoundsException e =
-                assertThrows(IndexOutOfBoundsException.class,
-                        () -> table.get(idx));
-        assertExceptionMessageContains(e, "index");
-    }
-
-    @Test
-    public void entryPutAfterStaticArea() {
-        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
-        int idx = table.length() + 1;
-        assertThrows(IndexOutOfBoundsException.class, () -> table.get(idx));
-
-        byte[] bytes = new byte[32];
-        rnd.nextBytes(bytes);
-        String name = new String(bytes, StandardCharsets.ISO_8859_1);
-        String value = "custom-value";
-
-        table.put(name, value);
-        HeaderField f = table.get(idx);
-        assertEquals(name, f.name);
-        assertEquals(value, f.value);
-    }
-
-    @Test
-    public void staticTableHasZeroSize() {
-        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
-        assertEquals(0, table.size());
-    }
-
-    @Test
-    public void lowerIndexPriority() {
-        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
-        int oldLength = table.length();
-        table.put("bender", "rodriguez");
-        table.put("bender", "rodriguez");
-        table.put("bender", "rodriguez");
-
-        assertEquals(table.length(), oldLength + 3); // more like an assumption
-        int i = table.indexOf("bender", "rodriguez");
-        assertEquals(oldLength + 1, i);
-    }
-
-    @Test
-    public void lowerIndexPriority2() {
-        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
-        int oldLength = table.length();
-        int idx = rnd.nextInt(oldLength) + 1;
-        HeaderField f = table.get(idx);
-        table.put(f.name, f.value);
-        assertEquals(table.length(), oldLength + 1);
-        int i = table.indexOf(f.name, f.value);
-        assertEquals(idx, i);
-    }
-
-    // TODO: negative indexes check
-    // TODO: ensure full table clearance when adding huge header field
-    // TODO: ensure eviction deletes minimum needed entries, not more
-
-    @Test
-    public void fifo() {
-        // Let's add a series of header fields
-        int NUM_HEADERS = 32;
-        HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
-        //                                ^   ^
-        //                   entry overhead   symbols per entry (max 2x2 digits)
-        for (int i = 1; i <= NUM_HEADERS; i++) {
-            String s = String.valueOf(i);
-            t.put(s, s);
-        }
-        // They MUST appear in a FIFO order:
-        //   newer entries are at lower indexes
-        //   older entries are at higher indexes
-        for (int j = 1; j <= NUM_HEADERS; j++) {
-            HeaderField f = t.get(STATIC_TABLE_LENGTH + j);
-            int actualName = Integer.parseInt(f.name);
-            int expectedName = NUM_HEADERS - j + 1;
-            assertEquals(expectedName, actualName);
-        }
-        // Entries MUST be evicted in the order they were added:
-        //   the newer the entry the later it is evicted
-        for (int k = 1; k <= NUM_HEADERS; k++) {
-            HeaderField f = t.evictEntry();
-            assertEquals(String.valueOf(k), f.name);
-        }
-    }
-
-    @Test
-    public void indexOf() {
-        // Let's put a series of header fields
-        int NUM_HEADERS = 32;
-        HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
-        //                                ^   ^
-        //                   entry overhead   symbols per entry (max 2x2 digits)
-        for (int i = 1; i <= NUM_HEADERS; i++) {
-            String s = String.valueOf(i);
-            t.put(s, s);
-        }
-        // and verify indexOf (reverse lookup) returns correct indexes for
-        // full lookup
-        for (int j = 1; j <= NUM_HEADERS; j++) {
-            String s = String.valueOf(j);
-            int actualIndex = t.indexOf(s, s);
-            int expectedIndex = STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1;
-            assertEquals(expectedIndex, actualIndex);
-        }
-        // as well as for just a name lookup
-        for (int j = 1; j <= NUM_HEADERS; j++) {
-            String s = String.valueOf(j);
-            int actualIndex = t.indexOf(s, "blah");
-            int expectedIndex = -(STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1);
-            assertEquals(expectedIndex, actualIndex);
-        }
-        // lookup for non-existent name returns 0
-        assertEquals(0, t.indexOf("chupacabra", "1"));
-    }
-
-    @Test
-    public void testToString() {
-        testToString0();
-    }
-
-    @Test
-    public void testToStringDifferentLocale() {
-        Locale locale = Locale.getDefault();
-        Locale.setDefault(Locale.FRENCH);
-        try {
-            String s = format("%.1f", 3.1);
-            assertEquals("3,1", s); // assumption of the test, otherwise the test is useless
-            testToString0();
-        } finally {
-            Locale.setDefault(locale);
-        }
-    }
-
-    private void testToString0() {
-        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
-        {
-            int maxSize = 2048;
-            table.setMaxSize(maxSize);
-            String expected = format(
-                    "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
-                    0, STATIC_TABLE_LENGTH, 0, maxSize, 0.0);
-            assertEquals(expected, table.toString());
-        }
-
-        {
-            String name = "custom-name";
-            String value = "custom-value";
-            int size = 512;
-
-            table.setMaxSize(size);
-            table.put(name, value);
-            String s = table.toString();
-
-            int used = name.length() + value.length() + 32;
-            double ratio = used * 100.0 / size;
-
-            String expected = format(
-                    "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
-                    1, STATIC_TABLE_LENGTH + 1, used, size, ratio);
-            assertEquals(expected, s);
-        }
-
-        {
-            table.setMaxSize(78);
-            table.put(":method", "");
-            table.put(":status", "");
-            String s = table.toString();
-            String expected =
-                    format("dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
-                           2, STATIC_TABLE_LENGTH + 2, 78, 78, 100.0);
-            assertEquals(expected, s);
-        }
-    }
-
-    @Test
-    public void stateString() {
-        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
-        table.put("custom-key", "custom-header");
-        // @formatter:off
-        assertEquals("[  1] (s =  55) custom-key: custom-header\n" +
-                     "      Table size:  55", table.getStateString());
-        // @formatter:on
-    }
-
-    private static Map<Integer, HeaderField> createStaticEntries() {
-        Pattern line = Pattern.compile(
-                "\\|\\s*(?<index>\\d+?)\\s*\\|\\s*(?<name>.+?)\\s*\\|\\s*(?<value>.*?)\\s*\\|");
-        Matcher m = line.matcher(SPEC);
-        Map<Integer, HeaderField> result = new HashMap<>();
-        while (m.find()) {
-            int index = Integer.parseInt(m.group("index"));
-            String name = m.group("name");
-            String value = m.group("value");
-            HeaderField f = new HeaderField(name, value);
-            result.put(index, f);
-        }
-        return Collections.unmodifiableMap(result); // lol
-    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/HuffmanTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,629 +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 java.net.http.internal.hpack;
-
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.util.Stack;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static java.lang.Integer.parseInt;
-import static org.testng.Assert.*;
-
-public final class HuffmanTest {
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-B
-    //
-    private static final String SPEC =
-            // @formatter:off
-     "                          code as bits                 as hex   len\n" +
-     "        sym              aligned to MSB                aligned   in\n" +
-     "                                                       to LSB   bits\n" +
-     "       (  0)  |11111111|11000                             1ff8  [13]\n" +
-     "       (  1)  |11111111|11111111|1011000                7fffd8  [23]\n" +
-     "       (  2)  |11111111|11111111|11111110|0010         fffffe2  [28]\n" +
-     "       (  3)  |11111111|11111111|11111110|0011         fffffe3  [28]\n" +
-     "       (  4)  |11111111|11111111|11111110|0100         fffffe4  [28]\n" +
-     "       (  5)  |11111111|11111111|11111110|0101         fffffe5  [28]\n" +
-     "       (  6)  |11111111|11111111|11111110|0110         fffffe6  [28]\n" +
-     "       (  7)  |11111111|11111111|11111110|0111         fffffe7  [28]\n" +
-     "       (  8)  |11111111|11111111|11111110|1000         fffffe8  [28]\n" +
-     "       (  9)  |11111111|11111111|11101010               ffffea  [24]\n" +
-     "       ( 10)  |11111111|11111111|11111111|111100      3ffffffc  [30]\n" +
-     "       ( 11)  |11111111|11111111|11111110|1001         fffffe9  [28]\n" +
-     "       ( 12)  |11111111|11111111|11111110|1010         fffffea  [28]\n" +
-     "       ( 13)  |11111111|11111111|11111111|111101      3ffffffd  [30]\n" +
-     "       ( 14)  |11111111|11111111|11111110|1011         fffffeb  [28]\n" +
-     "       ( 15)  |11111111|11111111|11111110|1100         fffffec  [28]\n" +
-     "       ( 16)  |11111111|11111111|11111110|1101         fffffed  [28]\n" +
-     "       ( 17)  |11111111|11111111|11111110|1110         fffffee  [28]\n" +
-     "       ( 18)  |11111111|11111111|11111110|1111         fffffef  [28]\n" +
-     "       ( 19)  |11111111|11111111|11111111|0000         ffffff0  [28]\n" +
-     "       ( 20)  |11111111|11111111|11111111|0001         ffffff1  [28]\n" +
-     "       ( 21)  |11111111|11111111|11111111|0010         ffffff2  [28]\n" +
-     "       ( 22)  |11111111|11111111|11111111|111110      3ffffffe  [30]\n" +
-     "       ( 23)  |11111111|11111111|11111111|0011         ffffff3  [28]\n" +
-     "       ( 24)  |11111111|11111111|11111111|0100         ffffff4  [28]\n" +
-     "       ( 25)  |11111111|11111111|11111111|0101         ffffff5  [28]\n" +
-     "       ( 26)  |11111111|11111111|11111111|0110         ffffff6  [28]\n" +
-     "       ( 27)  |11111111|11111111|11111111|0111         ffffff7  [28]\n" +
-     "       ( 28)  |11111111|11111111|11111111|1000         ffffff8  [28]\n" +
-     "       ( 29)  |11111111|11111111|11111111|1001         ffffff9  [28]\n" +
-     "       ( 30)  |11111111|11111111|11111111|1010         ffffffa  [28]\n" +
-     "       ( 31)  |11111111|11111111|11111111|1011         ffffffb  [28]\n" +
-     "   ' ' ( 32)  |010100                                       14  [ 6]\n" +
-     "   '!' ( 33)  |11111110|00                                 3f8  [10]\n" +
-     "  '\"' ( 34)  |11111110|01                                 3f9  [10]\n" +
-     "   '#' ( 35)  |11111111|1010                               ffa  [12]\n" +
-     "   '$' ( 36)  |11111111|11001                             1ff9  [13]\n" +
-     "   '%' ( 37)  |010101                                       15  [ 6]\n" +
-     "   '&' ( 38)  |11111000                                     f8  [ 8]\n" +
-     "   ''' ( 39)  |11111111|010                                7fa  [11]\n" +
-     "   '(' ( 40)  |11111110|10                                 3fa  [10]\n" +
-     "   ')' ( 41)  |11111110|11                                 3fb  [10]\n" +
-     "   '*' ( 42)  |11111001                                     f9  [ 8]\n" +
-     "   '+' ( 43)  |11111111|011                                7fb  [11]\n" +
-     "   ',' ( 44)  |11111010                                     fa  [ 8]\n" +
-     "   '-' ( 45)  |010110                                       16  [ 6]\n" +
-     "   '.' ( 46)  |010111                                       17  [ 6]\n" +
-     "   '/' ( 47)  |011000                                       18  [ 6]\n" +
-     "   '0' ( 48)  |00000                                         0  [ 5]\n" +
-     "   '1' ( 49)  |00001                                         1  [ 5]\n" +
-     "   '2' ( 50)  |00010                                         2  [ 5]\n" +
-     "   '3' ( 51)  |011001                                       19  [ 6]\n" +
-     "   '4' ( 52)  |011010                                       1a  [ 6]\n" +
-     "   '5' ( 53)  |011011                                       1b  [ 6]\n" +
-     "   '6' ( 54)  |011100                                       1c  [ 6]\n" +
-     "   '7' ( 55)  |011101                                       1d  [ 6]\n" +
-     "   '8' ( 56)  |011110                                       1e  [ 6]\n" +
-     "   '9' ( 57)  |011111                                       1f  [ 6]\n" +
-     "   ':' ( 58)  |1011100                                      5c  [ 7]\n" +
-     "   ';' ( 59)  |11111011                                     fb  [ 8]\n" +
-     "   '<' ( 60)  |11111111|1111100                           7ffc  [15]\n" +
-     "   '=' ( 61)  |100000                                       20  [ 6]\n" +
-     "   '>' ( 62)  |11111111|1011                               ffb  [12]\n" +
-     "   '?' ( 63)  |11111111|00                                 3fc  [10]\n" +
-     "   '@' ( 64)  |11111111|11010                             1ffa  [13]\n" +
-     "   'A' ( 65)  |100001                                       21  [ 6]\n" +
-     "   'B' ( 66)  |1011101                                      5d  [ 7]\n" +
-     "   'C' ( 67)  |1011110                                      5e  [ 7]\n" +
-     "   'D' ( 68)  |1011111                                      5f  [ 7]\n" +
-     "   'E' ( 69)  |1100000                                      60  [ 7]\n" +
-     "   'F' ( 70)  |1100001                                      61  [ 7]\n" +
-     "   'G' ( 71)  |1100010                                      62  [ 7]\n" +
-     "   'H' ( 72)  |1100011                                      63  [ 7]\n" +
-     "   'I' ( 73)  |1100100                                      64  [ 7]\n" +
-     "   'J' ( 74)  |1100101                                      65  [ 7]\n" +
-     "   'K' ( 75)  |1100110                                      66  [ 7]\n" +
-     "   'L' ( 76)  |1100111                                      67  [ 7]\n" +
-     "   'M' ( 77)  |1101000                                      68  [ 7]\n" +
-     "   'N' ( 78)  |1101001                                      69  [ 7]\n" +
-     "   'O' ( 79)  |1101010                                      6a  [ 7]\n" +
-     "   'P' ( 80)  |1101011                                      6b  [ 7]\n" +
-     "   'Q' ( 81)  |1101100                                      6c  [ 7]\n" +
-     "   'R' ( 82)  |1101101                                      6d  [ 7]\n" +
-     "   'S' ( 83)  |1101110                                      6e  [ 7]\n" +
-     "   'T' ( 84)  |1101111                                      6f  [ 7]\n" +
-     "   'U' ( 85)  |1110000                                      70  [ 7]\n" +
-     "   'V' ( 86)  |1110001                                      71  [ 7]\n" +
-     "   'W' ( 87)  |1110010                                      72  [ 7]\n" +
-     "   'X' ( 88)  |11111100                                     fc  [ 8]\n" +
-     "   'Y' ( 89)  |1110011                                      73  [ 7]\n" +
-     "   'Z' ( 90)  |11111101                                     fd  [ 8]\n" +
-     "   '[' ( 91)  |11111111|11011                             1ffb  [13]\n" +
-     "  '\\' ( 92)  |11111111|11111110|000                     7fff0  [19]\n" +
-     "   ']' ( 93)  |11111111|11100                             1ffc  [13]\n" +
-     "   '^' ( 94)  |11111111|111100                            3ffc  [14]\n" +
-     "   '_' ( 95)  |100010                                       22  [ 6]\n" +
-     "   '`' ( 96)  |11111111|1111101                           7ffd  [15]\n" +
-     "   'a' ( 97)  |00011                                         3  [ 5]\n" +
-     "   'b' ( 98)  |100011                                       23  [ 6]\n" +
-     "   'c' ( 99)  |00100                                         4  [ 5]\n" +
-     "   'd' (100)  |100100                                       24  [ 6]\n" +
-     "   'e' (101)  |00101                                         5  [ 5]\n" +
-     "   'f' (102)  |100101                                       25  [ 6]\n" +
-     "   'g' (103)  |100110                                       26  [ 6]\n" +
-     "   'h' (104)  |100111                                       27  [ 6]\n" +
-     "   'i' (105)  |00110                                         6  [ 5]\n" +
-     "   'j' (106)  |1110100                                      74  [ 7]\n" +
-     "   'k' (107)  |1110101                                      75  [ 7]\n" +
-     "   'l' (108)  |101000                                       28  [ 6]\n" +
-     "   'm' (109)  |101001                                       29  [ 6]\n" +
-     "   'n' (110)  |101010                                       2a  [ 6]\n" +
-     "   'o' (111)  |00111                                         7  [ 5]\n" +
-     "   'p' (112)  |101011                                       2b  [ 6]\n" +
-     "   'q' (113)  |1110110                                      76  [ 7]\n" +
-     "   'r' (114)  |101100                                       2c  [ 6]\n" +
-     "   's' (115)  |01000                                         8  [ 5]\n" +
-     "   't' (116)  |01001                                         9  [ 5]\n" +
-     "   'u' (117)  |101101                                       2d  [ 6]\n" +
-     "   'v' (118)  |1110111                                      77  [ 7]\n" +
-     "   'w' (119)  |1111000                                      78  [ 7]\n" +
-     "   'x' (120)  |1111001                                      79  [ 7]\n" +
-     "   'y' (121)  |1111010                                      7a  [ 7]\n" +
-     "   'z' (122)  |1111011                                      7b  [ 7]\n" +
-     "   '{' (123)  |11111111|1111110                           7ffe  [15]\n" +
-     "   '|' (124)  |11111111|100                                7fc  [11]\n" +
-     "   '}' (125)  |11111111|111101                            3ffd  [14]\n" +
-     "   '~' (126)  |11111111|11101                             1ffd  [13]\n" +
-     "       (127)  |11111111|11111111|11111111|1100         ffffffc  [28]\n" +
-     "       (128)  |11111111|11111110|0110                    fffe6  [20]\n" +
-     "       (129)  |11111111|11111111|010010                 3fffd2  [22]\n" +
-     "       (130)  |11111111|11111110|0111                    fffe7  [20]\n" +
-     "       (131)  |11111111|11111110|1000                    fffe8  [20]\n" +
-     "       (132)  |11111111|11111111|010011                 3fffd3  [22]\n" +
-     "       (133)  |11111111|11111111|010100                 3fffd4  [22]\n" +
-     "       (134)  |11111111|11111111|010101                 3fffd5  [22]\n" +
-     "       (135)  |11111111|11111111|1011001                7fffd9  [23]\n" +
-     "       (136)  |11111111|11111111|010110                 3fffd6  [22]\n" +
-     "       (137)  |11111111|11111111|1011010                7fffda  [23]\n" +
-     "       (138)  |11111111|11111111|1011011                7fffdb  [23]\n" +
-     "       (139)  |11111111|11111111|1011100                7fffdc  [23]\n" +
-     "       (140)  |11111111|11111111|1011101                7fffdd  [23]\n" +
-     "       (141)  |11111111|11111111|1011110                7fffde  [23]\n" +
-     "       (142)  |11111111|11111111|11101011               ffffeb  [24]\n" +
-     "       (143)  |11111111|11111111|1011111                7fffdf  [23]\n" +
-     "       (144)  |11111111|11111111|11101100               ffffec  [24]\n" +
-     "       (145)  |11111111|11111111|11101101               ffffed  [24]\n" +
-     "       (146)  |11111111|11111111|010111                 3fffd7  [22]\n" +
-     "       (147)  |11111111|11111111|1100000                7fffe0  [23]\n" +
-     "       (148)  |11111111|11111111|11101110               ffffee  [24]\n" +
-     "       (149)  |11111111|11111111|1100001                7fffe1  [23]\n" +
-     "       (150)  |11111111|11111111|1100010                7fffe2  [23]\n" +
-     "       (151)  |11111111|11111111|1100011                7fffe3  [23]\n" +
-     "       (152)  |11111111|11111111|1100100                7fffe4  [23]\n" +
-     "       (153)  |11111111|11111110|11100                  1fffdc  [21]\n" +
-     "       (154)  |11111111|11111111|011000                 3fffd8  [22]\n" +
-     "       (155)  |11111111|11111111|1100101                7fffe5  [23]\n" +
-     "       (156)  |11111111|11111111|011001                 3fffd9  [22]\n" +
-     "       (157)  |11111111|11111111|1100110                7fffe6  [23]\n" +
-     "       (158)  |11111111|11111111|1100111                7fffe7  [23]\n" +
-     "       (159)  |11111111|11111111|11101111               ffffef  [24]\n" +
-     "       (160)  |11111111|11111111|011010                 3fffda  [22]\n" +
-     "       (161)  |11111111|11111110|11101                  1fffdd  [21]\n" +
-     "       (162)  |11111111|11111110|1001                    fffe9  [20]\n" +
-     "       (163)  |11111111|11111111|011011                 3fffdb  [22]\n" +
-     "       (164)  |11111111|11111111|011100                 3fffdc  [22]\n" +
-     "       (165)  |11111111|11111111|1101000                7fffe8  [23]\n" +
-     "       (166)  |11111111|11111111|1101001                7fffe9  [23]\n" +
-     "       (167)  |11111111|11111110|11110                  1fffde  [21]\n" +
-     "       (168)  |11111111|11111111|1101010                7fffea  [23]\n" +
-     "       (169)  |11111111|11111111|011101                 3fffdd  [22]\n" +
-     "       (170)  |11111111|11111111|011110                 3fffde  [22]\n" +
-     "       (171)  |11111111|11111111|11110000               fffff0  [24]\n" +
-     "       (172)  |11111111|11111110|11111                  1fffdf  [21]\n" +
-     "       (173)  |11111111|11111111|011111                 3fffdf  [22]\n" +
-     "       (174)  |11111111|11111111|1101011                7fffeb  [23]\n" +
-     "       (175)  |11111111|11111111|1101100                7fffec  [23]\n" +
-     "       (176)  |11111111|11111111|00000                  1fffe0  [21]\n" +
-     "       (177)  |11111111|11111111|00001                  1fffe1  [21]\n" +
-     "       (178)  |11111111|11111111|100000                 3fffe0  [22]\n" +
-     "       (179)  |11111111|11111111|00010                  1fffe2  [21]\n" +
-     "       (180)  |11111111|11111111|1101101                7fffed  [23]\n" +
-     "       (181)  |11111111|11111111|100001                 3fffe1  [22]\n" +
-     "       (182)  |11111111|11111111|1101110                7fffee  [23]\n" +
-     "       (183)  |11111111|11111111|1101111                7fffef  [23]\n" +
-     "       (184)  |11111111|11111110|1010                    fffea  [20]\n" +
-     "       (185)  |11111111|11111111|100010                 3fffe2  [22]\n" +
-     "       (186)  |11111111|11111111|100011                 3fffe3  [22]\n" +
-     "       (187)  |11111111|11111111|100100                 3fffe4  [22]\n" +
-     "       (188)  |11111111|11111111|1110000                7ffff0  [23]\n" +
-     "       (189)  |11111111|11111111|100101                 3fffe5  [22]\n" +
-     "       (190)  |11111111|11111111|100110                 3fffe6  [22]\n" +
-     "       (191)  |11111111|11111111|1110001                7ffff1  [23]\n" +
-     "       (192)  |11111111|11111111|11111000|00           3ffffe0  [26]\n" +
-     "       (193)  |11111111|11111111|11111000|01           3ffffe1  [26]\n" +
-     "       (194)  |11111111|11111110|1011                    fffeb  [20]\n" +
-     "       (195)  |11111111|11111110|001                     7fff1  [19]\n" +
-     "       (196)  |11111111|11111111|100111                 3fffe7  [22]\n" +
-     "       (197)  |11111111|11111111|1110010                7ffff2  [23]\n" +
-     "       (198)  |11111111|11111111|101000                 3fffe8  [22]\n" +
-     "       (199)  |11111111|11111111|11110110|0            1ffffec  [25]\n" +
-     "       (200)  |11111111|11111111|11111000|10           3ffffe2  [26]\n" +
-     "       (201)  |11111111|11111111|11111000|11           3ffffe3  [26]\n" +
-     "       (202)  |11111111|11111111|11111001|00           3ffffe4  [26]\n" +
-     "       (203)  |11111111|11111111|11111011|110          7ffffde  [27]\n" +
-     "       (204)  |11111111|11111111|11111011|111          7ffffdf  [27]\n" +
-     "       (205)  |11111111|11111111|11111001|01           3ffffe5  [26]\n" +
-     "       (206)  |11111111|11111111|11110001               fffff1  [24]\n" +
-     "       (207)  |11111111|11111111|11110110|1            1ffffed  [25]\n" +
-     "       (208)  |11111111|11111110|010                     7fff2  [19]\n" +
-     "       (209)  |11111111|11111111|00011                  1fffe3  [21]\n" +
-     "       (210)  |11111111|11111111|11111001|10           3ffffe6  [26]\n" +
-     "       (211)  |11111111|11111111|11111100|000          7ffffe0  [27]\n" +
-     "       (212)  |11111111|11111111|11111100|001          7ffffe1  [27]\n" +
-     "       (213)  |11111111|11111111|11111001|11           3ffffe7  [26]\n" +
-     "       (214)  |11111111|11111111|11111100|010          7ffffe2  [27]\n" +
-     "       (215)  |11111111|11111111|11110010               fffff2  [24]\n" +
-     "       (216)  |11111111|11111111|00100                  1fffe4  [21]\n" +
-     "       (217)  |11111111|11111111|00101                  1fffe5  [21]\n" +
-     "       (218)  |11111111|11111111|11111010|00           3ffffe8  [26]\n" +
-     "       (219)  |11111111|11111111|11111010|01           3ffffe9  [26]\n" +
-     "       (220)  |11111111|11111111|11111111|1101         ffffffd  [28]\n" +
-     "       (221)  |11111111|11111111|11111100|011          7ffffe3  [27]\n" +
-     "       (222)  |11111111|11111111|11111100|100          7ffffe4  [27]\n" +
-     "       (223)  |11111111|11111111|11111100|101          7ffffe5  [27]\n" +
-     "       (224)  |11111111|11111110|1100                    fffec  [20]\n" +
-     "       (225)  |11111111|11111111|11110011               fffff3  [24]\n" +
-     "       (226)  |11111111|11111110|1101                    fffed  [20]\n" +
-     "       (227)  |11111111|11111111|00110                  1fffe6  [21]\n" +
-     "       (228)  |11111111|11111111|101001                 3fffe9  [22]\n" +
-     "       (229)  |11111111|11111111|00111                  1fffe7  [21]\n" +
-     "       (230)  |11111111|11111111|01000                  1fffe8  [21]\n" +
-     "       (231)  |11111111|11111111|1110011                7ffff3  [23]\n" +
-     "       (232)  |11111111|11111111|101010                 3fffea  [22]\n" +
-     "       (233)  |11111111|11111111|101011                 3fffeb  [22]\n" +
-     "       (234)  |11111111|11111111|11110111|0            1ffffee  [25]\n" +
-     "       (235)  |11111111|11111111|11110111|1            1ffffef  [25]\n" +
-     "       (236)  |11111111|11111111|11110100               fffff4  [24]\n" +
-     "       (237)  |11111111|11111111|11110101               fffff5  [24]\n" +
-     "       (238)  |11111111|11111111|11111010|10           3ffffea  [26]\n" +
-     "       (239)  |11111111|11111111|1110100                7ffff4  [23]\n" +
-     "       (240)  |11111111|11111111|11111010|11           3ffffeb  [26]\n" +
-     "       (241)  |11111111|11111111|11111100|110          7ffffe6  [27]\n" +
-     "       (242)  |11111111|11111111|11111011|00           3ffffec  [26]\n" +
-     "       (243)  |11111111|11111111|11111011|01           3ffffed  [26]\n" +
-     "       (244)  |11111111|11111111|11111100|111          7ffffe7  [27]\n" +
-     "       (245)  |11111111|11111111|11111101|000          7ffffe8  [27]\n" +
-     "       (246)  |11111111|11111111|11111101|001          7ffffe9  [27]\n" +
-     "       (247)  |11111111|11111111|11111101|010          7ffffea  [27]\n" +
-     "       (248)  |11111111|11111111|11111101|011          7ffffeb  [27]\n" +
-     "       (249)  |11111111|11111111|11111111|1110         ffffffe  [28]\n" +
-     "       (250)  |11111111|11111111|11111101|100          7ffffec  [27]\n" +
-     "       (251)  |11111111|11111111|11111101|101          7ffffed  [27]\n" +
-     "       (252)  |11111111|11111111|11111101|110          7ffffee  [27]\n" +
-     "       (253)  |11111111|11111111|11111101|111          7ffffef  [27]\n" +
-     "       (254)  |11111111|11111111|11111110|000          7fffff0  [27]\n" +
-     "       (255)  |11111111|11111111|11111011|10           3ffffee  [26]\n" +
-     "   EOS (256)  |11111111|11111111|11111111|111111      3fffffff  [30]";
-    // @formatter:on
-
-    @Test
-    public void read_table() throws IOException {
-        Pattern line = Pattern.compile(
-                "\\(\\s*(?<ascii>\\d+)\\s*\\)\\s*(?<binary>(\\|(0|1)+)+)\\s*" +
-                        "(?<hex>[0-9a-zA-Z]+)\\s*\\[\\s*(?<len>\\d+)\\s*\\]");
-        Matcher m = line.matcher(SPEC);
-        int i = 0;
-        while (m.find()) {
-            String ascii = m.group("ascii");
-            String binary = m.group("binary").replaceAll("\\|", "");
-            String hex = m.group("hex");
-            String len = m.group("len");
-
-            // Several sanity checks for the data read from the table, just to
-            // make sure what we read makes sense
-            assertEquals(parseInt(len), binary.length());
-            assertEquals(parseInt(binary, 2), parseInt(hex, 16));
-
-            int expected = parseInt(ascii);
-
-            // TODO: find actual eos, do not hardcode it!
-            byte[] bytes = intToBytes(0x3fffffff, 30,
-                    parseInt(hex, 16), parseInt(len));
-
-            StringBuilder actual = new StringBuilder();
-            Huffman.Reader t = new Huffman.Reader();
-            t.read(ByteBuffer.wrap(bytes), actual, false, true);
-
-            // What has been read MUST represent a single symbol
-            assertEquals(actual.length(), 1, "ascii: " + ascii);
-
-            // It's a lot more visual to compare char as codes rather than
-            // characters (as some of them might not be visible)
-            assertEquals(actual.charAt(0), expected);
-            i++;
-        }
-        assertEquals(i, 257); // 256 + EOS
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.4.1
-    //
-    @Test
-    public void read_1() {
-        read("f1e3 c2e5 f23a 6ba0 ab90 f4ff", "www.example.com");
-    }
-
-    @Test
-    public void write_1() {
-        write("www.example.com", "f1e3 c2e5 f23a 6ba0 ab90 f4ff");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.4.2
-    //
-    @Test
-    public void read_2() {
-        read("a8eb 1064 9cbf", "no-cache");
-    }
-
-    @Test
-    public void write_2() {
-        write("no-cache", "a8eb 1064 9cbf");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.4.3
-    //
-    @Test
-    public void read_3() {
-        read("25a8 49e9 5ba9 7d7f", "custom-key");
-    }
-
-    @Test
-    public void write_3() {
-        write("custom-key", "25a8 49e9 5ba9 7d7f");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.4.3
-    //
-    @Test
-    public void read_4() {
-        read("25a8 49e9 5bb8 e8b4 bf", "custom-value");
-    }
-
-    @Test
-    public void write_4() {
-        write("custom-value", "25a8 49e9 5bb8 e8b4 bf");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
-    //
-    @Test
-    public void read_5() {
-        read("6402", "302");
-    }
-
-    @Test
-    public void write_5() {
-        write("302", "6402");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
-    //
-    @Test
-    public void read_6() {
-        read("aec3 771a 4b", "private");
-    }
-
-    @Test
-    public void write_6() {
-        write("private", "aec3 771a 4b");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
-    //
-    @Test
-    public void read_7() {
-        read("d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff",
-                "Mon, 21 Oct 2013 20:13:21 GMT");
-    }
-
-    @Test
-    public void write_7() {
-        write("Mon, 21 Oct 2013 20:13:21 GMT",
-                "d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
-    //
-    @Test
-    public void read_8() {
-        read("9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3",
-                "https://www.example.com");
-    }
-
-    @Test
-    public void write_8() {
-        write("https://www.example.com",
-                "9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.2
-    //
-    @Test
-    public void read_9() {
-        read("640e ff", "307");
-    }
-
-    @Test
-    public void write_9() {
-        write("307", "640e ff");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
-    //
-    @Test
-    public void read_10() {
-        read("d07a be94 1054 d444 a820 0595 040b 8166 e084 a62d 1bff",
-                "Mon, 21 Oct 2013 20:13:22 GMT");
-    }
-
-    @Test
-    public void write_10() {
-        write("Mon, 21 Oct 2013 20:13:22 GMT",
-                "d07a be94 1054 d444 a820 0595 040b 8166 e084 a62d 1bff");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
-    //
-    @Test
-    public void read_11() {
-        read("9bd9 ab", "gzip");
-    }
-
-    @Test
-    public void write_11() {
-        write("gzip", "9bd9 ab");
-    }
-
-    //
-    // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
-    //
-    @Test
-    public void read_12() {
-        read("94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 " +
-             "d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 " +
-             "3160 65c0 03ed 4ee5 b106 3d50 07",
-             "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
-    }
-
-    @Test
-    public void test_trie_has_no_empty_nodes() {
-        Huffman.Node root = Huffman.INSTANCE.getRoot();
-        Stack<Huffman.Node> backlog = new Stack<>();
-        backlog.push(root);
-        while (!backlog.isEmpty()) {
-            Huffman.Node n = backlog.pop();
-            // The only type of nodes we couldn't possibly catch during
-            // construction is an empty node: no children and no char
-            if (n.left != null) {
-                backlog.push(n.left);
-            }
-            if (n.right != null) {
-                backlog.push(n.right);
-            }
-            assertFalse(!n.charIsSet && n.left == null && n.right == null,
-                    "Empty node in the trie");
-        }
-    }
-
-    @Test
-    public void test_trie_has_257_nodes() {
-        int count = 0;
-        Huffman.Node root = Huffman.INSTANCE.getRoot();
-        Stack<Huffman.Node> backlog = new Stack<>();
-        backlog.push(root);
-        while (!backlog.isEmpty()) {
-            Huffman.Node n = backlog.pop();
-            if (n.left != null) {
-                backlog.push(n.left);
-            }
-            if (n.right != null) {
-                backlog.push(n.right);
-            }
-            if (n.isLeaf()) {
-                count++;
-            }
-        }
-        assertEquals(count, 257);
-    }
-
-    @Test
-    public void cant_encode_outside_byte() {
-        TestHelper.Block<Object> coding =
-                () -> new Huffman.Writer()
-                        .from(((char) 256) + "", 0, 1)
-                        .write(ByteBuffer.allocate(1));
-        RuntimeException e =
-                TestHelper.assertVoidThrows(RuntimeException.class, coding);
-        TestHelper.assertExceptionMessageContains(e, "char");
-    }
-
-    private static void read(String hexdump, String decoded) {
-        ByteBuffer source = SpecHelper.toBytes(hexdump);
-        Appendable actual = new StringBuilder();
-        try {
-            new Huffman.Reader().read(source, actual, true);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        assertEquals(actual.toString(), decoded);
-    }
-
-    private static void write(String decoded, String hexdump) {
-        int n = Huffman.INSTANCE.lengthOf(decoded);
-        ByteBuffer destination = ByteBuffer.allocate(n); // Extra margin (1) to test having more bytes in the destination than needed is ok
-        Huffman.Writer writer = new Huffman.Writer();
-        BuffersTestingKit.forEachSplit(destination, byteBuffers -> {
-            writer.from(decoded, 0, decoded.length());
-            boolean written = false;
-            for (ByteBuffer b : byteBuffers) {
-                int pos = b.position();
-                written = writer.write(b);
-                b.position(pos);
-            }
-            assertTrue(written);
-            ByteBuffer concated = BuffersTestingKit.concat(byteBuffers);
-            String actual = SpecHelper.toHexdump(concated);
-            assertEquals(actual, hexdump);
-            writer.reset();
-        });
-    }
-
-    //
-    // It's not very pretty, yes I know that
-    //
-    //      hex:
-    //
-    //      |31|30|...|N-1|...|01|00|
-    //                  \        /
-    //                  codeLength
-    //
-    //      hex <<= 32 - codeLength; (align to MSB):
-    //
-    //      |31|30|...|32-N|...|01|00|
-    //        \        /
-    //        codeLength
-    //
-    //      EOS:
-    //
-    //      |31|30|...|M-1|...|01|00|
-    //                   \        /
-    //                   eosLength
-    //
-    //      eos <<= 32 - eosLength; (align to MSB):
-    //
-    //      pad with MSBs of EOS:
-    //
-    //      |31|30|...|32-N|32-N-1|...|01|00|
-    //                     |    32|...|
-    //
-    //      Finally, split into byte[]
-    //
-    private byte[] intToBytes(int eos, int eosLength, int hex, int codeLength) {
-        hex <<= 32 - codeLength;
-        eos >>= codeLength - (32 - eosLength);
-        hex |= eos;
-        int n = (int) Math.ceil(codeLength / 8.0);
-        byte[] result = new byte[n];
-        for (int i = 0; i < n; i++) {
-            result[i] = (byte) (hex >> (32 - 8 * (i + 1)));
-        }
-        return result;
-    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/SpecHelper.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-//
-// THIS IS NOT A TEST
-//
-public final class SpecHelper {
-
-    private SpecHelper() {
-        throw new AssertionError();
-    }
-
-    public static ByteBuffer toBytes(String hexdump) {
-        Pattern hexByte = Pattern.compile("[0-9a-fA-F]{2}");
-        List<String> bytes = new ArrayList<>();
-        Matcher matcher = hexByte.matcher(hexdump);
-        while (matcher.find()) {
-            bytes.add(matcher.group(0));
-        }
-        ByteBuffer result = ByteBuffer.allocate(bytes.size());
-        for (String f : bytes) {
-            result.put((byte) Integer.parseInt(f, 16));
-        }
-        result.flip();
-        return result;
-    }
-
-    public static String toHexdump(ByteBuffer bb) {
-        List<String> words = new ArrayList<>();
-        int i = 0;
-        while (bb.hasRemaining()) {
-            if (i % 2 == 0) {
-                words.add("");
-            }
-            byte b = bb.get();
-            String hex = Integer.toHexString(256 + Byte.toUnsignedInt(b)).substring(1);
-            words.set(i / 2, words.get(i / 2) + hex);
-            i++;
-        }
-        return words.stream().collect(Collectors.joining(" "));
-    }
-}
--- a/test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/TestHelper.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-/*
- * Copyright (c) 2014, 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 java.net.http.internal.hpack;
-
-import org.testng.annotations.Test;
-
-import java.util.Objects;
-import java.util.Random;
-
-public final class TestHelper {
-
-    public static Random newRandom() {
-        long seed = Long.getLong("jdk.test.lib.random.seed", System.currentTimeMillis());
-        System.out.println("new java.util.Random(" + seed + ")");
-        return new Random(seed);
-    }
-
-    public static <T extends Throwable> T assertVoidThrows(Class<T> clazz, Block<?> code) {
-        return assertThrows(clazz, () -> {
-            code.run();
-            return null;
-        });
-    }
-
-    public static <T extends Throwable> T assertThrows(Class<T> clazz, ReturningBlock<?> code) {
-        Objects.requireNonNull(clazz, "clazz == null");
-        Objects.requireNonNull(code, "code == null");
-        try {
-            code.run();
-        } catch (Throwable t) {
-            if (clazz.isInstance(t)) {
-                return clazz.cast(t);
-            }
-            throw new AssertionError("Expected to catch exception of type "
-                    + clazz.getCanonicalName() + ", instead caught "
-                    + t.getClass().getCanonicalName(), t);
-
-        }
-        throw new AssertionError(
-                "Expected to catch exception of type " + clazz.getCanonicalName()
-                        + ", but caught nothing");
-    }
-
-    public static <T> T assertDoesNotThrow(ReturningBlock<T> code) {
-        Objects.requireNonNull(code, "code == null");
-        try {
-            return code.run();
-        } catch (Throwable t) {
-            throw new AssertionError(
-                    "Expected code block to exit normally, instead " +
-                            "caught " + t.getClass().getCanonicalName(), t);
-        }
-    }
-
-    public static void assertVoidDoesNotThrow(Block<?> code) {
-        Objects.requireNonNull(code, "code == null");
-        try {
-            code.run();
-        } catch (Throwable t) {
-            throw new AssertionError(
-                    "Expected code block to exit normally, instead " +
-                            "caught " + t.getClass().getCanonicalName(), t);
-        }
-    }
-
-
-    public static void assertExceptionMessageContains(Throwable t,
-                                                      CharSequence firstSubsequence,
-                                                      CharSequence... others) {
-        assertCharSequenceContains(t.getMessage(), firstSubsequence, others);
-    }
-
-    public static void assertCharSequenceContains(CharSequence s,
-                                                  CharSequence firstSubsequence,
-                                                  CharSequence... others) {
-        if (s == null) {
-            throw new NullPointerException("Exception message is null");
-        }
-        String str = s.toString();
-        String missing = null;
-        if (!str.contains(firstSubsequence.toString())) {
-            missing = firstSubsequence.toString();
-        } else {
-            for (CharSequence o : others) {
-                if (!str.contains(o.toString())) {
-                    missing = o.toString();
-                    break;
-                }
-            }
-        }
-        if (missing != null) {
-            throw new AssertionError("CharSequence '" + s + "'" + " does not "
-                    + "contain subsequence '" + missing + "'");
-        }
-    }
-
-    public interface ReturningBlock<T> {
-        T run() throws Throwable;
-    }
-
-    public interface Block<T> {
-        void run() throws Throwable;
-    }
-
-    // tests
-
-    @Test
-    public void assertThrows() {
-        assertThrows(NullPointerException.class, () -> ((Object) null).toString());
-    }
-
-    @Test
-    public void assertThrowsWrongType() {
-        try {
-            assertThrows(IllegalArgumentException.class, () -> ((Object) null).toString());
-        } catch (AssertionError e) {
-            Throwable cause = e.getCause();
-            String message = e.getMessage();
-            if (cause != null
-                    && cause instanceof NullPointerException
-                    && message != null
-                    && message.contains("instead caught")) {
-                return;
-            }
-        }
-        throw new AssertionError();
-    }
-
-    @Test
-    public void assertThrowsNoneCaught() {
-        try {
-            assertThrows(IllegalArgumentException.class, () -> null);
-        } catch (AssertionError e) {
-            Throwable cause = e.getCause();
-            String message = e.getMessage();
-            if (cause == null
-                    && message != null
-                    && message.contains("but caught nothing")) {
-                return;
-            }
-        }
-        throw new AssertionError();
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/BinaryPrimitivesTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2014, 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.internal.net.http.hpack;
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+import static jdk.internal.net.http.hpack.BuffersTestingKit.*;
+import static jdk.internal.net.http.hpack.TestHelper.newRandom;
+
+//
+// Some of the tests below overlap in what they test. This allows to diagnose
+// bugs quicker and with less pain by simply ruling out common working bits.
+//
+public final class BinaryPrimitivesTest {
+
+    private final Random rnd = newRandom();
+
+    @Test
+    public void integerRead1() {
+        verifyRead(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
+    }
+
+    @Test
+    public void integerRead2() {
+        verifyRead(bytes(0b00001010), 10, 5);
+    }
+
+    @Test
+    public void integerRead3() {
+        verifyRead(bytes(0b00101010), 42, 8);
+    }
+
+    @Test
+    public void integerWrite1() {
+        verifyWrite(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
+    }
+
+    @Test
+    public void integerWrite2() {
+        verifyWrite(bytes(0b00001010), 10, 5);
+    }
+
+    @Test
+    public void integerWrite3() {
+        verifyWrite(bytes(0b00101010), 42, 8);
+    }
+
+    //
+    // Since readInteger(x) is the inverse of writeInteger(x), thus:
+    //
+    // for all x: readInteger(writeInteger(x)) == x
+    //
+    @Test
+    public void integerIdentity() throws IOException {
+        final int MAX_VALUE = 1 << 22;
+        int totalCases = 0;
+        int maxFilling = 0;
+        IntegerReader r = new IntegerReader();
+        IntegerWriter w = new IntegerWriter();
+        ByteBuffer buf = ByteBuffer.allocate(8);
+        for (int N = 1; N < 9; N++) {
+            for (int expected = 0; expected <= MAX_VALUE; expected++) {
+                w.reset().configure(expected, N, 1).write(buf);
+                buf.flip();
+                totalCases++;
+                maxFilling = Math.max(maxFilling, buf.remaining());
+                r.reset().configure(N).read(buf);
+                assertEquals(r.get(), expected);
+                buf.clear();
+            }
+        }
+        System.out.printf("totalCases: %,d, maxFilling: %,d, maxValue: %,d%n",
+                totalCases, maxFilling, MAX_VALUE);
+    }
+
+    @Test
+    public void integerReadChunked() {
+        final int NUM_TESTS = 1024;
+        IntegerReader r = new IntegerReader();
+        ByteBuffer bb = ByteBuffer.allocate(8);
+        IntegerWriter w = new IntegerWriter();
+        for (int i = 0; i < NUM_TESTS; i++) {
+            final int N = 1 + rnd.nextInt(8);
+            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
+            w.reset().configure(expected, N, rnd.nextInt()).write(bb);
+            bb.flip();
+
+            forEachSplit(bb,
+                    (buffers) -> {
+                        Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
+                        r.configure(N);
+                        for (ByteBuffer b : buf) {
+                            try {
+                                r.read(b);
+                            } catch (IOException e) {
+                                throw new UncheckedIOException(e);
+                            }
+                        }
+                        assertEquals(r.get(), expected);
+                        r.reset();
+                    });
+            bb.clear();
+        }
+    }
+
+    // FIXME: use maxValue in the test
+
+    @Test
+    // FIXME: tune values for better coverage
+    public void integerWriteChunked() {
+        ByteBuffer bb = ByteBuffer.allocate(6);
+        IntegerWriter w = new IntegerWriter();
+        IntegerReader r = new IntegerReader();
+        for (int i = 0; i < 1024; i++) { // number of tests
+            final int N = 1 + rnd.nextInt(8);
+            final int payload = rnd.nextInt(255);
+            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
+
+            forEachSplit(bb,
+                    (buffers) -> {
+                        List<ByteBuffer> buf = new ArrayList<>();
+                        relocateBuffers(injectEmptyBuffers(buffers)).forEach(buf::add);
+                        boolean written = false;
+                        w.configure(expected, N, payload); // TODO: test for payload it can be read after written
+                        for (ByteBuffer b : buf) {
+                            int pos = b.position();
+                            written = w.write(b);
+                            b.position(pos);
+                        }
+                        if (!written) {
+                            fail("please increase bb size");
+                        }
+                        try {
+                            r.configure(N).read(concat(buf));
+                        } catch (IOException e) {
+                            throw new UncheckedIOException(e);
+                        }
+                        // TODO: check payload here
+                        assertEquals(r.get(), expected);
+                        w.reset();
+                        r.reset();
+                        bb.clear();
+                    });
+        }
+    }
+
+
+    //
+    // Since readString(x) is the inverse of writeString(x), thus:
+    //
+    // for all x: readString(writeString(x)) == x
+    //
+    @Test
+    public void stringIdentity() throws IOException {
+        final int MAX_STRING_LENGTH = 4096;
+        ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6); // it takes 6 bytes to encode string length of Integer.MAX_VALUE
+        CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
+        StringReader reader = new StringReader();
+        StringWriter writer = new StringWriter();
+        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
+            for (int i = 0; i < 64; i++) {
+                // not so much "test in isolation", I know... we're testing .reset() as well
+                bytes.clear();
+                chars.clear();
+
+                byte[] b = new byte[len];
+                rnd.nextBytes(b);
+
+                String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
+
+                boolean written = writer
+                        .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
+                        .write(bytes);
+
+                if (!written) {
+                    fail("please increase 'bytes' size");
+                }
+                bytes.flip();
+                reader.read(bytes, chars);
+                chars.flip();
+                assertEquals(chars.toString(), expected);
+                reader.reset();
+                writer.reset();
+            }
+        }
+    }
+
+//    @Test
+//    public void huffmanStringWriteChunked() {
+//        fail();
+//    }
+//
+//    @Test
+//    public void huffmanStringReadChunked() {
+//        fail();
+//    }
+
+    @Test
+    public void stringWriteChunked() {
+        final int MAX_STRING_LENGTH = 8;
+        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
+        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
+        final StringReader reader = new StringReader();
+        final StringWriter writer = new StringWriter();
+        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
+
+            byte[] b = new byte[len];
+            rnd.nextBytes(b);
+
+            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
+
+            forEachSplit(bytes, (buffers) -> {
+                writer.configure(expected, 0, expected.length(), false);
+                boolean written = false;
+                for (ByteBuffer buf : buffers) {
+                    int p0 = buf.position();
+                    written = writer.write(buf);
+                    buf.position(p0);
+                }
+                if (!written) {
+                    fail("please increase 'bytes' size");
+                }
+                try {
+                    reader.read(concat(buffers), chars);
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+                chars.flip();
+                assertEquals(chars.toString(), expected);
+                reader.reset();
+                writer.reset();
+                chars.clear();
+                bytes.clear();
+            });
+        }
+    }
+
+    @Test
+    public void stringReadChunked() {
+        final int MAX_STRING_LENGTH = 16;
+        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
+        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
+        final StringReader reader = new StringReader();
+        final StringWriter writer = new StringWriter();
+        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
+
+            byte[] b = new byte[len];
+            rnd.nextBytes(b);
+
+            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
+
+            boolean written = writer
+                    .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
+                    .write(bytes);
+            writer.reset();
+
+            if (!written) {
+                fail("please increase 'bytes' size");
+            }
+            bytes.flip();
+
+            forEachSplit(bytes, (buffers) -> {
+                for (ByteBuffer buf : buffers) {
+                    int p0 = buf.position();
+                    try {
+                        reader.read(buf, chars);
+                    } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                    }
+                    buf.position(p0);
+                }
+                chars.flip();
+                assertEquals(chars.toString(), expected);
+                reader.reset();
+                chars.clear();
+            });
+
+            bytes.clear();
+        }
+    }
+
+//    @Test
+//    public void test_Huffman_String_Identity() {
+//        StringWriter writer = new StringWriter();
+//        StringReader reader = new StringReader();
+//        // 256 * 8 gives 2048 bits in case of plain 8 bit coding
+//        // 256 * 30 gives you 7680 bits or 960 bytes in case of almost
+//        //          improbable event of 256 30 bits symbols in a row
+//        ByteBuffer binary = ByteBuffer.allocate(960);
+//        CharBuffer text = CharBuffer.allocate(960 / 5); // 5 = minimum code length
+//        for (int len = 0; len < 128; len++) {
+//            for (int i = 0; i < 256; i++) {
+//                // not so much "test in isolation", I know...
+//                binary.clear();
+//
+//                byte[] bytes = new byte[len];
+//                rnd.nextBytes(bytes);
+//
+//                String s = new String(bytes, StandardCharsets.ISO_8859_1);
+//
+//                writer.write(CharBuffer.wrap(s), binary, true);
+//                binary.flip();
+//                reader.read(binary, text);
+//                text.flip();
+//                assertEquals(text.toString(), s);
+//            }
+//        }
+//    }
+
+    // TODO: atomic failures: e.g. readonly/overflow
+
+    private static byte[] bytes(int... data) {
+        byte[] bytes = new byte[data.length];
+        for (int i = 0; i < data.length; i++) {
+            bytes[i] = (byte) data[i];
+        }
+        return bytes;
+    }
+
+    private static void verifyRead(byte[] data, int expected, int N) {
+        ByteBuffer buf = ByteBuffer.wrap(data, 0, data.length);
+        IntegerReader reader = new IntegerReader();
+        try {
+            reader.configure(N).read(buf);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        assertEquals(expected, reader.get());
+    }
+
+    private void verifyWrite(byte[] expected, int data, int N) {
+        IntegerWriter w = new IntegerWriter();
+        ByteBuffer buf = ByteBuffer.allocate(2 * expected.length);
+        w.configure(data, N, 1).write(buf);
+        buf.flip();
+        assertEquals(ByteBuffer.wrap(expected), buf);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/BuffersTestingKit.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,210 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static java.nio.ByteBuffer.allocate;
+
+public final class BuffersTestingKit {
+
+    /**
+     * Relocates a {@code [position, limit)} region of the given buffer to
+     * corresponding region in a new buffer starting with provided {@code
+     * newPosition}.
+     *
+     * <p> Might be useful to make sure ByteBuffer's users do not rely on any
+     * absolute positions, but solely on what's reported by position(), limit().
+     *
+     * <p> The contents between the given buffer and the returned one are not
+     * shared.
+     */
+    public static ByteBuffer relocate(ByteBuffer buffer, int newPosition,
+                                      int newCapacity) {
+        int oldPosition = buffer.position();
+        int oldLimit = buffer.limit();
+
+        if (newPosition + oldLimit - oldPosition > newCapacity) {
+            throw new IllegalArgumentException();
+        }
+
+        ByteBuffer result;
+        if (buffer.isDirect()) {
+            result = ByteBuffer.allocateDirect(newCapacity);
+        } else {
+            result = allocate(newCapacity);
+        }
+
+        result.position(newPosition);
+        result.put(buffer).limit(result.position()).position(newPosition);
+        buffer.position(oldPosition);
+
+        if (buffer.isReadOnly()) {
+            return result.asReadOnlyBuffer();
+        }
+        return result;
+    }
+
+    public static Iterable<? extends ByteBuffer> relocateBuffers(
+            Iterable<? extends ByteBuffer> source) {
+        return () ->
+                new Iterator<ByteBuffer>() {
+
+                    private final Iterator<? extends ByteBuffer> it = source.iterator();
+
+                    @Override
+                    public boolean hasNext() {
+                        return it.hasNext();
+                    }
+
+                    @Override
+                    public ByteBuffer next() {
+                        ByteBuffer buf = it.next();
+                        int remaining = buf.remaining();
+                        int newCapacity = remaining + random.nextInt(17);
+                        int newPosition = random.nextInt(newCapacity - remaining + 1);
+                        return relocate(buf, newPosition, newCapacity);
+                    }
+                };
+    }
+
+    // TODO: not always of size 0 (it's fine for buffer to report !b.hasRemaining())
+    public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
+            Iterable<? extends ByteBuffer> source) {
+        return injectEmptyBuffers(source, () -> allocate(0));
+    }
+
+    public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
+            Iterable<? extends ByteBuffer> source,
+            Supplier<? extends ByteBuffer> emptyBufferFactory) {
+
+        return () ->
+                new Iterator<ByteBuffer>() {
+
+                    private final Iterator<? extends ByteBuffer> it = source.iterator();
+                    private ByteBuffer next = calculateNext();
+
+                    private ByteBuffer calculateNext() {
+                        if (random.nextBoolean()) {
+                            return emptyBufferFactory.get();
+                        } else if (it.hasNext()) {
+                            return it.next();
+                        } else {
+                            return null;
+                        }
+                    }
+
+                    @Override
+                    public boolean hasNext() {
+                        return next != null;
+                    }
+
+                    @Override
+                    public ByteBuffer next() {
+                        if (!hasNext()) {
+                            throw new NoSuchElementException();
+                        }
+                        ByteBuffer next = this.next;
+                        this.next = calculateNext();
+                        return next;
+                    }
+                };
+    }
+
+    public static ByteBuffer concat(Iterable<? extends ByteBuffer> split) {
+        return concat(split, ByteBuffer::allocate);
+    }
+
+    public static ByteBuffer concat(Iterable<? extends ByteBuffer> split,
+                                    Function<? super Integer, ? extends ByteBuffer> concatBufferFactory) {
+        int size = 0;
+        for (ByteBuffer bb : split) {
+            size += bb.remaining();
+        }
+
+        ByteBuffer result = concatBufferFactory.apply(size);
+        for (ByteBuffer bb : split) {
+            result.put(bb);
+        }
+
+        result.flip();
+        return result;
+    }
+
+    public static void forEachSplit(ByteBuffer bb,
+                                    Consumer<? super Iterable<? extends ByteBuffer>> action) {
+        forEachSplit(bb.remaining(),
+                (lengths) -> {
+                    int end = bb.position();
+                    List<ByteBuffer> buffers = new LinkedList<>();
+                    for (int len : lengths) {
+                        ByteBuffer d = bb.duplicate();
+                        d.position(end);
+                        d.limit(end + len);
+                        end += len;
+                        buffers.add(d);
+                    }
+                    action.accept(buffers);
+                });
+    }
+
+    private static void forEachSplit(int n, Consumer<? super Iterable<? extends Integer>> action) {
+        forEachSplit(n, new Stack<>(), action);
+    }
+
+    private static void forEachSplit(int n, Stack<Integer> path,
+                                     Consumer<? super Iterable<? extends Integer>> action) {
+        if (n == 0) {
+            action.accept(path);
+        } else {
+            for (int i = 1; i <= n; i++) {
+                path.push(i);
+                forEachSplit(n - i, path, action);
+                path.pop();
+            }
+        }
+    }
+
+    private static final Random random = new Random();
+
+    private BuffersTestingKit() {
+        throw new InternalError();
+    }
+
+//    public static void main(String[] args) {
+//
+//        List<ByteBuffer> buffers = Arrays.asList(
+//                (ByteBuffer) allocate(3).position(1).limit(2),
+//                allocate(0),
+//                allocate(7));
+//
+//        Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
+//        List<ByteBuffer> result = new ArrayList<>();
+//        buf.forEach(result::add);
+//        System.out.println(result);
+//    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/CircularBufferTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.hpack;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import jdk.internal.net.http.hpack.HeaderTable.CircularBuffer;
+
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import static org.testng.Assert.assertEquals;
+import static jdk.internal.net.http.hpack.TestHelper.newRandom;
+
+public final class CircularBufferTest {
+
+    private final Random r = newRandom();
+
+    @BeforeClass
+    public void setUp() {
+        r.setSeed(System.currentTimeMillis());
+    }
+
+    @Test
+    public void queue() {
+        for (int capacity = 1; capacity <= 2048; capacity++) {
+            queueOnce(capacity, 32);
+        }
+    }
+
+    @Test
+    public void resize() {
+        for (int capacity = 1; capacity <= 4096; capacity++) {
+            resizeOnce(capacity);
+        }
+    }
+
+    @Test
+    public void downSizeEmptyBuffer() {
+        CircularBuffer<Integer> buffer = new CircularBuffer<>(16);
+        buffer.resize(15);
+    }
+
+    private void resizeOnce(int capacity) {
+
+        int nextNumberToPut = 0;
+
+        Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
+        CircularBuffer<Integer> buffer = new CircularBuffer<>(capacity);
+
+        // Fill full, so the next add will wrap
+        for (int i = 0; i < capacity; i++, nextNumberToPut++) {
+            buffer.add(nextNumberToPut);
+            referenceQueue.add(nextNumberToPut);
+        }
+        int gets = r.nextInt(capacity); // [0, capacity)
+        for (int i = 0; i < gets; i++) {
+            referenceQueue.poll();
+            buffer.remove();
+        }
+        int puts = r.nextInt(gets + 1); // [0, gets]
+        for (int i = 0; i < puts; i++, nextNumberToPut++) {
+            buffer.add(nextNumberToPut);
+            referenceQueue.add(nextNumberToPut);
+        }
+
+        Integer[] expected = referenceQueue.toArray(new Integer[0]);
+        buffer.resize(expected.length);
+
+        assertEquals(buffer.elements, expected);
+    }
+
+    private void queueOnce(int capacity, int numWraps) {
+
+        Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
+        CircularBuffer<Integer> buffer = new CircularBuffer<>(capacity);
+
+        int nextNumberToPut = 0;
+        int totalPuts = 0;
+        int putsLimit = capacity * numWraps;
+        int remainingCapacity = capacity;
+        int size = 0;
+
+        while (totalPuts < putsLimit) {
+            assert remainingCapacity + size == capacity;
+            int puts = r.nextInt(remainingCapacity + 1); // [0, remainingCapacity]
+            remainingCapacity -= puts;
+            size += puts;
+            for (int i = 0; i < puts; i++, nextNumberToPut++) {
+                referenceQueue.add(nextNumberToPut);
+                buffer.add(nextNumberToPut);
+            }
+            totalPuts += puts;
+            int gets = r.nextInt(size + 1); // [0, size]
+            size -= gets;
+            remainingCapacity += gets;
+            for (int i = 0; i < gets; i++) {
+                Integer expected = referenceQueue.poll();
+                Integer actual = buffer.remove();
+                assertEquals(actual, expected);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/DecoderTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,685 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static jdk.internal.net.http.hpack.TestHelper.*;
+
+//
+// Tests whose names start with "testX" are the ones captured from real HPACK
+// use cases
+//
+public final class DecoderTest {
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
+    //
+    @Test
+    public void example1() {
+        // @formatter:off
+        test("400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
+             "746f 6d2d 6865 6164 6572",
+
+             "[  1] (s =  55) custom-key: custom-header\n" +
+             "      Table size:  55",
+
+             "custom-key: custom-header");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
+    //
+    @Test
+    public void example2() {
+        // @formatter:off
+        test("040c 2f73 616d 706c 652f 7061 7468",
+             "empty.",
+             ":path: /sample/path");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
+    //
+    @Test
+    public void example3() {
+        // @formatter:off
+        test("1008 7061 7373 776f 7264 0673 6563 7265\n" +
+             "74",
+             "empty.",
+             "password: secret");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
+    //
+    @Test
+    public void example4() {
+        // @formatter:off
+        test("82",
+             "empty.",
+             ":method: GET");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.3
+    //
+    @Test
+    public void example5() {
+        // @formatter:off
+        Decoder d = new Decoder(256);
+
+        test(d, "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
+                "2e63 6f6d",
+
+                "[  1] (s =  57) :authority: www.example.com\n" +
+                "      Table size:  57",
+
+                ":method: GET\n" +
+                ":scheme: http\n" +
+                ":path: /\n" +
+                ":authority: www.example.com");
+
+        test(d, "8286 84be 5808 6e6f 2d63 6163 6865",
+
+                "[  1] (s =  53) cache-control: no-cache\n" +
+                "[  2] (s =  57) :authority: www.example.com\n" +
+                "      Table size: 110",
+
+                ":method: GET\n" +
+                ":scheme: http\n" +
+                ":path: /\n" +
+                ":authority: www.example.com\n" +
+                "cache-control: no-cache");
+
+        test(d, "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
+                "0c63 7573 746f 6d2d 7661 6c75 65",
+
+                "[  1] (s =  54) custom-key: custom-value\n" +
+                "[  2] (s =  53) cache-control: no-cache\n" +
+                "[  3] (s =  57) :authority: www.example.com\n" +
+                "      Table size: 164",
+
+                ":method: GET\n" +
+                ":scheme: https\n" +
+                ":path: /index.html\n" +
+                ":authority: www.example.com\n" +
+                "custom-key: custom-value");
+
+        // @formatter:on
+    }
+
+    @Test
+    public void example5AllSplits() {
+        // @formatter:off
+        testAllSplits(
+                "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
+                "2e63 6f6d",
+
+                "[  1] (s =  57) :authority: www.example.com\n" +
+                "      Table size:  57",
+
+                ":method: GET\n" +
+                ":scheme: http\n" +
+                ":path: /\n" +
+                ":authority: www.example.com");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.4
+    //
+    @Test
+    public void example6() {
+        // @formatter:off
+        Decoder d = new Decoder(256);
+
+        test(d, "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
+                "ff",
+
+                "[  1] (s =  57) :authority: www.example.com\n" +
+                "      Table size:  57",
+
+                ":method: GET\n" +
+                ":scheme: http\n" +
+                ":path: /\n" +
+                ":authority: www.example.com");
+
+        test(d, "8286 84be 5886 a8eb 1064 9cbf",
+
+                "[  1] (s =  53) cache-control: no-cache\n" +
+                "[  2] (s =  57) :authority: www.example.com\n" +
+                "      Table size: 110",
+
+                ":method: GET\n" +
+                ":scheme: http\n" +
+                ":path: /\n" +
+                ":authority: www.example.com\n" +
+                "cache-control: no-cache");
+
+        test(d, "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
+                "a849 e95b b8e8 b4bf",
+
+                "[  1] (s =  54) custom-key: custom-value\n" +
+                "[  2] (s =  53) cache-control: no-cache\n" +
+                "[  3] (s =  57) :authority: www.example.com\n" +
+                "      Table size: 164",
+
+                ":method: GET\n" +
+                ":scheme: https\n" +
+                ":path: /index.html\n" +
+                ":authority: www.example.com\n" +
+                "custom-key: custom-value");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.5
+    //
+    @Test
+    public void example7() {
+        // @formatter:off
+        Decoder d = new Decoder(256);
+
+        test(d, "4803 3330 3258 0770 7269 7661 7465 611d\n" +
+                "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
+                "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
+                "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
+                "6c65 2e63 6f6d",
+
+                "[  1] (s =  63) location: https://www.example.com\n" +
+                "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "[  3] (s =  52) cache-control: private\n" +
+                "[  4] (s =  42) :status: 302\n" +
+                "      Table size: 222",
+
+                ":status: 302\n" +
+                "cache-control: private\n" +
+                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "location: https://www.example.com");
+
+        test(d, "4803 3330 37c1 c0bf",
+
+                "[  1] (s =  42) :status: 307\n" +
+                "[  2] (s =  63) location: https://www.example.com\n" +
+                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "[  4] (s =  52) cache-control: private\n" +
+                "      Table size: 222",
+
+                ":status: 307\n" +
+                "cache-control: private\n" +
+                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "location: https://www.example.com");
+
+        test(d, "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
+                "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
+                "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
+                "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
+                "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
+                "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
+                "3d31",
+
+                "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+                "[  2] (s =  52) content-encoding: gzip\n" +
+                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+                "      Table size: 215",
+
+                ":status: 200\n" +
+                "cache-control: private\n" +
+                "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+                "location: https://www.example.com\n" +
+                "content-encoding: gzip\n" +
+                "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.6
+    //
+    @Test
+    public void example8() {
+        // @formatter:off
+        Decoder d = new Decoder(256);
+
+        test(d, "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
+                "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
+                "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
+                "e9ae 82ae 43d3",
+
+                "[  1] (s =  63) location: https://www.example.com\n" +
+                "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "[  3] (s =  52) cache-control: private\n" +
+                "[  4] (s =  42) :status: 302\n" +
+                "      Table size: 222",
+
+                ":status: 302\n" +
+                "cache-control: private\n" +
+                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "location: https://www.example.com");
+
+        test(d, "4883 640e ffc1 c0bf",
+
+                "[  1] (s =  42) :status: 307\n" +
+                "[  2] (s =  63) location: https://www.example.com\n" +
+                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "[  4] (s =  52) cache-control: private\n" +
+                "      Table size: 222",
+
+                ":status: 307\n" +
+                "cache-control: private\n" +
+                "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+                "location: https://www.example.com");
+
+        test(d, "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
+                "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
+                "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
+                "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
+                "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
+
+                "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+                "[  2] (s =  52) content-encoding: gzip\n" +
+                "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+                "      Table size: 215",
+
+                ":status: 200\n" +
+                "cache-control: private\n" +
+                "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+                "location: https://www.example.com\n" +
+                "content-encoding: gzip\n" +
+                "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
+        // @formatter:on
+    }
+
+    @Test
+    // One of responses from Apache Server that helped to catch a bug
+    public void testX() {
+        Decoder d = new Decoder(4096);
+        // @formatter:off
+        test(d, "3fe1 1f88 6196 d07a be94 03ea 693f 7504\n" +
+                "00b6 a05c b827 2e32 fa98 b46f 769e 86b1\n" +
+                "9272 b025 da5c 2ea9 fd70 a8de 7fb5 3556\n" +
+                "5ab7 6ece c057 02e2 2ad2 17bf 6c96 d07a\n" +
+                "be94 0854 cb6d 4a08 0075 40bd 71b6 6e05\n" +
+                "a531 68df 0f13 8efe 4522 cd32 21b6 5686\n" +
+                "eb23 781f cf52 848f d24a 8f0f 0d02 3435\n" +
+                "5f87 497c a589 d34d 1f",
+
+                "[  1] (s =  53) content-type: text/html\n" +
+                "[  2] (s =  50) accept-ranges: bytes\n" +
+                "[  3] (s =  74) last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
+                "[  4] (s =  77) server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
+                "[  5] (s =  65) date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
+                "      Table size: 319",
+
+                ":status: 200\n" +
+                "date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
+                "server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
+                "last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
+                "etag: \"2d-432a5e4a73a80\"\n" +
+                "accept-ranges: bytes\n" +
+                "content-length: 45\n" +
+                "content-type: text/html");
+        // @formatter:on
+    }
+
+    @Test
+    public void testX1() {
+        // Supplier of a decoder with a particular state
+        Supplier<Decoder> s = () -> {
+            Decoder d = new Decoder(4096);
+            // @formatter:off
+            test(d, "88 76 92 ca 54 a7 d7 f4 fa ec af ed 6d da 61 d7 bb 1e ad ff" +
+                    "df 61 97 c3 61 be 94 13 4a 65 b6 a5 04 00 b8 a0 5a b8 db 77" +
+                    "1b 71 4c 5a 37 ff 0f 0d 84 08 00 00 03",
+
+                    "[  1] (s =  65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
+                    "[  2] (s =  59) server: Jetty(9.3.z-SNAPSHOT)\n" +
+                    "      Table size: 124",
+
+                    ":status: 200\n" +
+                    "server: Jetty(9.3.z-SNAPSHOT)\n" +
+                    "date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
+                    "content-length: 100000"
+            );
+            // @formatter:on
+            return d;
+        };
+        // For all splits of the following data fed to the supplied decoder we
+        // must get what's expected
+        // @formatter:off
+        testAllSplits(s,
+                "88 bf be 0f 0d 84 08 00 00 03",
+
+                "[  1] (s =  65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
+                "[  2] (s =  59) server: Jetty(9.3.z-SNAPSHOT)\n" +
+                "      Table size: 124",
+
+                ":status: 200\n" +
+                "server: Jetty(9.3.z-SNAPSHOT)\n" +
+                "date: Fri, 24 Jun 2016 14:55:56 GMT\n" +
+                "content-length: 100000");
+        // @formatter:on
+    }
+
+    //
+    // This test is missing in the spec
+    //
+    @Test
+    public void sizeUpdate() throws IOException {
+        Decoder d = new Decoder(4096);
+        assertEquals(d.getTable().maxSize(), 4096);
+        d.decode(ByteBuffer.wrap(new byte[]{0b00111110}), true, nopCallback()); // newSize = 30
+        assertEquals(d.getTable().maxSize(), 30);
+    }
+
+    @Test
+    public void incorrectSizeUpdate() {
+        ByteBuffer b = ByteBuffer.allocate(8);
+        Encoder e = new Encoder(8192) {
+            @Override
+            protected int calculateCapacity(int maxCapacity) {
+                return maxCapacity;
+            }
+        };
+        e.header("a", "b");
+        e.encode(b);
+        b.flip();
+        {
+            Decoder d = new Decoder(4096);
+            assertVoidThrows(IOException.class,
+                    () -> d.decode(b, true, (name, value) -> { }));
+        }
+        b.flip();
+        {
+            Decoder d = new Decoder(4096);
+            assertVoidThrows(IOException.class,
+                    () -> d.decode(b, false, (name, value) -> { }));
+        }
+    }
+
+    @Test
+    public void corruptedHeaderBlockInteger() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                (byte) 0b11111111, // indexed
+                (byte) 0b10011010  // 25 + ...
+        });
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+        assertExceptionMessageContains(e, "Unexpected end of header block");
+    }
+
+    // 5.1.  Integer Representation
+    // ...
+    // Integer encodings that exceed implementation limits -- in value or octet
+    // length -- MUST be treated as decoding errors. Different limits can
+    // be set for each of the different uses of integers, based on
+    // implementation constraints.
+    @Test
+    public void headerBlockIntegerNoOverflow() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                (byte) 0b11111111, // indexed + 127
+                // Integer.MAX_VALUE - 127 (base 128, little-endian):
+                (byte) 0b10000000,
+                (byte) 0b11111111,
+                (byte) 0b11111111,
+                (byte) 0b11111111,
+                (byte) 0b00000111
+        });
+
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+
+        assertExceptionMessageContains(e.getCause(), "index=2147483647");
+    }
+
+    @Test
+    public void headerBlockIntegerOverflow() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                (byte) 0b11111111, // indexed + 127
+                // Integer.MAX_VALUE - 127 + 1 (base 128, little endian):
+                (byte) 0b10000001,
+                (byte) 0b11111111,
+                (byte) 0b11111111,
+                (byte) 0b11111111,
+                (byte) 0b00000111
+        });
+
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+
+        assertExceptionMessageContains(e, "Integer overflow");
+    }
+
+    @Test
+    public void corruptedHeaderBlockString1() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                0b00001111, // literal, index=15
+                0b00000000,
+                0b00001000, // huffman=false, length=8
+                0b00000000, // \
+                0b00000000, //  but only 3 octets available...
+                0b00000000  // /
+        });
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+        assertExceptionMessageContains(e, "Unexpected end of header block");
+    }
+
+    @Test
+    public void corruptedHeaderBlockString2() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                0b00001111, // literal, index=15
+                0b00000000,
+                (byte) 0b10001000, // huffman=true, length=8
+                0b00000000, // \
+                0b00000000, //  \
+                0b00000000, //   but only 5 octets available...
+                0b00000000, //  /
+                0b00000000  // /
+        });
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+        assertExceptionMessageContains(e, "Unexpected end of header block");
+    }
+
+    // 5.2.  String Literal Representation
+    // ...A Huffman-encoded string literal containing the EOS symbol MUST be
+    // treated as a decoding error...
+    @Test
+    public void corruptedHeaderBlockHuffmanStringEOS() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                0b00001111, // literal, index=15
+                0b00000000,
+                (byte) 0b10000110, // huffman=true, length=6
+                0b00011001, 0b01001101, (byte) 0b11111111,
+                (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111100
+        });
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+
+        assertExceptionMessageContains(e, "Encountered EOS");
+    }
+
+    // 5.2.  String Literal Representation
+    // ...A padding strictly longer than 7 bits MUST be treated as a decoding
+    // error...
+    @Test
+    public void corruptedHeaderBlockHuffmanStringLongPadding1() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                0b00001111, // literal, index=15
+                0b00000000,
+                (byte) 0b10000011, // huffman=true, length=3
+                0b00011001, 0b01001101, (byte) 0b11111111
+                // len("aei") + len(padding) = (5 + 5 + 5) + (9)
+        });
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+
+        assertExceptionMessageContains(e, "Padding is too long", "len=9");
+    }
+
+    @Test
+    public void corruptedHeaderBlockHuffmanStringLongPadding2() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                0b00001111, // literal, index=15
+                0b00000000,
+                (byte) 0b10000011, // huffman=true, length=3
+                0b00011001, 0b01111010, (byte) 0b11111111
+                // len("aek") + len(padding) = (5 + 5 + 7) + (7)
+        });
+        assertVoidDoesNotThrow(() -> d.decode(data, true, nopCallback()));
+    }
+
+    // 5.2.  String Literal Representation
+    // ...A padding not corresponding to the most significant bits of the code
+    // for the EOS symbol MUST be treated as a decoding error...
+    @Test
+    public void corruptedHeaderBlockHuffmanStringNotEOSPadding() {
+        Decoder d = new Decoder(4096);
+        ByteBuffer data = ByteBuffer.wrap(new byte[]{
+                0b00001111, // literal, index=15
+                0b00000000,
+                (byte) 0b10000011, // huffman=true, length=3
+                0b00011001, 0b01111010, (byte) 0b11111110
+        });
+        IOException e = assertVoidThrows(IOException.class,
+                () -> d.decode(data, true, nopCallback()));
+
+        assertExceptionMessageContains(e, "Not a EOS prefix");
+    }
+
+    @Test
+    public void argsTestBiConsumerIsNull() {
+        Decoder decoder = new Decoder(4096);
+        assertVoidThrows(NullPointerException.class,
+                () -> decoder.decode(ByteBuffer.allocate(16), true, null));
+    }
+
+    @Test
+    public void argsTestByteBufferIsNull() {
+        Decoder decoder = new Decoder(4096);
+        assertVoidThrows(NullPointerException.class,
+                () -> decoder.decode(null, true, nopCallback()));
+    }
+
+    @Test
+    public void argsTestBothAreNull() {
+        Decoder decoder = new Decoder(4096);
+        assertVoidThrows(NullPointerException.class,
+                () -> decoder.decode(null, true, null));
+    }
+
+    private static void test(String hexdump,
+                             String headerTable, String headerList) {
+        test(new Decoder(4096), hexdump, headerTable, headerList);
+    }
+
+    private static void testAllSplits(String hexdump,
+                                      String expectedHeaderTable,
+                                      String expectedHeaderList) {
+        testAllSplits(() -> new Decoder(256), hexdump, expectedHeaderTable, expectedHeaderList);
+    }
+
+    private static void testAllSplits(Supplier<Decoder> supplier, String hexdump,
+                                      String expectedHeaderTable, String expectedHeaderList) {
+        ByteBuffer source = SpecHelper.toBytes(hexdump);
+
+        BuffersTestingKit.forEachSplit(source, iterable -> {
+            List<String> actual = new LinkedList<>();
+            Iterator<? extends ByteBuffer> i = iterable.iterator();
+            if (!i.hasNext()) {
+                return;
+            }
+            Decoder d = supplier.get();
+            do {
+                ByteBuffer n = i.next();
+                try {
+                    d.decode(n, !i.hasNext(), (name, value) -> {
+                        if (value == null) {
+                            actual.add(name.toString());
+                        } else {
+                            actual.add(name + ": " + value);
+                        }
+                    });
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            } while (i.hasNext());
+            assertEquals(d.getTable().getStateString(), expectedHeaderTable);
+            assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
+        });
+    }
+
+    //
+    // Sometimes we need to keep the same decoder along several runs,
+    // as it models the same connection
+    //
+    private static void test(Decoder d, String hexdump,
+                             String expectedHeaderTable, String expectedHeaderList) {
+
+        ByteBuffer source = SpecHelper.toBytes(hexdump);
+
+        List<String> actual = new LinkedList<>();
+        try {
+            d.decode(source, true, (name, value) -> {
+                if (value == null) {
+                    actual.add(name.toString());
+                } else {
+                    actual.add(name + ": " + value);
+                }
+            });
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        assertEquals(d.getTable().getStateString(), expectedHeaderTable);
+        assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
+    }
+
+    private static DecodingCallback nopCallback() {
+        return (t, u) -> { };
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/EncoderTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,693 @@
+/*
+ * Copyright (c) 2014, 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.internal.net.http.hpack;
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static jdk.internal.net.http.hpack.BuffersTestingKit.concat;
+import static jdk.internal.net.http.hpack.BuffersTestingKit.forEachSplit;
+import static jdk.internal.net.http.hpack.SpecHelper.toHexdump;
+import static jdk.internal.net.http.hpack.TestHelper.assertVoidThrows;
+import static java.util.Arrays.asList;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+// TODO: map textual representation of commands from the spec to actual
+// calls to encoder (actually, this is a good idea for decoder as well)
+public final class EncoderTest {
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
+    //
+    @Test
+    public void example1() {
+
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        e.literalWithIndexing("custom-key", false, "custom-header", false);
+        // @formatter:off
+        test(e,
+
+             "400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
+             "746f 6d2d 6865 6164 6572",
+
+             "[  1] (s =  55) custom-key: custom-header\n" +
+             "      Table size:  55");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
+    //
+    @Test
+    public void example2() {
+
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        e.literal(4, "/sample/path", false);
+        // @formatter:off
+        test(e,
+
+             "040c 2f73 616d 706c 652f 7061 7468",
+
+             "empty.");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
+    //
+    @Test
+    public void example3() {
+
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        e.literalNeverIndexed("password", false, "secret", false);
+        // @formatter:off
+        test(e,
+
+             "1008 7061 7373 776f 7264 0673 6563 7265\n" +
+             "74",
+
+             "empty.");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
+    //
+    @Test
+    public void example4() {
+
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        e.indexed(2);
+        // @formatter:off
+        test(e,
+
+             "82",
+
+             "empty.");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.3
+    //
+    @Test
+    public void example5() {
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        ByteBuffer output = ByteBuffer.allocate(64);
+        e.indexed(2);
+        e.encode(output);
+        e.indexed(6);
+        e.encode(output);
+        e.indexed(4);
+        e.encode(output);
+        e.literalWithIndexing(1, "www.example.com", false);
+        e.encode(output);
+
+        output.flip();
+
+        // @formatter:off
+        test(e, output,
+
+             "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
+             "2e63 6f6d",
+
+             "[  1] (s =  57) :authority: www.example.com\n" +
+             "      Table size:  57");
+
+        output.clear();
+
+        e.indexed( 2);
+        e.encode(output);
+        e.indexed( 6);
+        e.encode(output);
+        e.indexed( 4);
+        e.encode(output);
+        e.indexed(62);
+        e.encode(output);
+        e.literalWithIndexing(24, "no-cache", false);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "8286 84be 5808 6e6f 2d63 6163 6865",
+
+             "[  1] (s =  53) cache-control: no-cache\n" +
+             "[  2] (s =  57) :authority: www.example.com\n" +
+             "      Table size: 110");
+
+        output.clear();
+
+        e.indexed( 2);
+        e.encode(output);
+        e.indexed( 7);
+        e.encode(output);
+        e.indexed( 5);
+        e.encode(output);
+        e.indexed(63);
+        e.encode(output);
+        e.literalWithIndexing("custom-key", false, "custom-value", false);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
+             "0c63 7573 746f 6d2d 7661 6c75 65",
+
+             "[  1] (s =  54) custom-key: custom-value\n" +
+             "[  2] (s =  53) cache-control: no-cache\n" +
+             "[  3] (s =  57) :authority: www.example.com\n" +
+             "      Table size: 164");
+        // @formatter:on
+    }
+
+    @Test
+    public void example5AllSplits() {
+
+        List<Consumer<Encoder>> actions = new LinkedList<>();
+        actions.add(e -> e.indexed(2));
+        actions.add(e -> e.indexed(6));
+        actions.add(e -> e.indexed(4));
+        actions.add(e -> e.literalWithIndexing(1, "www.example.com", false));
+
+        encodeAllSplits(
+                actions,
+
+                "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
+                "2e63 6f6d",
+
+                "[  1] (s =  57) :authority: www.example.com\n" +
+                "      Table size:  57");
+    }
+
+    private static void encodeAllSplits(Iterable<Consumer<Encoder>> consumers,
+                                        String expectedHexdump,
+                                        String expectedTableState) {
+        ByteBuffer buffer = SpecHelper.toBytes(expectedHexdump);
+        erase(buffer); // Zeroed buffer of size needed to hold the encoding
+        forEachSplit(buffer, iterable -> {
+            List<ByteBuffer> copy = new LinkedList<>();
+            iterable.forEach(b -> copy.add(ByteBuffer.allocate(b.remaining())));
+            Iterator<ByteBuffer> output = copy.iterator();
+            if (!output.hasNext()) {
+                throw new IllegalStateException("No buffers to encode to");
+            }
+            Encoder e = newCustomEncoder(256); // FIXME: pull up (as a parameter)
+            drainInitialUpdate(e);
+            boolean encoded;
+            ByteBuffer b = output.next();
+            for (Consumer<Encoder> c : consumers) {
+                c.accept(e);
+                do {
+                    encoded = e.encode(b);
+                    if (!encoded) {
+                        if (output.hasNext()) {
+                            b = output.next();
+                        } else {
+                            throw new IllegalStateException("No room for encoding");
+                        }
+                    }
+                }
+                while (!encoded);
+            }
+            copy.forEach(Buffer::flip);
+            ByteBuffer data = concat(copy);
+            test(e, data, expectedHexdump, expectedTableState);
+        });
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.4
+    //
+    @Test
+    public void example6() {
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        ByteBuffer output = ByteBuffer.allocate(64);
+        e.indexed(2);
+        e.encode(output);
+        e.indexed(6);
+        e.encode(output);
+        e.indexed(4);
+        e.encode(output);
+        e.literalWithIndexing(1, "www.example.com", true);
+        e.encode(output);
+
+        output.flip();
+
+        // @formatter:off
+        test(e, output,
+
+             "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
+             "ff",
+
+             "[  1] (s =  57) :authority: www.example.com\n" +
+             "      Table size:  57");
+
+        output.clear();
+
+        e.indexed( 2);
+        e.encode(output);
+        e.indexed( 6);
+        e.encode(output);
+        e.indexed( 4);
+        e.encode(output);
+        e.indexed(62);
+        e.encode(output);
+        e.literalWithIndexing(24, "no-cache", true);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "8286 84be 5886 a8eb 1064 9cbf",
+
+             "[  1] (s =  53) cache-control: no-cache\n" +
+             "[  2] (s =  57) :authority: www.example.com\n" +
+             "      Table size: 110");
+
+        output.clear();
+
+        e.indexed( 2);
+        e.encode(output);
+        e.indexed( 7);
+        e.encode(output);
+        e.indexed( 5);
+        e.encode(output);
+        e.indexed(63);
+        e.encode(output);
+        e.literalWithIndexing("custom-key", true, "custom-value", true);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
+             "a849 e95b b8e8 b4bf",
+
+             "[  1] (s =  54) custom-key: custom-value\n" +
+             "[  2] (s =  53) cache-control: no-cache\n" +
+             "[  3] (s =  57) :authority: www.example.com\n" +
+             "      Table size: 164");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.5
+    //
+    @Test
+    public void example7() {
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        ByteBuffer output = ByteBuffer.allocate(128);
+        // @formatter:off
+        e.literalWithIndexing( 8, "302", false);
+        e.encode(output);
+        e.literalWithIndexing(24, "private", false);
+        e.encode(output);
+        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", false);
+        e.encode(output);
+        e.literalWithIndexing(46, "https://www.example.com", false);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "4803 3330 3258 0770 7269 7661 7465 611d\n" +
+             "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
+             "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
+             "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
+             "6c65 2e63 6f6d",
+
+             "[  1] (s =  63) location: https://www.example.com\n" +
+             "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+             "[  3] (s =  52) cache-control: private\n" +
+             "[  4] (s =  42) :status: 302\n" +
+             "      Table size: 222");
+
+        output.clear();
+
+        e.literalWithIndexing( 8, "307", false);
+        e.encode(output);
+        e.indexed(65);
+        e.encode(output);
+        e.indexed(64);
+        e.encode(output);
+        e.indexed(63);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "4803 3330 37c1 c0bf",
+
+             "[  1] (s =  42) :status: 307\n" +
+             "[  2] (s =  63) location: https://www.example.com\n" +
+             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+             "[  4] (s =  52) cache-control: private\n" +
+             "      Table size: 222");
+
+        output.clear();
+
+        e.indexed( 8);
+        e.encode(output);
+        e.indexed(65);
+        e.encode(output);
+        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", false);
+        e.encode(output);
+        e.indexed(64);
+        e.encode(output);
+        e.literalWithIndexing(26, "gzip", false);
+        e.encode(output);
+        e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", false);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
+             "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
+             "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
+             "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
+             "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
+             "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
+             "3d31",
+
+             "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+             "[  2] (s =  52) content-encoding: gzip\n" +
+             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+             "      Table size: 215");
+        // @formatter:on
+    }
+
+    //
+    // http://tools.ietf.org/html/rfc7541#appendix-C.6
+    //
+    @Test
+    public void example8() {
+        Encoder e = newCustomEncoder(256);
+        drainInitialUpdate(e);
+
+        ByteBuffer output = ByteBuffer.allocate(128);
+        // @formatter:off
+        e.literalWithIndexing( 8, "302", true);
+        e.encode(output);
+        e.literalWithIndexing(24, "private", true);
+        e.encode(output);
+        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", true);
+        e.encode(output);
+        e.literalWithIndexing(46, "https://www.example.com", true);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
+             "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
+             "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
+             "e9ae 82ae 43d3",
+
+             "[  1] (s =  63) location: https://www.example.com\n" +
+             "[  2] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+             "[  3] (s =  52) cache-control: private\n" +
+             "[  4] (s =  42) :status: 302\n" +
+             "      Table size: 222");
+
+        output.clear();
+
+        e.literalWithIndexing( 8, "307", true);
+        e.encode(output);
+        e.indexed(65);
+        e.encode(output);
+        e.indexed(64);
+        e.encode(output);
+        e.indexed(63);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "4883 640e ffc1 c0bf",
+
+             "[  1] (s =  42) :status: 307\n" +
+             "[  2] (s =  63) location: https://www.example.com\n" +
+             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+             "[  4] (s =  52) cache-control: private\n" +
+             "      Table size: 222");
+
+        output.clear();
+
+        e.indexed( 8);
+        e.encode(output);
+        e.indexed(65);
+        e.encode(output);
+        e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", true);
+        e.encode(output);
+        e.indexed(64);
+        e.encode(output);
+        e.literalWithIndexing(26, "gzip", true);
+        e.encode(output);
+        e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", true);
+        e.encode(output);
+
+        output.flip();
+
+        test(e, output,
+
+             "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
+             "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
+             "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
+             "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
+             "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
+
+             "[  1] (s =  98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+             "[  2] (s =  52) content-encoding: gzip\n" +
+             "[  3] (s =  65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+             "      Table size: 215");
+        // @formatter:on
+    }
+
+    @Test
+    public void initialSizeUpdateDefaultEncoder() throws IOException {
+        Function<Integer, Encoder> e = Encoder::new;
+        testSizeUpdate(e, 1024, asList(), asList(0));
+        testSizeUpdate(e, 1024, asList(1024), asList(0));
+        testSizeUpdate(e, 1024, asList(1024, 1024), asList(0));
+        testSizeUpdate(e, 1024, asList(1024, 512), asList(0));
+        testSizeUpdate(e, 1024, asList(512, 1024), asList(0));
+        testSizeUpdate(e, 1024, asList(512, 2048), asList(0));
+    }
+
+    @Test
+    public void initialSizeUpdateCustomEncoder() throws IOException {
+        Function<Integer, Encoder> e = EncoderTest::newCustomEncoder;
+        testSizeUpdate(e, 1024, asList(), asList(1024));
+        testSizeUpdate(e, 1024, asList(1024), asList(1024));
+        testSizeUpdate(e, 1024, asList(1024, 1024), asList(1024));
+        testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
+        testSizeUpdate(e, 1024, asList(512, 1024), asList(1024));
+        testSizeUpdate(e, 1024, asList(512, 2048), asList(2048));
+    }
+
+    @Test
+    public void seriesOfSizeUpdatesDefaultEncoder() throws IOException {
+        Function<Integer, Encoder> e = c -> {
+            Encoder encoder = new Encoder(c);
+            drainInitialUpdate(encoder);
+            return encoder;
+        };
+        testSizeUpdate(e, 0, asList(0), asList());
+        testSizeUpdate(e, 1024, asList(1024), asList());
+        testSizeUpdate(e, 1024, asList(2048), asList());
+        testSizeUpdate(e, 1024, asList(512), asList());
+        testSizeUpdate(e, 1024, asList(1024, 1024), asList());
+        testSizeUpdate(e, 1024, asList(1024, 2048), asList());
+        testSizeUpdate(e, 1024, asList(2048, 1024), asList());
+        testSizeUpdate(e, 1024, asList(1024, 512), asList());
+        testSizeUpdate(e, 1024, asList(512, 1024), asList());
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#section-4.2
+    //
+    @Test
+    public void seriesOfSizeUpdatesCustomEncoder() throws IOException {
+        Function<Integer, Encoder> e = c -> {
+            Encoder encoder = newCustomEncoder(c);
+            drainInitialUpdate(encoder);
+            return encoder;
+        };
+        testSizeUpdate(e, 0, asList(0), asList());
+        testSizeUpdate(e, 1024, asList(1024), asList());
+        testSizeUpdate(e, 1024, asList(2048), asList(2048));
+        testSizeUpdate(e, 1024, asList(512), asList(512));
+        testSizeUpdate(e, 1024, asList(1024, 1024), asList());
+        testSizeUpdate(e, 1024, asList(1024, 2048), asList(2048));
+        testSizeUpdate(e, 1024, asList(2048, 1024), asList());
+        testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
+        testSizeUpdate(e, 1024, asList(512, 1024), asList(512, 1024));
+    }
+
+    @Test
+    public void callSequenceViolations() {
+        {   // Hasn't set up a header
+            Encoder e = new Encoder(0);
+            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
+        }
+        {   // Can't set up header while there's an unfinished encoding
+            Encoder e = new Encoder(0);
+            e.indexed(32);
+            assertVoidThrows(IllegalStateException.class, () -> e.indexed(32));
+        }
+        {   // Can't setMaxCapacity while there's an unfinished encoding
+            Encoder e = new Encoder(0);
+            e.indexed(32);
+            assertVoidThrows(IllegalStateException.class, () -> e.setMaxCapacity(512));
+        }
+        {   // Hasn't set up a header
+            Encoder e = new Encoder(0);
+            e.setMaxCapacity(256);
+            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
+        }
+        {   // Hasn't set up a header after the previous encoding
+            Encoder e = new Encoder(0);
+            e.indexed(0);
+            boolean encoded = e.encode(ByteBuffer.allocate(16));
+            assertTrue(encoded); // assumption
+            assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
+        }
+    }
+
+    private static void test(Encoder encoder,
+                             String expectedTableState,
+                             String expectedHexdump) {
+
+        ByteBuffer b = ByteBuffer.allocate(128);
+        encoder.encode(b);
+        b.flip();
+        test(encoder, b, expectedTableState, expectedHexdump);
+    }
+
+    private static void test(Encoder encoder,
+                             ByteBuffer output,
+                             String expectedHexdump,
+                             String expectedTableState) {
+
+        String actualTableState = encoder.getHeaderTable().getStateString();
+        assertEquals(actualTableState, expectedTableState);
+
+        String actualHexdump = toHexdump(output);
+        assertEquals(actualHexdump, expectedHexdump.replaceAll("\\n", " "));
+    }
+
+    // initial size - the size encoder is constructed with
+    // updates      - a sequence of values for consecutive calls to encoder.setMaxCapacity
+    // expected     - a sequence of values expected to be decoded by a decoder
+    private void testSizeUpdate(Function<Integer, Encoder> encoder,
+                                int initialSize,
+                                List<Integer> updates,
+                                List<Integer> expected) throws IOException {
+        Encoder e = encoder.apply(initialSize);
+        updates.forEach(e::setMaxCapacity);
+        ByteBuffer b = ByteBuffer.allocate(64);
+        e.header("a", "b");
+        e.encode(b);
+        b.flip();
+        Decoder d = new Decoder(updates.isEmpty() ? initialSize : Collections.max(updates));
+        List<Integer> actual = new ArrayList<>();
+        d.decode(b, true, new DecodingCallback() {
+            @Override
+            public void onDecoded(CharSequence name, CharSequence value) { }
+
+            @Override
+            public void onSizeUpdate(int capacity) {
+                actual.add(capacity);
+            }
+        });
+        assertEquals(actual, expected);
+    }
+
+    //
+    // Default encoder does not need any table, therefore a subclass that
+    // behaves differently is needed
+    //
+    private static Encoder newCustomEncoder(int maxCapacity) {
+        return new Encoder(maxCapacity) {
+            @Override
+            protected int calculateCapacity(int maxCapacity) {
+                return maxCapacity;
+            }
+        };
+    }
+
+    private static void drainInitialUpdate(Encoder e) {
+        ByteBuffer b = ByteBuffer.allocate(4);
+        e.header("a", "b");
+        boolean done;
+        do {
+            done = e.encode(b);
+            b.flip();
+        } while (!done);
+    }
+
+    private static void erase(ByteBuffer buffer) {
+        buffer.clear();
+        while (buffer.hasRemaining()) {
+            buffer.put((byte) 0);
+        }
+        buffer.clear();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HeaderTableTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 2014, 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.internal.net.http.hpack;
+
+import org.testng.annotations.Test;
+import jdk.internal.net.http.hpack.HeaderTable.HeaderField;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.String.format;
+import static org.testng.Assert.assertEquals;
+import static jdk.internal.net.http.hpack.TestHelper.assertExceptionMessageContains;
+import static jdk.internal.net.http.hpack.TestHelper.assertThrows;
+import static jdk.internal.net.http.hpack.TestHelper.assertVoidThrows;
+import static jdk.internal.net.http.hpack.TestHelper.newRandom;
+
+public class HeaderTableTest {
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-A
+    //
+    // @formatter:off
+    private static final String SPEC =
+       "          | 1     | :authority                  |               |\n" +
+       "          | 2     | :method                     | GET           |\n" +
+       "          | 3     | :method                     | POST          |\n" +
+       "          | 4     | :path                       | /             |\n" +
+       "          | 5     | :path                       | /index.html   |\n" +
+       "          | 6     | :scheme                     | http          |\n" +
+       "          | 7     | :scheme                     | https         |\n" +
+       "          | 8     | :status                     | 200           |\n" +
+       "          | 9     | :status                     | 204           |\n" +
+       "          | 10    | :status                     | 206           |\n" +
+       "          | 11    | :status                     | 304           |\n" +
+       "          | 12    | :status                     | 400           |\n" +
+       "          | 13    | :status                     | 404           |\n" +
+       "          | 14    | :status                     | 500           |\n" +
+       "          | 15    | accept-charset              |               |\n" +
+       "          | 16    | accept-encoding             | gzip, deflate |\n" +
+       "          | 17    | accept-language             |               |\n" +
+       "          | 18    | accept-ranges               |               |\n" +
+       "          | 19    | accept                      |               |\n" +
+       "          | 20    | access-control-allow-origin |               |\n" +
+       "          | 21    | age                         |               |\n" +
+       "          | 22    | allow                       |               |\n" +
+       "          | 23    | authorization               |               |\n" +
+       "          | 24    | cache-control               |               |\n" +
+       "          | 25    | content-disposition         |               |\n" +
+       "          | 26    | content-encoding            |               |\n" +
+       "          | 27    | content-language            |               |\n" +
+       "          | 28    | content-length              |               |\n" +
+       "          | 29    | content-location            |               |\n" +
+       "          | 30    | content-range               |               |\n" +
+       "          | 31    | content-type                |               |\n" +
+       "          | 32    | cookie                      |               |\n" +
+       "          | 33    | date                        |               |\n" +
+       "          | 34    | etag                        |               |\n" +
+       "          | 35    | expect                      |               |\n" +
+       "          | 36    | expires                     |               |\n" +
+       "          | 37    | from                        |               |\n" +
+       "          | 38    | host                        |               |\n" +
+       "          | 39    | if-match                    |               |\n" +
+       "          | 40    | if-modified-since           |               |\n" +
+       "          | 41    | if-none-match               |               |\n" +
+       "          | 42    | if-range                    |               |\n" +
+       "          | 43    | if-unmodified-since         |               |\n" +
+       "          | 44    | last-modified               |               |\n" +
+       "          | 45    | link                        |               |\n" +
+       "          | 46    | location                    |               |\n" +
+       "          | 47    | max-forwards                |               |\n" +
+       "          | 48    | proxy-authenticate          |               |\n" +
+       "          | 49    | proxy-authorization         |               |\n" +
+       "          | 50    | range                       |               |\n" +
+       "          | 51    | referer                     |               |\n" +
+       "          | 52    | refresh                     |               |\n" +
+       "          | 53    | retry-after                 |               |\n" +
+       "          | 54    | server                      |               |\n" +
+       "          | 55    | set-cookie                  |               |\n" +
+       "          | 56    | strict-transport-security   |               |\n" +
+       "          | 57    | transfer-encoding           |               |\n" +
+       "          | 58    | user-agent                  |               |\n" +
+       "          | 59    | vary                        |               |\n" +
+       "          | 60    | via                         |               |\n" +
+       "          | 61    | www-authenticate            |               |\n";
+    // @formatter:on
+
+    private static final int STATIC_TABLE_LENGTH = createStaticEntries().size();
+    private final Random rnd = newRandom();
+
+    @Test
+    public void staticData() {
+        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
+        Map<Integer, HeaderField> staticHeaderFields = createStaticEntries();
+
+        Map<String, Integer> minimalIndexes = new HashMap<>();
+
+        for (Map.Entry<Integer, HeaderField> e : staticHeaderFields.entrySet()) {
+            Integer idx = e.getKey();
+            String hName = e.getValue().name;
+            Integer midx = minimalIndexes.get(hName);
+            if (midx == null) {
+                minimalIndexes.put(hName, idx);
+            } else {
+                minimalIndexes.put(hName, Math.min(idx, midx));
+            }
+        }
+
+        staticHeaderFields.entrySet().forEach(
+                e -> {
+                    // lookup
+                    HeaderField actualHeaderField = table.get(e.getKey());
+                    HeaderField expectedHeaderField = e.getValue();
+                    assertEquals(actualHeaderField, expectedHeaderField);
+
+                    // reverse lookup (name, value)
+                    String hName = expectedHeaderField.name;
+                    String hValue = expectedHeaderField.value;
+                    int expectedIndex = e.getKey();
+                    int actualIndex = table.indexOf(hName, hValue);
+
+                    assertEquals(actualIndex, expectedIndex);
+
+                    // reverse lookup (name)
+                    int expectedMinimalIndex = minimalIndexes.get(hName);
+                    int actualMinimalIndex = table.indexOf(hName, "blah-blah");
+
+                    assertEquals(-actualMinimalIndex, expectedMinimalIndex);
+                }
+        );
+    }
+
+    @Test
+    public void constructorSetsMaxSize() {
+        int size = rnd.nextInt(64);
+        HeaderTable t = new HeaderTable(size, HPACK.getLogger());
+        assertEquals(t.size(), 0);
+        assertEquals(t.maxSize(), size);
+    }
+
+    @Test
+    public void negativeMaximumSize() {
+        int maxSize = -(rnd.nextInt(100) + 1); // [-100, -1]
+        IllegalArgumentException e =
+                assertVoidThrows(IllegalArgumentException.class,
+                        () -> new HeaderTable(0, HPACK.getLogger()).setMaxSize(maxSize));
+        assertExceptionMessageContains(e, "maxSize");
+    }
+
+    @Test
+    public void zeroMaximumSize() {
+        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
+        table.setMaxSize(0);
+        assertEquals(table.maxSize(), 0);
+    }
+
+    @Test
+    public void negativeIndex() {
+        int idx = -(rnd.nextInt(256) + 1); // [-256, -1]
+        IndexOutOfBoundsException e =
+                assertVoidThrows(IndexOutOfBoundsException.class,
+                        () -> new HeaderTable(0, HPACK.getLogger()).get(idx));
+        assertExceptionMessageContains(e, "index");
+    }
+
+    @Test
+    public void zeroIndex() {
+        IndexOutOfBoundsException e =
+                assertThrows(IndexOutOfBoundsException.class,
+                        () -> new HeaderTable(0, HPACK.getLogger()).get(0));
+        assertExceptionMessageContains(e, "index");
+    }
+
+    @Test
+    public void length() {
+        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
+        assertEquals(table.length(), STATIC_TABLE_LENGTH);
+    }
+
+    @Test
+    public void indexOutsideStaticRange() {
+        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
+        int idx = table.length() + (rnd.nextInt(256) + 1);
+        IndexOutOfBoundsException e =
+                assertThrows(IndexOutOfBoundsException.class,
+                        () -> table.get(idx));
+        assertExceptionMessageContains(e, "index");
+    }
+
+    @Test
+    public void entryPutAfterStaticArea() {
+        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
+        int idx = table.length() + 1;
+        assertThrows(IndexOutOfBoundsException.class, () -> table.get(idx));
+
+        byte[] bytes = new byte[32];
+        rnd.nextBytes(bytes);
+        String name = new String(bytes, StandardCharsets.ISO_8859_1);
+        String value = "custom-value";
+
+        table.put(name, value);
+        HeaderField f = table.get(idx);
+        assertEquals(name, f.name);
+        assertEquals(value, f.value);
+    }
+
+    @Test
+    public void staticTableHasZeroSize() {
+        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
+        assertEquals(0, table.size());
+    }
+
+    @Test
+    public void lowerIndexPriority() {
+        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
+        int oldLength = table.length();
+        table.put("bender", "rodriguez");
+        table.put("bender", "rodriguez");
+        table.put("bender", "rodriguez");
+
+        assertEquals(table.length(), oldLength + 3); // more like an assumption
+        int i = table.indexOf("bender", "rodriguez");
+        assertEquals(oldLength + 1, i);
+    }
+
+    @Test
+    public void lowerIndexPriority2() {
+        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
+        int oldLength = table.length();
+        int idx = rnd.nextInt(oldLength) + 1;
+        HeaderField f = table.get(idx);
+        table.put(f.name, f.value);
+        assertEquals(table.length(), oldLength + 1);
+        int i = table.indexOf(f.name, f.value);
+        assertEquals(idx, i);
+    }
+
+    // TODO: negative indexes check
+    // TODO: ensure full table clearance when adding huge header field
+    // TODO: ensure eviction deletes minimum needed entries, not more
+
+    @Test
+    public void fifo() {
+        // Let's add a series of header fields
+        int NUM_HEADERS = 32;
+        HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
+        //                                ^   ^
+        //                   entry overhead   symbols per entry (max 2x2 digits)
+        for (int i = 1; i <= NUM_HEADERS; i++) {
+            String s = String.valueOf(i);
+            t.put(s, s);
+        }
+        // They MUST appear in a FIFO order:
+        //   newer entries are at lower indexes
+        //   older entries are at higher indexes
+        for (int j = 1; j <= NUM_HEADERS; j++) {
+            HeaderField f = t.get(STATIC_TABLE_LENGTH + j);
+            int actualName = Integer.parseInt(f.name);
+            int expectedName = NUM_HEADERS - j + 1;
+            assertEquals(expectedName, actualName);
+        }
+        // Entries MUST be evicted in the order they were added:
+        //   the newer the entry the later it is evicted
+        for (int k = 1; k <= NUM_HEADERS; k++) {
+            HeaderField f = t.evictEntry();
+            assertEquals(String.valueOf(k), f.name);
+        }
+    }
+
+    @Test
+    public void indexOf() {
+        // Let's put a series of header fields
+        int NUM_HEADERS = 32;
+        HeaderTable t = new HeaderTable((32 + 4) * NUM_HEADERS, HPACK.getLogger());
+        //                                ^   ^
+        //                   entry overhead   symbols per entry (max 2x2 digits)
+        for (int i = 1; i <= NUM_HEADERS; i++) {
+            String s = String.valueOf(i);
+            t.put(s, s);
+        }
+        // and verify indexOf (reverse lookup) returns correct indexes for
+        // full lookup
+        for (int j = 1; j <= NUM_HEADERS; j++) {
+            String s = String.valueOf(j);
+            int actualIndex = t.indexOf(s, s);
+            int expectedIndex = STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1;
+            assertEquals(expectedIndex, actualIndex);
+        }
+        // as well as for just a name lookup
+        for (int j = 1; j <= NUM_HEADERS; j++) {
+            String s = String.valueOf(j);
+            int actualIndex = t.indexOf(s, "blah");
+            int expectedIndex = -(STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1);
+            assertEquals(expectedIndex, actualIndex);
+        }
+        // lookup for non-existent name returns 0
+        assertEquals(0, t.indexOf("chupacabra", "1"));
+    }
+
+    @Test
+    public void testToString() {
+        testToString0();
+    }
+
+    @Test
+    public void testToStringDifferentLocale() {
+        Locale locale = Locale.getDefault();
+        Locale.setDefault(Locale.FRENCH);
+        try {
+            String s = format("%.1f", 3.1);
+            assertEquals("3,1", s); // assumption of the test, otherwise the test is useless
+            testToString0();
+        } finally {
+            Locale.setDefault(locale);
+        }
+    }
+
+    private void testToString0() {
+        HeaderTable table = new HeaderTable(0, HPACK.getLogger());
+        {
+            int maxSize = 2048;
+            table.setMaxSize(maxSize);
+            String expected = format(
+                    "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
+                    0, STATIC_TABLE_LENGTH, 0, maxSize, 0.0);
+            assertEquals(expected, table.toString());
+        }
+
+        {
+            String name = "custom-name";
+            String value = "custom-value";
+            int size = 512;
+
+            table.setMaxSize(size);
+            table.put(name, value);
+            String s = table.toString();
+
+            int used = name.length() + value.length() + 32;
+            double ratio = used * 100.0 / size;
+
+            String expected = format(
+                    "dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
+                    1, STATIC_TABLE_LENGTH + 1, used, size, ratio);
+            assertEquals(expected, s);
+        }
+
+        {
+            table.setMaxSize(78);
+            table.put(":method", "");
+            table.put(":status", "");
+            String s = table.toString();
+            String expected =
+                    format("dynamic length: %s, full length: %s, used space: %s/%s (%.1f%%)",
+                           2, STATIC_TABLE_LENGTH + 2, 78, 78, 100.0);
+            assertEquals(expected, s);
+        }
+    }
+
+    @Test
+    public void stateString() {
+        HeaderTable table = new HeaderTable(256, HPACK.getLogger());
+        table.put("custom-key", "custom-header");
+        // @formatter:off
+        assertEquals("[  1] (s =  55) custom-key: custom-header\n" +
+                     "      Table size:  55", table.getStateString());
+        // @formatter:on
+    }
+
+    private static Map<Integer, HeaderField> createStaticEntries() {
+        Pattern line = Pattern.compile(
+                "\\|\\s*(?<index>\\d+?)\\s*\\|\\s*(?<name>.+?)\\s*\\|\\s*(?<value>.*?)\\s*\\|");
+        Matcher m = line.matcher(SPEC);
+        Map<Integer, HeaderField> result = new HashMap<>();
+        while (m.find()) {
+            int index = Integer.parseInt(m.group("index"));
+            String name = m.group("name");
+            String value = m.group("value");
+            HeaderField f = new HeaderField(name, value);
+            result.put(index, f);
+        }
+        return Collections.unmodifiableMap(result); // lol
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HuffmanTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,629 @@
+/*
+ * 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.internal.net.http.hpack;
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.Integer.parseInt;
+import static org.testng.Assert.*;
+
+public final class HuffmanTest {
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-B
+    //
+    private static final String SPEC =
+            // @formatter:off
+     "                          code as bits                 as hex   len\n" +
+     "        sym              aligned to MSB                aligned   in\n" +
+     "                                                       to LSB   bits\n" +
+     "       (  0)  |11111111|11000                             1ff8  [13]\n" +
+     "       (  1)  |11111111|11111111|1011000                7fffd8  [23]\n" +
+     "       (  2)  |11111111|11111111|11111110|0010         fffffe2  [28]\n" +
+     "       (  3)  |11111111|11111111|11111110|0011         fffffe3  [28]\n" +
+     "       (  4)  |11111111|11111111|11111110|0100         fffffe4  [28]\n" +
+     "       (  5)  |11111111|11111111|11111110|0101         fffffe5  [28]\n" +
+     "       (  6)  |11111111|11111111|11111110|0110         fffffe6  [28]\n" +
+     "       (  7)  |11111111|11111111|11111110|0111         fffffe7  [28]\n" +
+     "       (  8)  |11111111|11111111|11111110|1000         fffffe8  [28]\n" +
+     "       (  9)  |11111111|11111111|11101010               ffffea  [24]\n" +
+     "       ( 10)  |11111111|11111111|11111111|111100      3ffffffc  [30]\n" +
+     "       ( 11)  |11111111|11111111|11111110|1001         fffffe9  [28]\n" +
+     "       ( 12)  |11111111|11111111|11111110|1010         fffffea  [28]\n" +
+     "       ( 13)  |11111111|11111111|11111111|111101      3ffffffd  [30]\n" +
+     "       ( 14)  |11111111|11111111|11111110|1011         fffffeb  [28]\n" +
+     "       ( 15)  |11111111|11111111|11111110|1100         fffffec  [28]\n" +
+     "       ( 16)  |11111111|11111111|11111110|1101         fffffed  [28]\n" +
+     "       ( 17)  |11111111|11111111|11111110|1110         fffffee  [28]\n" +
+     "       ( 18)  |11111111|11111111|11111110|1111         fffffef  [28]\n" +
+     "       ( 19)  |11111111|11111111|11111111|0000         ffffff0  [28]\n" +
+     "       ( 20)  |11111111|11111111|11111111|0001         ffffff1  [28]\n" +
+     "       ( 21)  |11111111|11111111|11111111|0010         ffffff2  [28]\n" +
+     "       ( 22)  |11111111|11111111|11111111|111110      3ffffffe  [30]\n" +
+     "       ( 23)  |11111111|11111111|11111111|0011         ffffff3  [28]\n" +
+     "       ( 24)  |11111111|11111111|11111111|0100         ffffff4  [28]\n" +
+     "       ( 25)  |11111111|11111111|11111111|0101         ffffff5  [28]\n" +
+     "       ( 26)  |11111111|11111111|11111111|0110         ffffff6  [28]\n" +
+     "       ( 27)  |11111111|11111111|11111111|0111         ffffff7  [28]\n" +
+     "       ( 28)  |11111111|11111111|11111111|1000         ffffff8  [28]\n" +
+     "       ( 29)  |11111111|11111111|11111111|1001         ffffff9  [28]\n" +
+     "       ( 30)  |11111111|11111111|11111111|1010         ffffffa  [28]\n" +
+     "       ( 31)  |11111111|11111111|11111111|1011         ffffffb  [28]\n" +
+     "   ' ' ( 32)  |010100                                       14  [ 6]\n" +
+     "   '!' ( 33)  |11111110|00                                 3f8  [10]\n" +
+     "  '\"' ( 34)  |11111110|01                                 3f9  [10]\n" +
+     "   '#' ( 35)  |11111111|1010                               ffa  [12]\n" +
+     "   '$' ( 36)  |11111111|11001                             1ff9  [13]\n" +
+     "   '%' ( 37)  |010101                                       15  [ 6]\n" +
+     "   '&' ( 38)  |11111000                                     f8  [ 8]\n" +
+     "   ''' ( 39)  |11111111|010                                7fa  [11]\n" +
+     "   '(' ( 40)  |11111110|10                                 3fa  [10]\n" +
+     "   ')' ( 41)  |11111110|11                                 3fb  [10]\n" +
+     "   '*' ( 42)  |11111001                                     f9  [ 8]\n" +
+     "   '+' ( 43)  |11111111|011                                7fb  [11]\n" +
+     "   ',' ( 44)  |11111010                                     fa  [ 8]\n" +
+     "   '-' ( 45)  |010110                                       16  [ 6]\n" +
+     "   '.' ( 46)  |010111                                       17  [ 6]\n" +
+     "   '/' ( 47)  |011000                                       18  [ 6]\n" +
+     "   '0' ( 48)  |00000                                         0  [ 5]\n" +
+     "   '1' ( 49)  |00001                                         1  [ 5]\n" +
+     "   '2' ( 50)  |00010                                         2  [ 5]\n" +
+     "   '3' ( 51)  |011001                                       19  [ 6]\n" +
+     "   '4' ( 52)  |011010                                       1a  [ 6]\n" +
+     "   '5' ( 53)  |011011                                       1b  [ 6]\n" +
+     "   '6' ( 54)  |011100                                       1c  [ 6]\n" +
+     "   '7' ( 55)  |011101                                       1d  [ 6]\n" +
+     "   '8' ( 56)  |011110                                       1e  [ 6]\n" +
+     "   '9' ( 57)  |011111                                       1f  [ 6]\n" +
+     "   ':' ( 58)  |1011100                                      5c  [ 7]\n" +
+     "   ';' ( 59)  |11111011                                     fb  [ 8]\n" +
+     "   '<' ( 60)  |11111111|1111100                           7ffc  [15]\n" +
+     "   '=' ( 61)  |100000                                       20  [ 6]\n" +
+     "   '>' ( 62)  |11111111|1011                               ffb  [12]\n" +
+     "   '?' ( 63)  |11111111|00                                 3fc  [10]\n" +
+     "   '@' ( 64)  |11111111|11010                             1ffa  [13]\n" +
+     "   'A' ( 65)  |100001                                       21  [ 6]\n" +
+     "   'B' ( 66)  |1011101                                      5d  [ 7]\n" +
+     "   'C' ( 67)  |1011110                                      5e  [ 7]\n" +
+     "   'D' ( 68)  |1011111                                      5f  [ 7]\n" +
+     "   'E' ( 69)  |1100000                                      60  [ 7]\n" +
+     "   'F' ( 70)  |1100001                                      61  [ 7]\n" +
+     "   'G' ( 71)  |1100010                                      62  [ 7]\n" +
+     "   'H' ( 72)  |1100011                                      63  [ 7]\n" +
+     "   'I' ( 73)  |1100100                                      64  [ 7]\n" +
+     "   'J' ( 74)  |1100101                                      65  [ 7]\n" +
+     "   'K' ( 75)  |1100110                                      66  [ 7]\n" +
+     "   'L' ( 76)  |1100111                                      67  [ 7]\n" +
+     "   'M' ( 77)  |1101000                                      68  [ 7]\n" +
+     "   'N' ( 78)  |1101001                                      69  [ 7]\n" +
+     "   'O' ( 79)  |1101010                                      6a  [ 7]\n" +
+     "   'P' ( 80)  |1101011                                      6b  [ 7]\n" +
+     "   'Q' ( 81)  |1101100                                      6c  [ 7]\n" +
+     "   'R' ( 82)  |1101101                                      6d  [ 7]\n" +
+     "   'S' ( 83)  |1101110                                      6e  [ 7]\n" +
+     "   'T' ( 84)  |1101111                                      6f  [ 7]\n" +
+     "   'U' ( 85)  |1110000                                      70  [ 7]\n" +
+     "   'V' ( 86)  |1110001                                      71  [ 7]\n" +
+     "   'W' ( 87)  |1110010                                      72  [ 7]\n" +
+     "   'X' ( 88)  |11111100                                     fc  [ 8]\n" +
+     "   'Y' ( 89)  |1110011                                      73  [ 7]\n" +
+     "   'Z' ( 90)  |11111101                                     fd  [ 8]\n" +
+     "   '[' ( 91)  |11111111|11011                             1ffb  [13]\n" +
+     "  '\\' ( 92)  |11111111|11111110|000                     7fff0  [19]\n" +
+     "   ']' ( 93)  |11111111|11100                             1ffc  [13]\n" +
+     "   '^' ( 94)  |11111111|111100                            3ffc  [14]\n" +
+     "   '_' ( 95)  |100010                                       22  [ 6]\n" +
+     "   '`' ( 96)  |11111111|1111101                           7ffd  [15]\n" +
+     "   'a' ( 97)  |00011                                         3  [ 5]\n" +
+     "   'b' ( 98)  |100011                                       23  [ 6]\n" +
+     "   'c' ( 99)  |00100                                         4  [ 5]\n" +
+     "   'd' (100)  |100100                                       24  [ 6]\n" +
+     "   'e' (101)  |00101                                         5  [ 5]\n" +
+     "   'f' (102)  |100101                                       25  [ 6]\n" +
+     "   'g' (103)  |100110                                       26  [ 6]\n" +
+     "   'h' (104)  |100111                                       27  [ 6]\n" +
+     "   'i' (105)  |00110                                         6  [ 5]\n" +
+     "   'j' (106)  |1110100                                      74  [ 7]\n" +
+     "   'k' (107)  |1110101                                      75  [ 7]\n" +
+     "   'l' (108)  |101000                                       28  [ 6]\n" +
+     "   'm' (109)  |101001                                       29  [ 6]\n" +
+     "   'n' (110)  |101010                                       2a  [ 6]\n" +
+     "   'o' (111)  |00111                                         7  [ 5]\n" +
+     "   'p' (112)  |101011                                       2b  [ 6]\n" +
+     "   'q' (113)  |1110110                                      76  [ 7]\n" +
+     "   'r' (114)  |101100                                       2c  [ 6]\n" +
+     "   's' (115)  |01000                                         8  [ 5]\n" +
+     "   't' (116)  |01001                                         9  [ 5]\n" +
+     "   'u' (117)  |101101                                       2d  [ 6]\n" +
+     "   'v' (118)  |1110111                                      77  [ 7]\n" +
+     "   'w' (119)  |1111000                                      78  [ 7]\n" +
+     "   'x' (120)  |1111001                                      79  [ 7]\n" +
+     "   'y' (121)  |1111010                                      7a  [ 7]\n" +
+     "   'z' (122)  |1111011                                      7b  [ 7]\n" +
+     "   '{' (123)  |11111111|1111110                           7ffe  [15]\n" +
+     "   '|' (124)  |11111111|100                                7fc  [11]\n" +
+     "   '}' (125)  |11111111|111101                            3ffd  [14]\n" +
+     "   '~' (126)  |11111111|11101                             1ffd  [13]\n" +
+     "       (127)  |11111111|11111111|11111111|1100         ffffffc  [28]\n" +
+     "       (128)  |11111111|11111110|0110                    fffe6  [20]\n" +
+     "       (129)  |11111111|11111111|010010                 3fffd2  [22]\n" +
+     "       (130)  |11111111|11111110|0111                    fffe7  [20]\n" +
+     "       (131)  |11111111|11111110|1000                    fffe8  [20]\n" +
+     "       (132)  |11111111|11111111|010011                 3fffd3  [22]\n" +
+     "       (133)  |11111111|11111111|010100                 3fffd4  [22]\n" +
+     "       (134)  |11111111|11111111|010101                 3fffd5  [22]\n" +
+     "       (135)  |11111111|11111111|1011001                7fffd9  [23]\n" +
+     "       (136)  |11111111|11111111|010110                 3fffd6  [22]\n" +
+     "       (137)  |11111111|11111111|1011010                7fffda  [23]\n" +
+     "       (138)  |11111111|11111111|1011011                7fffdb  [23]\n" +
+     "       (139)  |11111111|11111111|1011100                7fffdc  [23]\n" +
+     "       (140)  |11111111|11111111|1011101                7fffdd  [23]\n" +
+     "       (141)  |11111111|11111111|1011110                7fffde  [23]\n" +
+     "       (142)  |11111111|11111111|11101011               ffffeb  [24]\n" +
+     "       (143)  |11111111|11111111|1011111                7fffdf  [23]\n" +
+     "       (144)  |11111111|11111111|11101100               ffffec  [24]\n" +
+     "       (145)  |11111111|11111111|11101101               ffffed  [24]\n" +
+     "       (146)  |11111111|11111111|010111                 3fffd7  [22]\n" +
+     "       (147)  |11111111|11111111|1100000                7fffe0  [23]\n" +
+     "       (148)  |11111111|11111111|11101110               ffffee  [24]\n" +
+     "       (149)  |11111111|11111111|1100001                7fffe1  [23]\n" +
+     "       (150)  |11111111|11111111|1100010                7fffe2  [23]\n" +
+     "       (151)  |11111111|11111111|1100011                7fffe3  [23]\n" +
+     "       (152)  |11111111|11111111|1100100                7fffe4  [23]\n" +
+     "       (153)  |11111111|11111110|11100                  1fffdc  [21]\n" +
+     "       (154)  |11111111|11111111|011000                 3fffd8  [22]\n" +
+     "       (155)  |11111111|11111111|1100101                7fffe5  [23]\n" +
+     "       (156)  |11111111|11111111|011001                 3fffd9  [22]\n" +
+     "       (157)  |11111111|11111111|1100110                7fffe6  [23]\n" +
+     "       (158)  |11111111|11111111|1100111                7fffe7  [23]\n" +
+     "       (159)  |11111111|11111111|11101111               ffffef  [24]\n" +
+     "       (160)  |11111111|11111111|011010                 3fffda  [22]\n" +
+     "       (161)  |11111111|11111110|11101                  1fffdd  [21]\n" +
+     "       (162)  |11111111|11111110|1001                    fffe9  [20]\n" +
+     "       (163)  |11111111|11111111|011011                 3fffdb  [22]\n" +
+     "       (164)  |11111111|11111111|011100                 3fffdc  [22]\n" +
+     "       (165)  |11111111|11111111|1101000                7fffe8  [23]\n" +
+     "       (166)  |11111111|11111111|1101001                7fffe9  [23]\n" +
+     "       (167)  |11111111|11111110|11110                  1fffde  [21]\n" +
+     "       (168)  |11111111|11111111|1101010                7fffea  [23]\n" +
+     "       (169)  |11111111|11111111|011101                 3fffdd  [22]\n" +
+     "       (170)  |11111111|11111111|011110                 3fffde  [22]\n" +
+     "       (171)  |11111111|11111111|11110000               fffff0  [24]\n" +
+     "       (172)  |11111111|11111110|11111                  1fffdf  [21]\n" +
+     "       (173)  |11111111|11111111|011111                 3fffdf  [22]\n" +
+     "       (174)  |11111111|11111111|1101011                7fffeb  [23]\n" +
+     "       (175)  |11111111|11111111|1101100                7fffec  [23]\n" +
+     "       (176)  |11111111|11111111|00000                  1fffe0  [21]\n" +
+     "       (177)  |11111111|11111111|00001                  1fffe1  [21]\n" +
+     "       (178)  |11111111|11111111|100000                 3fffe0  [22]\n" +
+     "       (179)  |11111111|11111111|00010                  1fffe2  [21]\n" +
+     "       (180)  |11111111|11111111|1101101                7fffed  [23]\n" +
+     "       (181)  |11111111|11111111|100001                 3fffe1  [22]\n" +
+     "       (182)  |11111111|11111111|1101110                7fffee  [23]\n" +
+     "       (183)  |11111111|11111111|1101111                7fffef  [23]\n" +
+     "       (184)  |11111111|11111110|1010                    fffea  [20]\n" +
+     "       (185)  |11111111|11111111|100010                 3fffe2  [22]\n" +
+     "       (186)  |11111111|11111111|100011                 3fffe3  [22]\n" +
+     "       (187)  |11111111|11111111|100100                 3fffe4  [22]\n" +
+     "       (188)  |11111111|11111111|1110000                7ffff0  [23]\n" +
+     "       (189)  |11111111|11111111|100101                 3fffe5  [22]\n" +
+     "       (190)  |11111111|11111111|100110                 3fffe6  [22]\n" +
+     "       (191)  |11111111|11111111|1110001                7ffff1  [23]\n" +
+     "       (192)  |11111111|11111111|11111000|00           3ffffe0  [26]\n" +
+     "       (193)  |11111111|11111111|11111000|01           3ffffe1  [26]\n" +
+     "       (194)  |11111111|11111110|1011                    fffeb  [20]\n" +
+     "       (195)  |11111111|11111110|001                     7fff1  [19]\n" +
+     "       (196)  |11111111|11111111|100111                 3fffe7  [22]\n" +
+     "       (197)  |11111111|11111111|1110010                7ffff2  [23]\n" +
+     "       (198)  |11111111|11111111|101000                 3fffe8  [22]\n" +
+     "       (199)  |11111111|11111111|11110110|0            1ffffec  [25]\n" +
+     "       (200)  |11111111|11111111|11111000|10           3ffffe2  [26]\n" +
+     "       (201)  |11111111|11111111|11111000|11           3ffffe3  [26]\n" +
+     "       (202)  |11111111|11111111|11111001|00           3ffffe4  [26]\n" +
+     "       (203)  |11111111|11111111|11111011|110          7ffffde  [27]\n" +
+     "       (204)  |11111111|11111111|11111011|111          7ffffdf  [27]\n" +
+     "       (205)  |11111111|11111111|11111001|01           3ffffe5  [26]\n" +
+     "       (206)  |11111111|11111111|11110001               fffff1  [24]\n" +
+     "       (207)  |11111111|11111111|11110110|1            1ffffed  [25]\n" +
+     "       (208)  |11111111|11111110|010                     7fff2  [19]\n" +
+     "       (209)  |11111111|11111111|00011                  1fffe3  [21]\n" +
+     "       (210)  |11111111|11111111|11111001|10           3ffffe6  [26]\n" +
+     "       (211)  |11111111|11111111|11111100|000          7ffffe0  [27]\n" +
+     "       (212)  |11111111|11111111|11111100|001          7ffffe1  [27]\n" +
+     "       (213)  |11111111|11111111|11111001|11           3ffffe7  [26]\n" +
+     "       (214)  |11111111|11111111|11111100|010          7ffffe2  [27]\n" +
+     "       (215)  |11111111|11111111|11110010               fffff2  [24]\n" +
+     "       (216)  |11111111|11111111|00100                  1fffe4  [21]\n" +
+     "       (217)  |11111111|11111111|00101                  1fffe5  [21]\n" +
+     "       (218)  |11111111|11111111|11111010|00           3ffffe8  [26]\n" +
+     "       (219)  |11111111|11111111|11111010|01           3ffffe9  [26]\n" +
+     "       (220)  |11111111|11111111|11111111|1101         ffffffd  [28]\n" +
+     "       (221)  |11111111|11111111|11111100|011          7ffffe3  [27]\n" +
+     "       (222)  |11111111|11111111|11111100|100          7ffffe4  [27]\n" +
+     "       (223)  |11111111|11111111|11111100|101          7ffffe5  [27]\n" +
+     "       (224)  |11111111|11111110|1100                    fffec  [20]\n" +
+     "       (225)  |11111111|11111111|11110011               fffff3  [24]\n" +
+     "       (226)  |11111111|11111110|1101                    fffed  [20]\n" +
+     "       (227)  |11111111|11111111|00110                  1fffe6  [21]\n" +
+     "       (228)  |11111111|11111111|101001                 3fffe9  [22]\n" +
+     "       (229)  |11111111|11111111|00111                  1fffe7  [21]\n" +
+     "       (230)  |11111111|11111111|01000                  1fffe8  [21]\n" +
+     "       (231)  |11111111|11111111|1110011                7ffff3  [23]\n" +
+     "       (232)  |11111111|11111111|101010                 3fffea  [22]\n" +
+     "       (233)  |11111111|11111111|101011                 3fffeb  [22]\n" +
+     "       (234)  |11111111|11111111|11110111|0            1ffffee  [25]\n" +
+     "       (235)  |11111111|11111111|11110111|1            1ffffef  [25]\n" +
+     "       (236)  |11111111|11111111|11110100               fffff4  [24]\n" +
+     "       (237)  |11111111|11111111|11110101               fffff5  [24]\n" +
+     "       (238)  |11111111|11111111|11111010|10           3ffffea  [26]\n" +
+     "       (239)  |11111111|11111111|1110100                7ffff4  [23]\n" +
+     "       (240)  |11111111|11111111|11111010|11           3ffffeb  [26]\n" +
+     "       (241)  |11111111|11111111|11111100|110          7ffffe6  [27]\n" +
+     "       (242)  |11111111|11111111|11111011|00           3ffffec  [26]\n" +
+     "       (243)  |11111111|11111111|11111011|01           3ffffed  [26]\n" +
+     "       (244)  |11111111|11111111|11111100|111          7ffffe7  [27]\n" +
+     "       (245)  |11111111|11111111|11111101|000          7ffffe8  [27]\n" +
+     "       (246)  |11111111|11111111|11111101|001          7ffffe9  [27]\n" +
+     "       (247)  |11111111|11111111|11111101|010          7ffffea  [27]\n" +
+     "       (248)  |11111111|11111111|11111101|011          7ffffeb  [27]\n" +
+     "       (249)  |11111111|11111111|11111111|1110         ffffffe  [28]\n" +
+     "       (250)  |11111111|11111111|11111101|100          7ffffec  [27]\n" +
+     "       (251)  |11111111|11111111|11111101|101          7ffffed  [27]\n" +
+     "       (252)  |11111111|11111111|11111101|110          7ffffee  [27]\n" +
+     "       (253)  |11111111|11111111|11111101|111          7ffffef  [27]\n" +
+     "       (254)  |11111111|11111111|11111110|000          7fffff0  [27]\n" +
+     "       (255)  |11111111|11111111|11111011|10           3ffffee  [26]\n" +
+     "   EOS (256)  |11111111|11111111|11111111|111111      3fffffff  [30]";
+    // @formatter:on
+
+    @Test
+    public void read_table() throws IOException {
+        Pattern line = Pattern.compile(
+                "\\(\\s*(?<ascii>\\d+)\\s*\\)\\s*(?<binary>(\\|(0|1)+)+)\\s*" +
+                        "(?<hex>[0-9a-zA-Z]+)\\s*\\[\\s*(?<len>\\d+)\\s*\\]");
+        Matcher m = line.matcher(SPEC);
+        int i = 0;
+        while (m.find()) {
+            String ascii = m.group("ascii");
+            String binary = m.group("binary").replaceAll("\\|", "");
+            String hex = m.group("hex");
+            String len = m.group("len");
+
+            // Several sanity checks for the data read from the table, just to
+            // make sure what we read makes sense
+            assertEquals(parseInt(len), binary.length());
+            assertEquals(parseInt(binary, 2), parseInt(hex, 16));
+
+            int expected = parseInt(ascii);
+
+            // TODO: find actual eos, do not hardcode it!
+            byte[] bytes = intToBytes(0x3fffffff, 30,
+                    parseInt(hex, 16), parseInt(len));
+
+            StringBuilder actual = new StringBuilder();
+            Huffman.Reader t = new Huffman.Reader();
+            t.read(ByteBuffer.wrap(bytes), actual, false, true);
+
+            // What has been read MUST represent a single symbol
+            assertEquals(actual.length(), 1, "ascii: " + ascii);
+
+            // It's a lot more visual to compare char as codes rather than
+            // characters (as some of them might not be visible)
+            assertEquals(actual.charAt(0), expected);
+            i++;
+        }
+        assertEquals(i, 257); // 256 + EOS
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.4.1
+    //
+    @Test
+    public void read_1() {
+        read("f1e3 c2e5 f23a 6ba0 ab90 f4ff", "www.example.com");
+    }
+
+    @Test
+    public void write_1() {
+        write("www.example.com", "f1e3 c2e5 f23a 6ba0 ab90 f4ff");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.4.2
+    //
+    @Test
+    public void read_2() {
+        read("a8eb 1064 9cbf", "no-cache");
+    }
+
+    @Test
+    public void write_2() {
+        write("no-cache", "a8eb 1064 9cbf");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.4.3
+    //
+    @Test
+    public void read_3() {
+        read("25a8 49e9 5ba9 7d7f", "custom-key");
+    }
+
+    @Test
+    public void write_3() {
+        write("custom-key", "25a8 49e9 5ba9 7d7f");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.4.3
+    //
+    @Test
+    public void read_4() {
+        read("25a8 49e9 5bb8 e8b4 bf", "custom-value");
+    }
+
+    @Test
+    public void write_4() {
+        write("custom-value", "25a8 49e9 5bb8 e8b4 bf");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+    //
+    @Test
+    public void read_5() {
+        read("6402", "302");
+    }
+
+    @Test
+    public void write_5() {
+        write("302", "6402");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+    //
+    @Test
+    public void read_6() {
+        read("aec3 771a 4b", "private");
+    }
+
+    @Test
+    public void write_6() {
+        write("private", "aec3 771a 4b");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+    //
+    @Test
+    public void read_7() {
+        read("d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff",
+                "Mon, 21 Oct 2013 20:13:21 GMT");
+    }
+
+    @Test
+    public void write_7() {
+        write("Mon, 21 Oct 2013 20:13:21 GMT",
+                "d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+    //
+    @Test
+    public void read_8() {
+        read("9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3",
+                "https://www.example.com");
+    }
+
+    @Test
+    public void write_8() {
+        write("https://www.example.com",
+                "9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.2
+    //
+    @Test
+    public void read_9() {
+        read("640e ff", "307");
+    }
+
+    @Test
+    public void write_9() {
+        write("307", "640e ff");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
+    //
+    @Test
+    public void read_10() {
+        read("d07a be94 1054 d444 a820 0595 040b 8166 e084 a62d 1bff",
+                "Mon, 21 Oct 2013 20:13:22 GMT");
+    }
+
+    @Test
+    public void write_10() {
+        write("Mon, 21 Oct 2013 20:13:22 GMT",
+                "d07a be94 1054 d444 a820 0595 040b 8166 e084 a62d 1bff");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
+    //
+    @Test
+    public void read_11() {
+        read("9bd9 ab", "gzip");
+    }
+
+    @Test
+    public void write_11() {
+        write("gzip", "9bd9 ab");
+    }
+
+    //
+    // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
+    //
+    @Test
+    public void read_12() {
+        read("94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 " +
+             "d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 " +
+             "3160 65c0 03ed 4ee5 b106 3d50 07",
+             "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
+    }
+
+    @Test
+    public void test_trie_has_no_empty_nodes() {
+        Huffman.Node root = Huffman.INSTANCE.getRoot();
+        Stack<Huffman.Node> backlog = new Stack<>();
+        backlog.push(root);
+        while (!backlog.isEmpty()) {
+            Huffman.Node n = backlog.pop();
+            // The only type of nodes we couldn't possibly catch during
+            // construction is an empty node: no children and no char
+            if (n.left != null) {
+                backlog.push(n.left);
+            }
+            if (n.right != null) {
+                backlog.push(n.right);
+            }
+            assertFalse(!n.charIsSet && n.left == null && n.right == null,
+                    "Empty node in the trie");
+        }
+    }
+
+    @Test
+    public void test_trie_has_257_nodes() {
+        int count = 0;
+        Huffman.Node root = Huffman.INSTANCE.getRoot();
+        Stack<Huffman.Node> backlog = new Stack<>();
+        backlog.push(root);
+        while (!backlog.isEmpty()) {
+            Huffman.Node n = backlog.pop();
+            if (n.left != null) {
+                backlog.push(n.left);
+            }
+            if (n.right != null) {
+                backlog.push(n.right);
+            }
+            if (n.isLeaf()) {
+                count++;
+            }
+        }
+        assertEquals(count, 257);
+    }
+
+    @Test
+    public void cant_encode_outside_byte() {
+        TestHelper.Block<Object> coding =
+                () -> new Huffman.Writer()
+                        .from(((char) 256) + "", 0, 1)
+                        .write(ByteBuffer.allocate(1));
+        RuntimeException e =
+                TestHelper.assertVoidThrows(RuntimeException.class, coding);
+        TestHelper.assertExceptionMessageContains(e, "char");
+    }
+
+    private static void read(String hexdump, String decoded) {
+        ByteBuffer source = SpecHelper.toBytes(hexdump);
+        Appendable actual = new StringBuilder();
+        try {
+            new Huffman.Reader().read(source, actual, true);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        assertEquals(actual.toString(), decoded);
+    }
+
+    private static void write(String decoded, String hexdump) {
+        int n = Huffman.INSTANCE.lengthOf(decoded);
+        ByteBuffer destination = ByteBuffer.allocate(n); // Extra margin (1) to test having more bytes in the destination than needed is ok
+        Huffman.Writer writer = new Huffman.Writer();
+        BuffersTestingKit.forEachSplit(destination, byteBuffers -> {
+            writer.from(decoded, 0, decoded.length());
+            boolean written = false;
+            for (ByteBuffer b : byteBuffers) {
+                int pos = b.position();
+                written = writer.write(b);
+                b.position(pos);
+            }
+            assertTrue(written);
+            ByteBuffer concated = BuffersTestingKit.concat(byteBuffers);
+            String actual = SpecHelper.toHexdump(concated);
+            assertEquals(actual, hexdump);
+            writer.reset();
+        });
+    }
+
+    //
+    // It's not very pretty, yes I know that
+    //
+    //      hex:
+    //
+    //      |31|30|...|N-1|...|01|00|
+    //                  \        /
+    //                  codeLength
+    //
+    //      hex <<= 32 - codeLength; (align to MSB):
+    //
+    //      |31|30|...|32-N|...|01|00|
+    //        \        /
+    //        codeLength
+    //
+    //      EOS:
+    //
+    //      |31|30|...|M-1|...|01|00|
+    //                   \        /
+    //                   eosLength
+    //
+    //      eos <<= 32 - eosLength; (align to MSB):
+    //
+    //      pad with MSBs of EOS:
+    //
+    //      |31|30|...|32-N|32-N-1|...|01|00|
+    //                     |    32|...|
+    //
+    //      Finally, split into byte[]
+    //
+    private byte[] intToBytes(int eos, int eosLength, int hex, int codeLength) {
+        hex <<= 32 - codeLength;
+        eos >>= codeLength - (32 - eosLength);
+        hex |= eos;
+        int n = (int) Math.ceil(codeLength / 8.0);
+        byte[] result = new byte[n];
+        for (int i = 0; i < n; i++) {
+            result[i] = (byte) (hex >> (32 - 8 * (i + 1)));
+        }
+        return result;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/SpecHelper.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2014, 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.internal.net.http.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+//
+// THIS IS NOT A TEST
+//
+public final class SpecHelper {
+
+    private SpecHelper() {
+        throw new AssertionError();
+    }
+
+    public static ByteBuffer toBytes(String hexdump) {
+        Pattern hexByte = Pattern.compile("[0-9a-fA-F]{2}");
+        List<String> bytes = new ArrayList<>();
+        Matcher matcher = hexByte.matcher(hexdump);
+        while (matcher.find()) {
+            bytes.add(matcher.group(0));
+        }
+        ByteBuffer result = ByteBuffer.allocate(bytes.size());
+        for (String f : bytes) {
+            result.put((byte) Integer.parseInt(f, 16));
+        }
+        result.flip();
+        return result;
+    }
+
+    public static String toHexdump(ByteBuffer bb) {
+        List<String> words = new ArrayList<>();
+        int i = 0;
+        while (bb.hasRemaining()) {
+            if (i % 2 == 0) {
+                words.add("");
+            }
+            byte b = bb.get();
+            String hex = Integer.toHexString(256 + Byte.toUnsignedInt(b)).substring(1);
+            words.set(i / 2, words.get(i / 2) + hex);
+            i++;
+        }
+        return words.stream().collect(Collectors.joining(" "));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/TestHelper.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2014, 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.internal.net.http.hpack;
+
+import org.testng.annotations.Test;
+
+import java.util.Objects;
+import java.util.Random;
+
+public final class TestHelper {
+
+    public static Random newRandom() {
+        long seed = Long.getLong("jdk.test.lib.random.seed", System.currentTimeMillis());
+        System.out.println("new java.util.Random(" + seed + ")");
+        return new Random(seed);
+    }
+
+    public static <T extends Throwable> T assertVoidThrows(Class<T> clazz, Block<?> code) {
+        return assertThrows(clazz, () -> {
+            code.run();
+            return null;
+        });
+    }
+
+    public static <T extends Throwable> T assertThrows(Class<T> clazz, ReturningBlock<?> code) {
+        Objects.requireNonNull(clazz, "clazz == null");
+        Objects.requireNonNull(code, "code == null");
+        try {
+            code.run();
+        } catch (Throwable t) {
+            if (clazz.isInstance(t)) {
+                return clazz.cast(t);
+            }
+            throw new AssertionError("Expected to catch exception of type "
+                    + clazz.getCanonicalName() + ", instead caught "
+                    + t.getClass().getCanonicalName(), t);
+
+        }
+        throw new AssertionError(
+                "Expected to catch exception of type " + clazz.getCanonicalName()
+                        + ", but caught nothing");
+    }
+
+    public static <T> T assertDoesNotThrow(ReturningBlock<T> code) {
+        Objects.requireNonNull(code, "code == null");
+        try {
+            return code.run();
+        } catch (Throwable t) {
+            throw new AssertionError(
+                    "Expected code block to exit normally, instead " +
+                            "caught " + t.getClass().getCanonicalName(), t);
+        }
+    }
+
+    public static void assertVoidDoesNotThrow(Block<?> code) {
+        Objects.requireNonNull(code, "code == null");
+        try {
+            code.run();
+        } catch (Throwable t) {
+            throw new AssertionError(
+                    "Expected code block to exit normally, instead " +
+                            "caught " + t.getClass().getCanonicalName(), t);
+        }
+    }
+
+
+    public static void assertExceptionMessageContains(Throwable t,
+                                                      CharSequence firstSubsequence,
+                                                      CharSequence... others) {
+        assertCharSequenceContains(t.getMessage(), firstSubsequence, others);
+    }
+
+    public static void assertCharSequenceContains(CharSequence s,
+                                                  CharSequence firstSubsequence,
+                                                  CharSequence... others) {
+        if (s == null) {
+            throw new NullPointerException("Exception message is null");
+        }
+        String str = s.toString();
+        String missing = null;
+        if (!str.contains(firstSubsequence.toString())) {
+            missing = firstSubsequence.toString();
+        } else {
+            for (CharSequence o : others) {
+                if (!str.contains(o.toString())) {
+                    missing = o.toString();
+                    break;
+                }
+            }
+        }
+        if (missing != null) {
+            throw new AssertionError("CharSequence '" + s + "'" + " does not "
+                    + "contain subsequence '" + missing + "'");
+        }
+    }
+
+    public interface ReturningBlock<T> {
+        T run() throws Throwable;
+    }
+
+    public interface Block<T> {
+        void run() throws Throwable;
+    }
+
+    // tests
+
+    @Test
+    public void assertThrows() {
+        assertThrows(NullPointerException.class, () -> ((Object) null).toString());
+    }
+
+    @Test
+    public void assertThrowsWrongType() {
+        try {
+            assertThrows(IllegalArgumentException.class, () -> ((Object) null).toString());
+        } catch (AssertionError e) {
+            Throwable cause = e.getCause();
+            String message = e.getMessage();
+            if (cause != null
+                    && cause instanceof NullPointerException
+                    && message != null
+                    && message.contains("instead caught")) {
+                return;
+            }
+        }
+        throw new AssertionError();
+    }
+
+    @Test
+    public void assertThrowsNoneCaught() {
+        try {
+            assertThrows(IllegalArgumentException.class, () -> null);
+        } catch (AssertionError e) {
+            Throwable cause = e.getCause();
+            String message = e.getMessage();
+            if (cause == null
+                    && message != null
+                    && message.contains("but caught nothing")) {
+                return;
+            }
+        }
+        throw new AssertionError();
+    }
+}
--- a/test/jdk/java/net/httpclient/http2/server/BodyInputStream.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/BodyInputStream.java	Wed Feb 07 21:45:37 2018 +0000
@@ -25,10 +25,10 @@
 import java.nio.ByteBuffer;
 import java.util.List;
 
-import java.net.http.internal.common.Utils;
-import java.net.http.internal.frame.DataFrame;
-import java.net.http.internal.frame.Http2Frame;
-import java.net.http.internal.frame.ResetFrame;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.frame.DataFrame;
+import jdk.internal.net.http.frame.Http2Frame;
+import jdk.internal.net.http.frame.ResetFrame;
 
 /**
  * InputStream reads frames off stream q and supplies read demand from any
--- a/test/jdk/java/net/httpclient/http2/server/BodyOutputStream.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/BodyOutputStream.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,7 +24,7 @@
 import java.io.*;
 import java.nio.ByteBuffer;
 
-import java.net.http.internal.frame.DataFrame;
+import jdk.internal.net.http.frame.DataFrame;
 
 /**
  * OutputStream. Incoming window updates handled by the main connection
--- a/test/jdk/java/net/httpclient/http2/server/EchoHandler.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/EchoHandler.java	Wed Feb 07 21:45:37 2018 +0000
@@ -22,7 +22,7 @@
  */
 
 import java.io.*;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public class EchoHandler implements Http2Handler {
     public EchoHandler() {}
--- a/test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java	Wed Feb 07 21:45:37 2018 +0000
@@ -22,7 +22,7 @@
  */
 
 import java.io.*;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public class Http2EchoHandler implements Http2Handler {
     public Http2EchoHandler() {}
--- a/test/jdk/java/net/httpclient/http2/server/Http2RedirectHandler.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2RedirectHandler.java	Wed Feb 07 21:45:37 2018 +0000
@@ -25,7 +25,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.function.Supplier;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public class Http2RedirectHandler implements Http2Handler {
 
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java	Wed Feb 07 21:45:37 2018 +0000
@@ -29,7 +29,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 import javax.net.ssl.SSLSession;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public interface Http2TestExchange {
 
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java	Wed Feb 07 21:45:37 2018 +0000
@@ -29,9 +29,9 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 import javax.net.ssl.SSLSession;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.frame.HeaderFrame;
-import java.net.http.internal.frame.HeadersFrame;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.frame.HeaderFrame;
+import jdk.internal.net.http.frame.HeadersFrame;
 
 public class Http2TestExchangeImpl implements Http2TestExchange {
 
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,7 +24,7 @@
 import javax.net.ssl.SSLSession;
 import java.io.InputStream;
 import java.net.URI;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 /**
  * A supplier of Http2TestExchanges. If the default Http2TestExchange impl is
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java	Wed Feb 07 21:45:37 2018 +0000
@@ -35,7 +35,7 @@
 import javax.net.ssl.SSLServerSocket;
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SNIServerName;
-import java.net.http.internal.frame.ErrorFrame;
+import jdk.internal.net.http.frame.ErrorFrame;
 
 /**
  * Waits for incoming TCP connections from a client and establishes
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java	Wed Feb 07 21:45:37 2018 +0000
@@ -40,14 +40,14 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.function.Consumer;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.frame.*;
-import java.net.http.internal.hpack.Decoder;
-import java.net.http.internal.hpack.DecodingCallback;
-import java.net.http.internal.hpack.Encoder;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.frame.*;
+import jdk.internal.net.http.hpack.Decoder;
+import jdk.internal.net.http.hpack.DecodingCallback;
+import jdk.internal.net.http.hpack.Encoder;
 import sun.net.www.http.ChunkedInputStream;
 import sun.net.www.http.HttpClient;
-import static java.net.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE;
+import static jdk.internal.net.http.frame.SettingsFrame.HEADER_TABLE_SIZE;
 
 /**
  * Represents one HTTP2 connection, either plaintext upgraded from HTTP/1.1
--- a/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java	Wed Feb 07 21:45:37 2018 +0000
@@ -23,8 +23,8 @@
 
 import java.io.*;
 import java.net.*;
-import java.net.http.internal.common.HttpHeadersImpl;
-import java.net.http.internal.frame.Http2Frame;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.frame.Http2Frame;
 
 // will be converted to a PushPromiseFrame in the writeLoop
 // a thread is then created to produce the DataFrames from the InputStream
--- a/test/jdk/java/net/httpclient/http2/server/PushHandler.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/PushHandler.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,7 +24,7 @@
 import java.io.*;
 import java.net.*;
 import java.nio.file.*;
-import java.net.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public class PushHandler implements Http2Handler {
 
--- a/test/jdk/java/net/httpclient/websocket/BuildingWebSocketDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/websocket/BuildingWebSocketDriver.java	Wed Feb 07 21:45:37 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,9 @@
 /*
  * @test
  * @bug 8159053
- * @modules java.net.http/java.net.http.internal.websocket:open
- * @run testng/othervm --add-reads java.net.http=ALL-UNNAMED java.net.http/java.net.http.internal.websocket.BuildingWebSocketTest
+ * @modules java.net.http/jdk.internal.net.http.websocket:open
+ * @run testng/othervm
+ *      --add-reads java.net.http=ALL-UNNAMED
+ *      java.net.http/jdk.internal.net.http.websocket.BuildingWebSocketTest
  */
 public final class BuildingWebSocketDriver { }
--- a/test/jdk/java/net/httpclient/websocket/HeaderWriterDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/websocket/HeaderWriterDriver.java	Wed Feb 07 21:45:37 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,9 @@
 /*
  * @test
  * @bug 8159053
- * @modules java.net.http/java.net.http.internal.websocket:open
- * @run testng/othervm --add-reads java.net.http=ALL-UNNAMED java.net.http/java.net.http.internal.websocket.HeaderWriterTest
+ * @modules java.net.http/jdk.internal.net.http.websocket:open
+ * @run testng/othervm
+ *      --add-reads java.net.http=ALL-UNNAMED
+ *      java.net.http/jdk.internal.net.http.websocket.HeaderWriterTest
  */
 public final class HeaderWriterDriver { }
--- a/test/jdk/java/net/httpclient/websocket/MaskerDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/websocket/MaskerDriver.java	Wed Feb 07 21:45:37 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,9 @@
 /*
  * @test
  * @bug 8159053
- * @modules java.net.http/java.net.http.internal.websocket:open
- * @run testng/othervm --add-reads java.net.http=ALL-UNNAMED java.net.http/java.net.http.internal.websocket.MaskerTest
+ * @modules java.net.http/jdk.internal.net.http.websocket:open
+ * @run testng/othervm
+ *      --add-reads java.net.http=ALL-UNNAMED
+ *      java.net.http/jdk.internal.net.http.websocket.MaskerTest
  */
 public final class MaskerDriver { }
--- a/test/jdk/java/net/httpclient/websocket/ReaderDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/websocket/ReaderDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8159053
- * @modules java.net.http/java.net.http.internal.websocket:open
- * @run testng/othervm --add-reads java.net.http=ALL-UNNAMED java.net.http/java.net.http.internal.websocket.ReaderTest
+ * @modules java.net.http/jdk.internal.net.http.websocket:open
+ * @run testng/othervm --add-reads java.net.http=ALL-UNNAMED java.net.http/jdk.internal.net.http.websocket.ReaderTest
  */
 public final class ReaderDriver { }
--- a/test/jdk/java/net/httpclient/websocket/WebSocketImplDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/websocket/WebSocketImplDriver.java	Wed Feb 07 21:45:37 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
@@ -23,7 +23,9 @@
 
 /*
  * @test
- * @modules java.net.http/java.net.http.internal.websocket:open
- * @run testng/othervm --add-reads java.net.http=ALL-UNNAMED java.net.http/java.net.http.internal.websocket.WebSocketImplTest
+ * @modules java.net.http/jdk.internal.net.http.websocket:open
+ * @run testng/othervm
+ *      --add-reads java.net.http=ALL-UNNAMED
+ *      java.net.http/jdk.internal.net.http.websocket.WebSocketImplTest
  */
 public class WebSocketImplDriver { }
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/BuildingWebSocketTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,225 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.internal.websocket;
-
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.WebSocket;
-import java.time.Duration;
-import java.util.List;
-import java.util.concurrent.CompletionStage;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.net.http.internal.websocket.TestSupport.assertCompletesExceptionally;
-import static java.net.http.internal.websocket.TestSupport.assertThrows;
-
-/*
- * In some places in this class a new String is created out of a string literal.
- * The idea is to make sure the code under test relies on something better than
- * the reference equality ( == ) for string equality checks.
- */
-public class BuildingWebSocketTest {
-
-    private final static URI VALID_URI = URI.create("ws://websocket.example.com");
-
-    @Test
-    public void nullArguments() {
-        HttpClient c = HttpClient.newHttpClient();
-
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .buildAsync(null, listener()));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .buildAsync(VALID_URI, null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .buildAsync(null, null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .header(null, "value"));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .header("name", null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .header(null, null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .subprotocols(null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .subprotocols(null, "sub2.example.com"));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .subprotocols("sub1.example.com", (String) null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .subprotocols("sub1.example.com", (String[]) null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .subprotocols("sub1.example.com", "sub2.example.com", null));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                             .subprotocols("sub1.example.com", null, "sub3.example.com"));
-        assertThrows(NullPointerException.class,
-                     () -> c.newWebSocketBuilder()
-                            .connectTimeout(null));
-    }
-
-    @Test(dataProvider = "badURIs")
-    void illegalURI(URI uri) {
-        WebSocket.Builder b = HttpClient.newHttpClient().newWebSocketBuilder();
-        assertCompletesExceptionally(IllegalArgumentException.class,
-                                     b.buildAsync(uri, listener()));
-    }
-
-    @Test
-    public void illegalHeaders() {
-        List<String> headers =
-                List.of("Sec-WebSocket-Accept",
-                        "Sec-WebSocket-Extensions",
-                        "Sec-WebSocket-Key",
-                        "Sec-WebSocket-Protocol",
-                        "Sec-WebSocket-Version")
-                        .stream()
-                        .flatMap(s -> Stream.of(s, new String(s))) // a string and a copy of it
-                        .collect(Collectors.toList());
-
-        Function<String, CompletionStage<?>> f =
-                header -> HttpClient.newHttpClient()
-                        .newWebSocketBuilder()
-                        .header(header, "value")
-                        .buildAsync(VALID_URI, listener());
-
-        headers.forEach(h -> assertCompletesExceptionally(IllegalArgumentException.class, f.apply(h)));
-    }
-
-    // TODO: test for bad syntax headers
-    // TODO: test for overwrites (subprotocols) and additions (headers)
-
-    @Test(dataProvider = "badSubprotocols")
-    public void illegalSubprotocolsSyntax(String s) {
-        WebSocket.Builder b = HttpClient.newHttpClient()
-                .newWebSocketBuilder()
-                .subprotocols(s);
-        assertCompletesExceptionally(IllegalArgumentException.class,
-                                     b.buildAsync(VALID_URI, listener()));
-    }
-
-    @Test(dataProvider = "duplicatingSubprotocols")
-    public void illegalSubprotocolsDuplicates(String mostPreferred,
-                                              String[] lesserPreferred) {
-        WebSocket.Builder b = HttpClient.newHttpClient()
-                .newWebSocketBuilder()
-                .subprotocols(mostPreferred, lesserPreferred);
-        assertCompletesExceptionally(IllegalArgumentException.class,
-                                     b.buildAsync(VALID_URI, listener()));
-    }
-
-    @Test(dataProvider = "badConnectTimeouts")
-    public void illegalConnectTimeout(Duration d) {
-        WebSocket.Builder b = HttpClient.newHttpClient()
-                .newWebSocketBuilder()
-                .connectTimeout(d);
-        assertCompletesExceptionally(IllegalArgumentException.class,
-                                     b.buildAsync(VALID_URI, listener()));
-    }
-
-    @DataProvider
-    public Object[][] badURIs() {
-        return new Object[][]{
-                {URI.create("http://example.com")},
-                {URI.create("ftp://example.com")},
-                {URI.create("wss://websocket.example.com/hello#fragment")},
-                {URI.create("ws://websocket.example.com/hello#fragment")},
-        };
-    }
-
-    @DataProvider
-    public Object[][] badConnectTimeouts() {
-        return new Object[][]{
-                {Duration.ofDays   ( 0)},
-                {Duration.ofDays   (-1)},
-                {Duration.ofHours  ( 0)},
-                {Duration.ofHours  (-1)},
-                {Duration.ofMinutes( 0)},
-                {Duration.ofMinutes(-1)},
-                {Duration.ofSeconds( 0)},
-                {Duration.ofSeconds(-1)},
-                {Duration.ofMillis ( 0)},
-                {Duration.ofMillis (-1)},
-                {Duration.ofNanos  ( 0)},
-                {Duration.ofNanos  (-1)},
-                {Duration.ZERO},
-        };
-    }
-
-    // https://tools.ietf.org/html/rfc7230#section-3.2.6
-    // https://tools.ietf.org/html/rfc20
-    @DataProvider
-    public static Object[][] badSubprotocols() {
-        return new Object[][]{
-                {""},
-                {new String("")},
-                {"round-brackets("},
-                {"round-brackets)"},
-                {"comma,"},
-                {"slash/"},
-                {"colon:"},
-                {"semicolon;"},
-                {"angle-brackets<"},
-                {"angle-brackets>"},
-                {"equals="},
-                {"question-mark?"},
-                {"at@"},
-                {"brackets["},
-                {"backslash\\"},
-                {"brackets]"},
-                {"curly-brackets{"},
-                {"curly-brackets}"},
-                {"space "},
-                {"non-printable-character " + Character.toString((char) 31)},
-                {"non-printable-character " + Character.toString((char) 127)},
-        };
-    }
-
-    @DataProvider
-    public static Object[][] duplicatingSubprotocols() {
-        return new Object[][]{
-                {"a.b.c", new String[]{"a.b.c"}},
-                {"a.b.c", new String[]{"x.y.z", "p.q.r", "x.y.z"}},
-                {"a.b.c", new String[]{new String("a.b.c")}},
-        };
-    }
-
-    private static WebSocket.Listener listener() {
-        return new WebSocket.Listener() { };
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/HeaderWriterTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.internal.websocket;
-
-import org.testng.annotations.Test;
-import java.net.http.internal.websocket.Frame.HeaderWriter;
-import java.net.http.internal.websocket.Frame.Opcode;
-
-import java.nio.ByteBuffer;
-import java.util.OptionalInt;
-
-import static java.util.OptionalInt.empty;
-import static java.util.OptionalInt.of;
-import static org.testng.Assert.assertEquals;
-import static java.net.http.internal.websocket.TestSupport.assertThrows;
-import static java.net.http.internal.websocket.TestSupport.forEachPermutation;
-
-public class HeaderWriterTest {
-
-    private long cases, frames;
-
-    @Test
-    public void negativePayload() {
-        System.out.println("testing negative payload");
-        HeaderWriter w = new HeaderWriter();
-        assertThrows(IllegalArgumentException.class,
-                ".*(?i)negative.*",
-                () -> w.payloadLen(-1));
-    }
-
-    @Test
-    public void test() {
-        System.out.println("testing regular payloads");
-        final long[] payloads = {0, 126, 65536, Integer.MAX_VALUE + 1L};
-        final OptionalInt[] masks = {empty(), of(-1), of(0), of(0xCAFEBABE),
-                of(Integer.MAX_VALUE), of(Integer.MIN_VALUE)};
-        for (boolean fin : new boolean[]{true, false}) {
-            for (boolean rsv1 : new boolean[]{true, false}) {
-                for (boolean rsv2 : new boolean[]{true, false}) {
-                    for (boolean rsv3 : new boolean[]{true, false}) {
-                        for (Opcode opcode : Opcode.values()) {
-                            for (long payloadLen : payloads) {
-                                for (OptionalInt mask : masks) {
-                                    verify(fin, rsv1, rsv2, rsv3, opcode, payloadLen, mask);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        System.out.println("Frames: " + frames + ", Total cases: " + cases);
-    }
-
-    private void verify(boolean fin,
-                        boolean rsv1,
-                        boolean rsv2,
-                        boolean rsv3,
-                        Opcode opcode,
-                        long payloadLen,
-                        OptionalInt mask) {
-        frames++;
-        HeaderWriter writer = new HeaderWriter();
-        ByteBuffer expected = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
-        writer.fin(fin).rsv1(rsv1).rsv2(rsv2).rsv3(rsv3).opcode(opcode).payloadLen(payloadLen);
-        mask.ifPresentOrElse(writer::mask, writer::noMask);
-        writer.write(expected);
-        expected.flip();
-        verifyPermutations(expected, writer,
-                () -> writer.fin(fin),
-                () -> writer.rsv1(rsv1),
-                () -> writer.rsv2(rsv2),
-                () -> writer.rsv3(rsv3),
-                () -> writer.opcode(opcode),
-                () -> writer.payloadLen(payloadLen),
-                () -> mask.ifPresentOrElse(writer::mask, writer::noMask));
-    }
-
-    private void verifyPermutations(ByteBuffer expected,
-                                    HeaderWriter writer,
-                                    Runnable... actions) {
-        forEachPermutation(actions.length,
-                order -> {
-                    cases++;
-                    for (int i : order) {
-                        actions[i].run();
-                    }
-                    ByteBuffer actual = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES + 2);
-                    writer.write(actual);
-                    actual.flip();
-                    assertEquals(actual, expected);
-                });
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/MaskerTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.internal.websocket;
-
-import org.testng.annotations.Test;
-
-import java.nio.ByteBuffer;
-import java.security.SecureRandom;
-import java.util.stream.IntStream;
-
-import static org.testng.Assert.assertEquals;
-import static java.net.http.internal.websocket.Frame.Masker.transferMasking;
-import static java.net.http.internal.websocket.TestSupport.forEachBufferPartition;
-import static java.net.http.internal.websocket.TestSupport.fullCopy;
-
-public class MaskerTest {
-
-    private static final SecureRandom random = new SecureRandom();
-
-    @Test
-    public void stateless() {
-        IntStream.iterate(0, i -> i + 1).limit(125).boxed()
-                .forEach(r -> {
-                    int m = random.nextInt();
-                    ByteBuffer src = createSourceBuffer(r);
-                    ByteBuffer dst = createDestinationBuffer(r);
-                    verify(src, dst, maskArray(m), 0,
-                            () -> transferMasking(src, dst, m));
-                });
-    }
-
-    /*
-     * Stateful masker to make sure setting a mask resets the state as if a new
-     * Masker instance is created each time
-     */
-    private final Frame.Masker masker = new Frame.Masker();
-
-    @Test
-    public void stateful0() {
-        // This size (17 = 8 + 8 + 1) should test all the stages
-        // (galloping/slow) of masking good enough
-        int N = 17;
-        ByteBuffer src = createSourceBuffer(N);
-        ByteBuffer dst = createDestinationBuffer(N);
-        int mask = random.nextInt();
-        forEachBufferPartition(src,
-                buffers -> {
-                    int offset = 0;
-                    masker.mask(mask);
-                    int[] maskBytes = maskArray(mask);
-                    for (ByteBuffer s : buffers) {
-                        offset = verify(s, dst, maskBytes, offset,
-                                () -> masker.transferMasking(s, dst));
-                    }
-                });
-    }
-
-    @Test
-    public void stateful1() {
-        int m = random.nextInt();
-        masker.mask(m);
-        ByteBuffer src = ByteBuffer.allocate(0);
-        ByteBuffer dst = ByteBuffer.allocate(16);
-        verify(src, dst, maskArray(m), 0,
-                () -> masker.transferMasking(src, dst));
-    }
-
-    private static int verify(ByteBuffer src,
-                              ByteBuffer dst,
-                              int[] maskBytes,
-                              int offset,
-                              Runnable masking) {
-        ByteBuffer srcCopy = fullCopy(src);
-        ByteBuffer dstCopy = fullCopy(dst);
-        masking.run();
-        int srcRemaining = srcCopy.remaining();
-        int dstRemaining = dstCopy.remaining();
-        int masked = Math.min(srcRemaining, dstRemaining);
-        // 1. position check
-        assertEquals(src.position(), srcCopy.position() + masked);
-        assertEquals(dst.position(), dstCopy.position() + masked);
-        // 2. masking check
-        src.position(srcCopy.position());
-        dst.position(dstCopy.position());
-        for (; src.hasRemaining() && dst.hasRemaining();
-             offset = (offset + 1) & 3) {
-            assertEquals(dst.get(), src.get() ^ maskBytes[offset]);
-        }
-        // 3. corruption check
-        // 3.1 src contents haven't changed
-        int srcPosition = src.position();
-        int srcLimit = src.limit();
-        src.clear();
-        srcCopy.clear();
-        assertEquals(src, srcCopy);
-        src.limit(srcLimit).position(srcPosition); // restore src
-        // 3.2 dst leading and trailing regions' contents haven't changed
-        int dstPosition = dst.position();
-        int dstInitialPosition = dstCopy.position();
-        int dstLimit = dst.limit();
-        // leading
-        dst.position(0).limit(dstInitialPosition);
-        dstCopy.position(0).limit(dstInitialPosition);
-        assertEquals(dst, dstCopy);
-        // trailing
-        dst.limit(dst.capacity()).position(dstLimit);
-        dstCopy.limit(dst.capacity()).position(dstLimit);
-        assertEquals(dst, dstCopy);
-        // restore dst
-        dst.position(dstPosition).limit(dstLimit);
-        return offset;
-    }
-
-    private static ByteBuffer createSourceBuffer(int remaining) {
-        int leading = random.nextInt(4);
-        int trailing = random.nextInt(4);
-        byte[] bytes = new byte[leading + remaining + trailing];
-        random.nextBytes(bytes);
-        return ByteBuffer.wrap(bytes).position(leading).limit(leading + remaining);
-    }
-
-    private static ByteBuffer createDestinationBuffer(int remaining) {
-        int leading = random.nextInt(4);
-        int trailing = random.nextInt(4);
-        return ByteBuffer.allocate(leading + remaining + trailing)
-                .position(leading).limit(leading + remaining);
-    }
-
-    private static int[] maskArray(int mask) {
-        return new int[]{
-                (byte) (mask >>> 24),
-                (byte) (mask >>> 16),
-                (byte) (mask >>>  8),
-                (byte) (mask >>>  0)
-        };
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/MockListener.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,402 +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 java.net.http.internal.websocket;
-
-import java.net.http.WebSocket;
-import java.net.http.WebSocket.MessagePart;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-
-import static java.net.http.internal.websocket.TestSupport.fullCopy;
-
-public class MockListener implements WebSocket.Listener {
-
-    private final long bufferSize;
-    private long count;
-    private final List<Invocation> invocations = new ArrayList<>();
-    private final CompletableFuture<?> lastCall = new CompletableFuture<>();
-
-    /*
-     * Typical buffer sizes: 1, n, Long.MAX_VALUE
-     */
-    public MockListener(long bufferSize) {
-        if (bufferSize < 1) {
-            throw new IllegalArgumentException();
-        }
-        this.bufferSize = bufferSize;
-    }
-
-    @Override
-    public void onOpen(WebSocket webSocket) {
-        System.out.printf("onOpen(%s)%n", webSocket);
-        invocations.add(new OnOpen(webSocket));
-        onOpen0(webSocket);
-    }
-
-    protected void onOpen0(WebSocket webSocket) {
-        replenish(webSocket);
-    }
-
-    @Override
-    public CompletionStage<?> onText(WebSocket webSocket,
-                                     CharSequence message,
-                                     MessagePart part) {
-        System.out.printf("onText(%s, %s, %s)%n", webSocket, message, part);
-        invocations.add(new OnText(webSocket, message.toString(), part));
-        return onText0(webSocket, message, part);
-    }
-
-    protected CompletionStage<?> onText0(WebSocket webSocket,
-                                         CharSequence message,
-                                         MessagePart part) {
-        replenish(webSocket);
-        return null;
-    }
-
-    @Override
-    public CompletionStage<?> onBinary(WebSocket webSocket,
-                                       ByteBuffer message,
-                                       MessagePart part) {
-        System.out.printf("onBinary(%s, %s, %s)%n", webSocket, message, part);
-        invocations.add(new OnBinary(webSocket, fullCopy(message), part));
-        return onBinary0(webSocket, message, part);
-    }
-
-    protected CompletionStage<?> onBinary0(WebSocket webSocket,
-                                           ByteBuffer message,
-                                           MessagePart part) {
-        replenish(webSocket);
-        return null;
-    }
-
-    @Override
-    public CompletionStage<?> onPing(WebSocket webSocket, ByteBuffer message) {
-        System.out.printf("onPing(%s, %s)%n", webSocket, message);
-        invocations.add(new OnPing(webSocket, fullCopy(message)));
-        return onPing0(webSocket, message);
-    }
-
-    protected CompletionStage<?> onPing0(WebSocket webSocket, ByteBuffer message) {
-        replenish(webSocket);
-        return null;
-    }
-
-    @Override
-    public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
-        System.out.printf("onPong(%s, %s)%n", webSocket, message);
-        invocations.add(new OnPong(webSocket, fullCopy(message)));
-        return onPong0(webSocket, message);
-    }
-
-    protected CompletionStage<?> onPong0(WebSocket webSocket, ByteBuffer message) {
-        replenish(webSocket);
-        return null;
-    }
-
-    @Override
-    public CompletionStage<?> onClose(WebSocket webSocket,
-                                      int statusCode,
-                                      String reason) {
-        System.out.printf("onClose(%s, %s, %s)%n", webSocket, statusCode, reason);
-        invocations.add(new OnClose(webSocket, statusCode, reason));
-        lastCall.complete(null);
-        return null;
-    }
-
-    @Override
-    public void onError(WebSocket webSocket, Throwable error) {
-        System.out.printf("onError(%s, %s)%n", webSocket, error);
-        invocations.add(new OnError(webSocket, error == null ? null : error.getClass()));
-        lastCall.complete(null);
-    }
-
-    public CompletableFuture<?> onCloseOrOnErrorCalled() {
-        return lastCall.copy();
-    }
-
-    protected void replenish(WebSocket webSocket) {
-        if (--count <= 0) {
-            count = bufferSize - bufferSize / 2;
-        }
-        webSocket.request(count);
-    }
-
-    public List<Invocation> invocations() {
-        return new ArrayList<>(invocations);
-    }
-
-    public abstract static class Invocation {
-
-        public static OnOpen onOpen(WebSocket webSocket) {
-            return new OnOpen(webSocket);
-        }
-
-        public static OnText onText(WebSocket webSocket,
-                                    String text,
-                                    MessagePart part) {
-            return new OnText(webSocket, text, part);
-        }
-
-        public static OnBinary onBinary(WebSocket webSocket,
-                                        ByteBuffer data,
-                                        MessagePart part) {
-            return new OnBinary(webSocket, data, part);
-        }
-
-        public static OnPing onPing(WebSocket webSocket,
-                                    ByteBuffer data) {
-            return new OnPing(webSocket, data);
-        }
-
-        public static OnPong onPong(WebSocket webSocket,
-                                    ByteBuffer data) {
-            return new OnPong(webSocket, data);
-        }
-
-        public static OnClose onClose(WebSocket webSocket,
-                                      int statusCode,
-                                      String reason) {
-            return new OnClose(webSocket, statusCode, reason);
-        }
-
-        public static OnError onError(WebSocket webSocket,
-                                      Class<? extends Throwable> clazz) {
-            return new OnError(webSocket, clazz);
-        }
-
-        final WebSocket webSocket;
-
-        private Invocation(WebSocket webSocket) {
-            this.webSocket = webSocket;
-        }
-    }
-
-    public static final class OnOpen extends Invocation {
-
-        public OnOpen(WebSocket webSocket) {
-            super(webSocket);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Invocation that = (Invocation) o;
-            return Objects.equals(webSocket, that.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(webSocket);
-        }
-    }
-
-    public static final class OnText extends Invocation {
-
-        final String text;
-        final MessagePart part;
-
-        public OnText(WebSocket webSocket, String text, MessagePart part) {
-            super(webSocket);
-            this.text = text;
-            this.part = part;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OnText onText = (OnText) o;
-            return Objects.equals(text, onText.text) &&
-                    part == onText.part &&
-                    Objects.equals(webSocket, onText.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(text, part, webSocket);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("onText(%s, %s, %s)", webSocket, text, part);
-        }
-    }
-
-    public static final class OnBinary extends Invocation {
-
-        final ByteBuffer data;
-        final MessagePart part;
-
-        public OnBinary(WebSocket webSocket, ByteBuffer data, MessagePart part) {
-            super(webSocket);
-            this.data = data;
-            this.part = part;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OnBinary onBinary = (OnBinary) o;
-            return Objects.equals(data, onBinary.data) &&
-                    part == onBinary.part &&
-                    Objects.equals(webSocket, onBinary.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(data, part, webSocket);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("onBinary(%s, %s, %s)", webSocket, data, part);
-        }
-    }
-
-    public static final class OnPing extends Invocation {
-
-        final ByteBuffer data;
-
-        public OnPing(WebSocket webSocket, ByteBuffer data) {
-            super(webSocket);
-            this.data = data;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OnPing onPing = (OnPing) o;
-            return Objects.equals(data, onPing.data) &&
-                    Objects.equals(webSocket, onPing.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(data, webSocket);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("onPing(%s, %s)", webSocket, data);
-        }
-    }
-
-    public static final class OnPong extends Invocation {
-
-        final ByteBuffer data;
-
-        public OnPong(WebSocket webSocket, ByteBuffer data) {
-            super(webSocket);
-            this.data = data;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OnPong onPong = (OnPong) o;
-            return Objects.equals(data, onPong.data) &&
-                    Objects.equals(webSocket, onPong.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(data, webSocket);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("onPong(%s, %s)", webSocket, data);
-        }
-    }
-
-    public static final class OnClose extends Invocation {
-
-        final int statusCode;
-        final String reason;
-
-        public OnClose(WebSocket webSocket, int statusCode, String reason) {
-            super(webSocket);
-            this.statusCode = statusCode;
-            this.reason = reason;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OnClose onClose = (OnClose) o;
-            return statusCode == onClose.statusCode &&
-                    Objects.equals(reason, onClose.reason) &&
-                    Objects.equals(webSocket, onClose.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(statusCode, reason, webSocket);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("onClose(%s, %s, %s)", webSocket, statusCode, reason);
-        }
-    }
-
-    public static final class OnError extends Invocation {
-
-        final Class<? extends Throwable> clazz;
-
-        public OnError(WebSocket webSocket, Class<? extends Throwable> clazz) {
-            super(webSocket);
-            this.clazz = clazz;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OnError onError = (OnError) o;
-            return Objects.equals(clazz, onError.clazz) &&
-                    Objects.equals(webSocket, onError.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(clazz, webSocket);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("onError(%s, %s)", webSocket, clazz);
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/MockTransport.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,436 +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 java.net.http.internal.websocket;
-
-import java.net.http.WebSocket.MessagePart;
-import java.net.http.internal.common.Demand;
-import java.net.http.internal.common.SequentialScheduler;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Queue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-import static java.net.http.internal.websocket.TestSupport.fullCopy;
-
-public class MockTransport<T> implements Transport<T> {
-
-    private final long startTime = System.currentTimeMillis();
-    private final Queue<Invocation> output = new ConcurrentLinkedQueue<>();
-    private final Queue<CompletableFuture<Consumer<MessageStreamConsumer>>>
-            input = new ConcurrentLinkedQueue<>();
-    private final Supplier<T> supplier;
-    private final MessageStreamConsumer consumer;
-    private final SequentialScheduler scheduler
-            = new SequentialScheduler(new ReceiveTask());
-    private final Demand demand = new Demand();
-
-    public MockTransport(Supplier<T> sendResultSupplier,
-                         MessageStreamConsumer consumer) {
-        this.supplier = sendResultSupplier;
-        this.consumer = consumer;
-        input.addAll(receive());
-    }
-
-    @Override
-    public final CompletableFuture<T> sendText(CharSequence message,
-                                               boolean isLast) {
-        output.add(Invocation.sendText(message, isLast));
-        return send(String.format("sendText(%s, %s)", message, isLast),
-                    () -> sendText0(message, isLast));
-    }
-
-    protected CompletableFuture<T> sendText0(CharSequence message,
-                                             boolean isLast) {
-        return defaultSend();
-    }
-
-    protected CompletableFuture<T> defaultSend() {
-        return CompletableFuture.completedFuture(result());
-    }
-
-    @Override
-    public final CompletableFuture<T> sendBinary(ByteBuffer message,
-                                                 boolean isLast) {
-        output.add(Invocation.sendBinary(message, isLast));
-        return send(String.format("sendBinary(%s, %s)", message, isLast),
-                    () -> sendBinary0(message, isLast));
-    }
-
-    protected CompletableFuture<T> sendBinary0(ByteBuffer message,
-                                               boolean isLast) {
-        return defaultSend();
-    }
-
-    @Override
-    public final CompletableFuture<T> sendPing(ByteBuffer message) {
-        output.add(Invocation.sendPing(message));
-        return send(String.format("sendPing(%s)", message),
-                    () -> sendPing0(message));
-    }
-
-    protected CompletableFuture<T> sendPing0(ByteBuffer message) {
-        return defaultSend();
-    }
-
-    @Override
-    public final CompletableFuture<T> sendPong(ByteBuffer message) {
-        output.add(Invocation.sendPong(message));
-        return send(String.format("sendPong(%s)", message),
-                    () -> sendPong0(message));
-    }
-
-    protected CompletableFuture<T> sendPong0(ByteBuffer message) {
-        return defaultSend();
-    }
-
-    @Override
-    public final CompletableFuture<T> sendClose(int statusCode, String reason) {
-        output.add(Invocation.sendClose(statusCode, reason));
-        return send(String.format("sendClose(%s, %s)", statusCode, reason),
-                    () -> sendClose0(statusCode, reason));
-    }
-
-    protected CompletableFuture<T> sendClose0(int statusCode, String reason) {
-        return defaultSend();
-    }
-
-    protected Collection<CompletableFuture<Consumer<MessageStreamConsumer>>> receive() {
-        return List.of();
-    }
-
-    public static Consumer<MessageStreamConsumer> onText(CharSequence data,
-                                                         MessagePart part) {
-        return c -> c.onText(data.toString(), part);
-    }
-
-    public static Consumer<MessageStreamConsumer> onBinary(ByteBuffer data,
-                                                           MessagePart part) {
-        return c -> c.onBinary(fullCopy(data), part);
-    }
-
-    public static Consumer<MessageStreamConsumer> onPing(ByteBuffer data) {
-        return c -> c.onPing(fullCopy(data));
-    }
-
-    public static Consumer<MessageStreamConsumer> onPong(ByteBuffer data) {
-        return c -> c.onPong(fullCopy(data));
-    }
-
-    public static Consumer<MessageStreamConsumer> onClose(int statusCode,
-                                                          String reason) {
-        return c -> c.onClose(statusCode, reason);
-    }
-
-    public static Consumer<MessageStreamConsumer> onError(Throwable error) {
-        return c -> c.onError(error);
-    }
-
-    public static Consumer<MessageStreamConsumer> onComplete() {
-        return c -> c.onComplete();
-    }
-
-    @Override
-    public void request(long n) {
-        demand.increase(n);
-        scheduler.runOrSchedule();
-    }
-
-    @Override
-    public void acknowledgeReception() {
-        demand.tryDecrement();
-    }
-
-    @Override
-    public final void closeOutput() throws IOException {
-        output.add(Invocation.closeOutput());
-        begin("closeOutput()");
-        closeOutput0();
-        end("closeOutput()");
-    }
-
-    protected void closeOutput0() throws IOException {
-        defaultClose();
-    }
-
-    protected void defaultClose() throws IOException {
-    }
-
-    @Override
-    public final void closeInput() throws IOException {
-        output.add(Invocation.closeInput());
-        begin("closeInput()");
-        closeInput0();
-        end("closeInput()");
-    }
-
-    protected void closeInput0() throws IOException {
-        defaultClose();
-    }
-
-    public abstract static class Invocation {
-
-        static Invocation.SendText sendText(CharSequence message,
-                                            boolean isLast) {
-            return new SendText(message, isLast);
-        }
-
-        static Invocation.SendBinary sendBinary(ByteBuffer message,
-                                                boolean isLast) {
-            return new SendBinary(message, isLast);
-        }
-
-        static Invocation.SendPing sendPing(ByteBuffer message) {
-            return new SendPing(message);
-        }
-
-        static Invocation.SendPong sendPong(ByteBuffer message) {
-            return new SendPong(message);
-        }
-
-        static Invocation.SendClose sendClose(int statusCode, String reason) {
-            return new SendClose(statusCode, reason);
-        }
-
-        public static CloseOutput closeOutput() {
-            return new CloseOutput();
-        }
-
-        public static CloseInput closeInput() {
-            return new CloseInput();
-        }
-
-        public static final class SendText extends Invocation {
-
-            final CharSequence message;
-            final boolean isLast;
-
-            SendText(CharSequence message, boolean isLast) {
-                this.message = message.toString();
-                this.isLast = isLast;
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                if (this == obj) return true;
-                if (obj == null || getClass() != obj.getClass()) return false;
-                SendText sendText = (SendText) obj;
-                return isLast == sendText.isLast &&
-                        Objects.equals(message, sendText.message);
-            }
-
-            @Override
-            public int hashCode() {
-                return Objects.hash(isLast, message);
-            }
-        }
-
-        public static final class SendBinary extends Invocation {
-
-            final ByteBuffer message;
-            final boolean isLast;
-
-            SendBinary(ByteBuffer message, boolean isLast) {
-                this.message = fullCopy(message);
-                this.isLast = isLast;
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                if (this == obj) return true;
-                if (obj == null || getClass() != obj.getClass()) return false;
-                SendBinary that = (SendBinary) obj;
-                return isLast == that.isLast &&
-                        Objects.equals(message, that.message);
-            }
-
-            @Override
-            public int hashCode() {
-                return Objects.hash(message, isLast);
-            }
-        }
-
-        private static final class SendPing extends Invocation {
-
-            final ByteBuffer message;
-
-            SendPing(ByteBuffer message) {
-                this.message = fullCopy(message);
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                if (this == obj) return true;
-                if (obj == null || getClass() != obj.getClass()) return false;
-                SendPing sendPing = (SendPing) obj;
-                return Objects.equals(message, sendPing.message);
-            }
-
-            @Override
-            public int hashCode() {
-                return Objects.hash(message);
-            }
-        }
-
-        private static final class SendPong extends Invocation {
-
-            final ByteBuffer message;
-
-            SendPong(ByteBuffer message) {
-                this.message = fullCopy(message);
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                if (this == obj) return true;
-                if (obj == null || getClass() != obj.getClass()) return false;
-                SendPing sendPing = (SendPing) obj;
-                return Objects.equals(message, sendPing.message);
-            }
-
-            @Override
-            public int hashCode() {
-                return Objects.hash(message);
-            }
-        }
-
-        private static final class SendClose extends Invocation {
-
-            final int statusCode;
-            final String reason;
-
-            SendClose(int statusCode, String reason) {
-                this.statusCode = statusCode;
-                this.reason = reason;
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                if (this == obj) return true;
-                if (obj == null || getClass() != obj.getClass()) return false;
-                SendClose sendClose = (SendClose) obj;
-                return statusCode == sendClose.statusCode &&
-                        Objects.equals(reason, sendClose.reason);
-            }
-
-            @Override
-            public int hashCode() {
-                return Objects.hash(statusCode, reason);
-            }
-        }
-
-        private static final class CloseOutput extends Invocation {
-
-            CloseOutput() { }
-
-            @Override
-            public int hashCode() {
-                return 0;
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return obj instanceof CloseOutput;
-            }
-        }
-
-        private static final class CloseInput extends Invocation {
-
-            CloseInput() { }
-
-            @Override
-            public int hashCode() {
-                return 0;
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return obj instanceof CloseInput;
-            }
-        }
-    }
-
-    public Queue<Invocation> invocations() {
-        return new LinkedList<>(output);
-    }
-
-    protected final T result() {
-        return supplier.get();
-    }
-
-    private CompletableFuture<T> send(String name,
-                                      Supplier<CompletableFuture<T>> supplier) {
-        begin(name);
-        CompletableFuture<T> cf = supplier.get().whenComplete((r, e) -> {
-            System.out.printf("[%6s ms.] complete %s%n", elapsedTime(), name);
-        });
-        end(name);
-        return cf;
-    }
-
-    private void begin(String name) {
-        System.out.printf("[%6s ms.] begin %s%n", elapsedTime(), name);
-    }
-
-    private void end(String name) {
-        System.out.printf("[%6s ms.] end %s%n", elapsedTime(), name);
-    }
-
-    private long elapsedTime() {
-        return System.currentTimeMillis() - startTime;
-    }
-
-    private final class ReceiveTask implements SequentialScheduler.RestartableTask {
-
-        @Override
-        public void run(SequentialScheduler.DeferredCompleter taskCompleter) {
-            if (!scheduler.isStopped() && !demand.isFulfilled() && !input.isEmpty()) {
-                CompletableFuture<Consumer<MessageStreamConsumer>> cf = input.remove();
-                if (cf.isDone()) { // Forcing synchronous execution
-                    cf.join().accept(consumer);
-                    repeat(taskCompleter);
-                } else {
-                    cf.whenCompleteAsync((r, e) -> {
-                        r.accept(consumer);
-                        repeat(taskCompleter);
-                    });
-                }
-            } else {
-                taskCompleter.complete();
-            }
-        }
-
-        private void repeat(SequentialScheduler.DeferredCompleter taskCompleter) {
-            taskCompleter.complete();
-            scheduler.runOrSchedule();
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/ReaderTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,271 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.internal.websocket;
-
-import org.testng.annotations.Test;
-import java.net.http.internal.websocket.Frame.Opcode;
-
-import java.nio.ByteBuffer;
-import java.util.Optional;
-import java.util.OptionalInt;
-import java.util.OptionalLong;
-import java.util.function.IntPredicate;
-import java.util.function.IntUnaryOperator;
-
-import static java.util.OptionalInt.empty;
-import static java.util.OptionalInt.of;
-import static org.testng.Assert.assertEquals;
-import static java.net.http.internal.websocket.TestSupport.assertThrows;
-import static java.net.http.internal.websocket.TestSupport.forEachBufferPartition;
-
-public class ReaderTest {
-
-    private long cases, frames;
-
-    @Test
-    void notMinimalEncoding01() {
-        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
-        h.put((byte) 0b1000_0000).put((byte) 0b0111_1110).putChar((char) 125).flip();
-        assertThrows(FailWebSocketException.class,
-                ".*(?i)minimally-encoded.*",
-                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
-    }
-
-    @Test
-    void notMinimalEncoding02() {
-        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
-        h.put((byte) 0b1000_0000).put((byte) 0b0111_1111).putLong(125).flip();
-        assertThrows(FailWebSocketException.class,
-                ".*(?i)minimally-encoded.*",
-                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
-    }
-
-    @Test
-    void notMinimalEncoding03() {
-        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
-        h.put((byte) 0b1000_0000).put((byte) 0b0111_1111).putLong(65535).flip();
-        assertThrows(FailWebSocketException.class,
-                ".*(?i)minimally-encoded.*",
-                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
-    }
-
-    @Test
-    public void negativePayload() {
-        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
-        h.put((byte) 0b1000_0000).put((byte) 0b0111_1111).putLong(-2L).flip();
-        assertThrows(FailWebSocketException.class,
-                ".*(?i)negative.*",
-                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
-    }
-
-    @Test
-    public void frameStart() {
-        final long[] payloads = {0, 126, 65536, Integer.MAX_VALUE + 1L};
-        final OptionalInt[] masks = {empty(), of(-1), of(0), of(0xCAFEBABE),
-                of(Integer.MAX_VALUE), of(Integer.MIN_VALUE)};
-        for (boolean fin : new boolean[]{true, false}) {
-            for (boolean rsv1 : new boolean[]{true, false}) {
-                for (boolean rsv2 : new boolean[]{true, false}) {
-                    for (boolean rsv3 : new boolean[]{true, false}) {
-                        for (Opcode opcode : Opcode.values()) {
-                            for (long payloadLen : payloads) {
-                                for (OptionalInt mask : masks) {
-                                    verifyFrameStart(fin, rsv1, rsv2, rsv3, opcode, payloadLen, mask);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        System.out.println("Frames: " + frames + ", Total cases: " + cases);
-    }
-
-    /*
-     * Tests whether or not the frame starts properly.
-     * That is, a header and the first invocation of payloadData (if any).
-     */
-    private void verifyFrameStart(boolean fin,
-                                  boolean rsv1,
-                                  boolean rsv2,
-                                  boolean rsv3,
-                                  Opcode opcode,
-                                  long payloadLen,
-                                  OptionalInt mask) {
-        frames++;
-        Frame.HeaderWriter w = new Frame.HeaderWriter();
-        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
-        w.fin(fin).rsv1(rsv1).rsv2(rsv2).rsv3(rsv3).opcode(opcode).payloadLen(payloadLen);
-        mask.ifPresentOrElse(w::mask, w::noMask);
-        w.write(h);
-        h.flip();
-        forEachBufferPartition(h,
-                buffers -> {
-                    cases++;
-                    Frame.Reader r = new Frame.Reader();
-                    MockConsumer c = new MockConsumer();
-                    for (ByteBuffer b : buffers) {
-                        r.readFrame(b, c);
-                    }
-                    assertEquals(fin, c.fin());
-                    assertEquals(rsv1, c.rsv1());
-                    assertEquals(rsv2, c.rsv2());
-                    assertEquals(rsv3, c.rsv3());
-                    assertEquals(opcode, c.opcode());
-                    assertEquals(mask.isPresent(), c.mask());
-                    assertEquals(payloadLen, c.payloadLen());
-                    assertEquals(mask, c.maskingKey());
-                    assertEquals(payloadLen == 0, c.isEndFrame());
-                });
-    }
-
-    /*
-     * Used to verify the order, the number of invocations as well as the
-     * arguments of each individual invocation to Frame.Consumer's methods.
-     */
-    private static class MockConsumer implements Frame.Consumer {
-
-        private int invocationOrder;
-
-        private Optional<Boolean> fin = Optional.empty();
-        private Optional<Boolean> rsv1 = Optional.empty();
-        private Optional<Boolean> rsv2 = Optional.empty();
-        private Optional<Boolean> rsv3 = Optional.empty();
-        private Optional<Opcode> opcode = Optional.empty();
-        private Optional<Boolean> mask = Optional.empty();
-        private OptionalLong payloadLen = OptionalLong.empty();
-        private OptionalInt maskingKey = OptionalInt.empty();
-
-        @Override
-        public void fin(boolean value) {
-            checkAndSetOrder(0, 1);
-            fin = Optional.of(value);
-        }
-
-        @Override
-        public void rsv1(boolean value) {
-            checkAndSetOrder(1, 2);
-            rsv1 = Optional.of(value);
-        }
-
-        @Override
-        public void rsv2(boolean value) {
-            checkAndSetOrder(2, 3);
-            rsv2 = Optional.of(value);
-        }
-
-        @Override
-        public void rsv3(boolean value) {
-            checkAndSetOrder(3, 4);
-            rsv3 = Optional.of(value);
-        }
-
-        @Override
-        public void opcode(Opcode value) {
-            checkAndSetOrder(4, 5);
-            opcode = Optional.of(value);
-        }
-
-        @Override
-        public void mask(boolean value) {
-            checkAndSetOrder(5, 6);
-            mask = Optional.of(value);
-        }
-
-        @Override
-        public void payloadLen(long value) {
-            checkAndSetOrder(p -> p == 5 || p == 6, n -> 7);
-            payloadLen = OptionalLong.of(value);
-        }
-
-        @Override
-        public void maskingKey(int value) {
-            checkAndSetOrder(7, 8);
-            maskingKey = of(value);
-        }
-
-        @Override
-        public void payloadData(ByteBuffer data) {
-            checkAndSetOrder(p -> p == 7 || p == 8, n -> 9);
-            assert payloadLen.isPresent();
-            if (payloadLen.getAsLong() != 0 && !data.hasRemaining()) {
-                throw new TestSupport.AssertionFailedException("Artefact of reading");
-            }
-        }
-
-        @Override
-        public void endFrame() {
-            checkAndSetOrder(9, 10);
-        }
-
-        boolean isEndFrame() {
-            return invocationOrder == 10;
-        }
-
-        public boolean fin() {
-            return fin.get();
-        }
-
-        public boolean rsv1() {
-            return rsv1.get();
-        }
-
-        public boolean rsv2() {
-            return rsv2.get();
-        }
-
-        public boolean rsv3() {
-            return rsv3.get();
-        }
-
-        public Opcode opcode() {
-            return opcode.get();
-        }
-
-        public boolean mask() {
-            return mask.get();
-        }
-
-        public long payloadLen() {
-            return payloadLen.getAsLong();
-        }
-
-        public OptionalInt maskingKey() {
-            return maskingKey;
-        }
-
-        private void checkAndSetOrder(int expectedValue, int newValue) {
-            checkAndSetOrder(p -> p == expectedValue, n -> newValue);
-        }
-
-        private void checkAndSetOrder(IntPredicate expectedValue,
-                                      IntUnaryOperator newValue) {
-            if (!expectedValue.test(invocationOrder)) {
-                throw new TestSupport.AssertionFailedException(
-                        expectedValue + " -> " + newValue);
-            }
-            invocationOrder = newValue.applyAsInt(invocationOrder);
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/TestSupport.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,336 +0,0 @@
-/*
- * 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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package java.net.http.internal.websocket;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Stack;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.regex.Pattern;
-
-import static java.util.List.of;
-import static java.util.Objects.requireNonNull;
-
-/*
- * Auxiliary test infrastructure
- */
-final class TestSupport {
-
-    private TestSupport() { }
-
-    static <A, B, R> Iterator<R> cartesianIterator(List<A> a,
-                                                   List<B> b,
-                                                   F2<A, B, R> f2) {
-        @SuppressWarnings("unchecked")
-        F<R> t = p -> f2.apply((A) p[0], (B) p[1]);
-        return cartesianIterator(of(a, b), t);
-    }
-
-    static <A, B, C, R> Iterator<R> cartesianIterator(List<A> a,
-                                                      List<B> b,
-                                                      List<C> c,
-                                                      F3<A, B, C, R> f3) {
-        @SuppressWarnings("unchecked")
-        F<R> t = p -> f3.apply((A) p[0], (B) p[1], (C) p[2]);
-        return cartesianIterator(of(a, b, c), t);
-    }
-
-    static <A, B, C, D, R> Iterator<R> cartesianIterator(List<A> a,
-                                                         List<B> b,
-                                                         List<C> c,
-                                                         List<D> d,
-                                                         F4<A, B, C, D, R> f4) {
-        @SuppressWarnings("unchecked")
-        F<R> t = p -> f4.apply((A) p[0], (B) p[1], (C) p[2], (D) p[3]);
-        return cartesianIterator(of(a, b, c, d), t);
-    }
-
-    static <A, B, C, D, E, R> Iterator<R> cartesianIterator(List<A> a,
-                                                            List<B> b,
-                                                            List<C> c,
-                                                            List<D> d,
-                                                            List<E> e,
-                                                            F5<A, B, C, D, E, R> f5) {
-        @SuppressWarnings("unchecked")
-        F<R> t = p -> f5.apply((A) p[0], (B) p[1], (C) p[2], (D) p[3], (E) p[4]);
-        return cartesianIterator(of(a, b, c, d, e), t);
-    }
-
-    static <R> Iterator<R> cartesianIterator(List<? extends List<?>> params,
-                                             F<R> function) {
-        if (params.isEmpty()) {
-            return Collections.emptyIterator();
-        }
-        for (List<?> l : params) {
-            if (l.isEmpty()) {
-                return Collections.emptyIterator();
-            }
-        }
-        // Assertion: if we are still here, there is at least a single element
-        // in the product
-        return new Iterator<>() {
-
-            private final int arity = params.size();
-            private final int[] coordinates = new int[arity];
-            private boolean hasNext = true;
-
-            @Override
-            public boolean hasNext() {
-                return hasNext;
-            }
-
-            @Override
-            public R next() {
-                if (!hasNext) {
-                    throw new NoSuchElementException();
-                }
-                Object[] array = new Object[arity];
-                for (int i = 0; i < arity; i++) {
-                    array[i] = params.get(i).get(coordinates[i]);
-                }
-                int p = arity - 1;
-                while (p >= 0 && coordinates[p] == params.get(p).size() - 1) {
-                    p--;
-                }
-                if (p < 0) {
-                    hasNext = false;
-                } else {
-                    coordinates[p]++;
-                    for (int i = p + 1; i < arity; i++) {
-                        coordinates[i] = 0;
-                    }
-                }
-                return function.apply(array);
-            }
-        };
-    }
-
-    @FunctionalInterface
-    public interface F1<A, R> {
-        R apply(A a);
-    }
-
-    @FunctionalInterface
-    public interface F2<A, B, R> {
-        R apply(A a, B b);
-    }
-
-    @FunctionalInterface
-    public interface F3<A, B, C, R> {
-        R apply(A a, B b, C c);
-    }
-
-    @FunctionalInterface
-    public interface F4<A, B, C, D, R> {
-        R apply(A a, B b, C c, D d);
-    }
-
-    @FunctionalInterface
-    public interface F5<A, B, C, D, E, R> {
-        R apply(A a, B b, C c, D d, E e);
-    }
-
-    @FunctionalInterface
-    public interface F<R> {
-        R apply(Object[] args);
-    }
-
-    static <T> Iterator<T> iteratorOf1(T element) {
-        return List.of(element).iterator();
-    }
-
-    @SafeVarargs
-    static <T> Iterator<T> iteratorOf(T... elements) {
-        return List.of(elements).iterator();
-    }
-
-    static <T> Iterator<T> limit(int maxElements, Iterator<? extends T> elements) {
-        return new Iterator<>() {
-
-            int count = maxElements;
-
-            @Override
-            public boolean hasNext() {
-                return count > 0 && elements.hasNext();
-            }
-
-            @Override
-            public T next() {
-                if (!hasNext()) {
-                    throw new NoSuchElementException();
-                }
-                count--;
-                return elements.next();
-            }
-        };
-    }
-
-    static ByteBuffer fullCopy(ByteBuffer src) {
-        ByteBuffer copy = ByteBuffer.allocate(src.capacity());
-        int p = src.position();
-        int l = src.limit();
-        src.clear();
-        copy.put(src).position(p).limit(l);
-        src.position(p).limit(l);
-        return copy;
-    }
-
-    static void forEachBufferPartition(ByteBuffer src,
-                                       Consumer<? super Iterable<? extends ByteBuffer>> action) {
-        forEachPartition(src.remaining(),
-                (lengths) -> {
-                    int end = src.position();
-                    List<ByteBuffer> buffers = new LinkedList<>();
-                    for (int len : lengths) {
-                        ByteBuffer d = src.duplicate();
-                        d.position(end);
-                        d.limit(end + len);
-                        end += len;
-                        buffers.add(d);
-                    }
-                    action.accept(buffers);
-                });
-    }
-
-    private static void forEachPartition(int n,
-                                         Consumer<? super Iterable<Integer>> action) {
-        forEachPartition(n, new Stack<>(), action);
-    }
-
-    private static void forEachPartition(int n,
-                                         Stack<Integer> path,
-                                         Consumer<? super Iterable<Integer>> action) {
-        if (n == 0) {
-            action.accept(path);
-        } else {
-            for (int i = 1; i <= n; i++) {
-                path.push(i);
-                forEachPartition(n - i, path, action);
-                path.pop();
-            }
-        }
-    }
-
-    static void forEachPermutation(int n, Consumer<? super int[]> c) {
-        int[] a = new int[n];
-        for (int i = 0; i < n; i++) {
-            a[i] = i;
-        }
-        permutations(0, a, c);
-    }
-
-    private static void permutations(int i, int[] a, Consumer<? super int[]> c) {
-        if (i == a.length) {
-            c.accept(Arrays.copyOf(a, a.length));
-            return;
-        }
-        for (int j = i; j < a.length; j++) {
-            swap(a, i, j);
-            permutations(i + 1, a, c);
-            swap(a, i, j);
-        }
-    }
-
-    private static void swap(int[] a, int i, int j) {
-        int x = a[i];
-        a[i] = a[j];
-        a[j] = x;
-    }
-
-    public static <T extends Throwable> T assertThrows(Class<? extends T> clazz,
-                                                       ThrowingProcedure code) {
-        @SuppressWarnings("unchecked")
-        T t = (T) assertThrows(clazz::isInstance, code);
-        return t;
-    }
-
-    /*
-     * The rationale behind asking for a regex is to not pollute variable names
-     * space in the scope of assertion: if it's something as simple as checking
-     * a message, we can do it inside
-     */
-    @SuppressWarnings("unchecked")
-    static <T extends Throwable> T assertThrows(Class<? extends T> clazz,
-                                                String messageRegex,
-                                                ThrowingProcedure code) {
-        requireNonNull(messageRegex, "messagePattern");
-        Predicate<Throwable> p = e -> clazz.isInstance(e)
-                && Pattern.matches(messageRegex, e.getMessage());
-        return (T) assertThrows(p, code);
-    }
-
-    static Throwable assertThrows(Predicate<? super Throwable> predicate,
-                                  ThrowingProcedure code) {
-        requireNonNull(predicate, "predicate");
-        requireNonNull(code, "code");
-        Throwable caught = null;
-        try {
-            code.run();
-        } catch (Throwable t) {
-            caught = t;
-        }
-        if (caught == null) {
-            throw new AssertionFailedException("No exception was thrown");
-        }
-        if (predicate.test(caught)) {
-            System.out.println("Got expected exception: " + caught);
-            return caught;
-        }
-        throw new AssertionFailedException("Caught exception didn't match the predicate", caught);
-    }
-
-    /*
-     * Blocking assertion, waits for completion
-     */
-    static Throwable assertCompletesExceptionally(Class<? extends Throwable> clazz,
-                                                  CompletionStage<?> stage) {
-        CompletableFuture<?> cf =
-                CompletableFuture.completedFuture(null).thenCompose(x -> stage);
-        return assertThrows(t -> clazz.isInstance(t.getCause()), cf::get);
-    }
-
-    interface ThrowingProcedure {
-        void run() throws Throwable;
-    }
-
-    static final class AssertionFailedException extends RuntimeException {
-
-        private static final long serialVersionUID = 1L;
-
-        AssertionFailedException(String message) {
-            super(message);
-        }
-
-        AssertionFailedException(String message, Throwable cause) {
-            super(message, cause);
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/java.net.http/java/net/http/internal/websocket/WebSocketImplTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,453 +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 java.net.http.internal.websocket;
-
-import java.net.http.WebSocket;
-import org.testng.annotations.Test;
-
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Collection;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-import static java.net.http.WebSocket.MessagePart.FIRST;
-import static java.net.http.WebSocket.MessagePart.LAST;
-import static java.net.http.WebSocket.MessagePart.PART;
-import static java.net.http.WebSocket.MessagePart.WHOLE;
-import static java.net.http.WebSocket.NORMAL_CLOSURE;
-import static java.net.http.internal.websocket.MockListener.Invocation.onClose;
-import static java.net.http.internal.websocket.MockListener.Invocation.onError;
-import static java.net.http.internal.websocket.MockListener.Invocation.onOpen;
-import static java.net.http.internal.websocket.MockListener.Invocation.onPing;
-import static java.net.http.internal.websocket.MockListener.Invocation.onPong;
-import static java.net.http.internal.websocket.MockListener.Invocation.onText;
-import static java.net.http.internal.websocket.MockTransport.onClose;
-import static java.net.http.internal.websocket.MockTransport.onPing;
-import static java.net.http.internal.websocket.MockTransport.onPong;
-import static java.net.http.internal.websocket.MockTransport.onText;
-import static java.net.http.internal.websocket.TestSupport.assertCompletesExceptionally;
-import static org.testng.Assert.assertEquals;
-
-/*
- * Formatting in this file may seem strange:
- *
- *  (
- *   ( ...)
- *  ...
- *  )
- *  ...
- *
- *  However there is a rationale behind it. Sometimes the level of argument
- *  nesting is high, which makes it hard to manage parentheses.
- */
-public class WebSocketImplTest {
-
-    // TODO: request in onClose/onError
-    // TODO: throw exception in onClose/onError
-    // TODO: exception is thrown from request()
-    // TODO: repeated sendClose complete normally
-    // TODO: default Close message is sent if IAE is thrown from sendClose
-
-    @Test
-    public void testNonPositiveRequest() throws Exception {
-        MockListener listener = new MockListener(Long.MAX_VALUE) {
-            @Override
-            protected void onOpen0(WebSocket webSocket) {
-                webSocket.request(0);
-            }
-        };
-        WebSocket ws = newInstance(listener, List.of(now(onText("1", WHOLE))));
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.Invocation> invocations = listener.invocations();
-        assertEquals(
-                invocations,
-                List.of(
-                        onOpen(ws),
-                        onError(ws, IllegalArgumentException.class)
-                )
-        );
-    }
-
-    @Test
-    public void testText1() throws Exception {
-        MockListener listener = new MockListener(Long.MAX_VALUE);
-        WebSocket ws = newInstance(
-                listener,
-                List.of(
-                        now(onText("1", FIRST)),
-                        now(onText("2", PART)),
-                        now(onText("3", LAST)),
-                        now(onClose(NORMAL_CLOSURE, "no reason"))
-                )
-        );
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.Invocation> invocations = listener.invocations();
-        assertEquals(
-                invocations,
-                List.of(
-                        onOpen(ws),
-                        onText(ws, "1", FIRST),
-                        onText(ws, "2", PART),
-                        onText(ws, "3", LAST),
-                        onClose(ws, NORMAL_CLOSURE, "no reason")
-                )
-        );
-    }
-
-    @Test
-    public void testText2() throws Exception {
-        MockListener listener = new MockListener(Long.MAX_VALUE);
-        WebSocket ws = newInstance(
-                listener,
-                List.of(
-                        now(onText("1", FIRST)),
-                        seconds(1, onText("2", PART)),
-                        now(onText("3", LAST)),
-                        seconds(1, onClose(NORMAL_CLOSURE, "no reason"))
-                )
-        );
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.Invocation> invocations = listener.invocations();
-        assertEquals(
-                invocations,
-                List.of(
-                        onOpen(ws),
-                        onText(ws, "1", FIRST),
-                        onText(ws, "2", PART),
-                        onText(ws, "3", LAST),
-                        onClose(ws, NORMAL_CLOSURE, "no reason")
-                )
-        );
-    }
-
-    @Test
-    public void testTextIntermixedWithPongs() throws Exception {
-        MockListener listener = new MockListener(Long.MAX_VALUE);
-        WebSocket ws = newInstance(
-                listener,
-                List.of(
-                        now(onText("1", FIRST)),
-                        now(onText("2", PART)),
-                        now(onPong(ByteBuffer.allocate(16))),
-                        seconds(1, onPong(ByteBuffer.allocate(32))),
-                        now(onText("3", LAST)),
-                        now(onPong(ByteBuffer.allocate(64))),
-                        now(onClose(NORMAL_CLOSURE, "no reason"))
-                )
-        );
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.Invocation> invocations = listener.invocations();
-        assertEquals(
-                invocations,
-                List.of(
-                        onOpen(ws),
-                        onText(ws, "1", FIRST),
-                        onText(ws, "2", PART),
-                        onPong(ws, ByteBuffer.allocate(16)),
-                        onPong(ws, ByteBuffer.allocate(32)),
-                        onText(ws, "3", LAST),
-                        onPong(ws, ByteBuffer.allocate(64)),
-                        onClose(ws, NORMAL_CLOSURE, "no reason")
-                )
-        );
-    }
-
-    @Test
-    public void testTextIntermixedWithPings() throws Exception {
-        MockListener listener = new MockListener(Long.MAX_VALUE);
-        WebSocket ws = newInstance(
-                listener,
-                List.of(
-                        now(onText("1", FIRST)),
-                        now(onText("2", PART)),
-                        now(onPing(ByteBuffer.allocate(16))),
-                        seconds(1, onPing(ByteBuffer.allocate(32))),
-                        now(onText("3", LAST)),
-                        now(onPing(ByteBuffer.allocate(64))),
-                        now(onClose(NORMAL_CLOSURE, "no reason"))
-                )
-        );
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.Invocation> invocations = listener.invocations();
-        assertEquals(
-                invocations,
-                List.of(
-                        onOpen(ws),
-                        onText(ws, "1", FIRST),
-                        onText(ws, "2", PART),
-                        onPing(ws, ByteBuffer.allocate(16)),
-                        onPing(ws, ByteBuffer.allocate(32)),
-                        onText(ws, "3", LAST),
-                        onPing(ws, ByteBuffer.allocate(64)),
-                        onClose(ws, NORMAL_CLOSURE, "no reason"))
-        );
-    }
-
-    // Tease out "java.lang.IllegalStateException: Send pending" due to possible
-    // race between sending a message and replenishing the permit
-    @Test
-    public void testManyTextMessages() {
-        WebSocketImpl ws = newInstance(
-                new MockListener(1),
-                new TransportFactory() {
-                    @Override
-                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                                            MessageStreamConsumer consumer) {
-
-                        final Random r = new Random();
-
-                        return new MockTransport<>(sendResultSupplier, consumer) {
-                            @Override
-                            protected CompletableFuture<T> defaultSend() {
-                                return millis(r.nextInt(100), result());
-                            }
-                        };
-                    }
-                });
-        int NUM_MESSAGES = 512;
-        CompletableFuture<WebSocket> current = CompletableFuture.completedFuture(ws);
-        for (int i = 0; i < NUM_MESSAGES; i++) {
-            current = current.thenCompose(w -> w.sendText(" ", true));
-        }
-        current.join();
-        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
-        assertEquals(transport.invocations().size(), NUM_MESSAGES);
-    }
-
-    @Test
-    public void testManyBinaryMessages() {
-        WebSocketImpl ws = newInstance(
-                new MockListener(1),
-                new TransportFactory() {
-                    @Override
-                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                                            MessageStreamConsumer consumer) {
-
-                        final Random r = new Random();
-
-                        return new MockTransport<>(sendResultSupplier, consumer) {
-                            @Override
-                            protected CompletableFuture<T> defaultSend() {
-                                return millis(r.nextInt(150), result());
-                            }
-                        };
-                    }
-                });
-        CompletableFuture<WebSocket> start = new CompletableFuture<>();
-
-        int NUM_MESSAGES = 512;
-        CompletableFuture<WebSocket> current = start;
-        for (int i = 0; i < NUM_MESSAGES; i++) {
-            current = current.thenComposeAsync(w -> w.sendBinary(ByteBuffer.allocate(1), true));
-        }
-
-        start.completeAsync(() -> ws);
-        current.join();
-
-        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
-        assertEquals(transport.invocations().size(), NUM_MESSAGES);
-    }
-
-
-    @Test
-    public void sendTextImmediately() {
-        WebSocketImpl ws = newInstance(
-                new MockListener(1),
-                new TransportFactory() {
-                    @Override
-                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                                            MessageStreamConsumer consumer) {
-                        return new MockTransport<>(sendResultSupplier, consumer);
-                    }
-                });
-        CompletableFuture.completedFuture(ws)
-                .thenCompose(w -> w.sendText("1", true))
-                .thenCompose(w -> w.sendText("2", true))
-                .thenCompose(w -> w.sendText("3", true))
-                .join();
-        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
-        assertEquals(transport.invocations().size(), 3);
-    }
-
-    @Test
-    public void sendTextWithDelay() {
-        MockListener listener = new MockListener(1);
-        WebSocketImpl ws = newInstance(
-                listener,
-                new TransportFactory() {
-                    @Override
-                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                                            MessageStreamConsumer consumer) {
-                        return new MockTransport<>(sendResultSupplier, consumer) {
-                            @Override
-                            protected CompletableFuture<T> defaultSend() {
-                                return seconds(1, result());
-                            }
-                        };
-                    }
-                });
-        CompletableFuture.completedFuture(ws)
-                .thenCompose(w -> w.sendText("1", true))
-                .thenCompose(w -> w.sendText("2", true))
-                .thenCompose(w -> w.sendText("3", true))
-                .join();
-        assertEquals(listener.invocations(), List.of(onOpen(ws)));
-        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
-        assertEquals(transport.invocations().size(), 3);
-    }
-
-    @Test
-    public void sendTextMixedDelay() {
-        MockListener listener = new MockListener(1);
-        WebSocketImpl ws = newInstance(
-                listener,
-                new TransportFactory() {
-
-                    final Random r = new Random();
-
-                    @Override
-                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                                            MessageStreamConsumer consumer) {
-                        return new MockTransport<>(sendResultSupplier, consumer) {
-                            @Override
-                            protected CompletableFuture<T> defaultSend() {
-                                return r.nextBoolean()
-                                        ? seconds(1, result())
-                                        : now(result());
-                            }
-                        };
-                    }
-                });
-        CompletableFuture.completedFuture(ws)
-                .thenCompose(w -> w.sendText("1", true))
-                .thenCompose(w -> w.sendText("2", true))
-                .thenCompose(w -> w.sendText("3", true))
-                .thenCompose(w -> w.sendText("4", true))
-                .thenCompose(w -> w.sendText("5", true))
-                .thenCompose(w -> w.sendText("6", true))
-                .thenCompose(w -> w.sendText("7", true))
-                .thenCompose(w -> w.sendText("8", true))
-                .thenCompose(w -> w.sendText("9", true))
-                .join();
-        assertEquals(listener.invocations(), List.of(onOpen(ws)));
-        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
-        assertEquals(transport.invocations().size(), 9);
-    }
-
-    @Test(enabled = false) // temporarily disabled
-    public void sendControlMessagesConcurrently() {
-        MockListener listener = new MockListener(1);
-
-        CompletableFuture<?> first = new CompletableFuture<>(); // barrier
-
-        WebSocketImpl ws = newInstance(
-                listener,
-                new TransportFactory() {
-
-                    final AtomicInteger i = new AtomicInteger();
-
-                    @Override
-                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                                            MessageStreamConsumer consumer) {
-                        return new MockTransport<>(sendResultSupplier, consumer) {
-                            @Override
-                            protected CompletableFuture<T> defaultSend() {
-                                if (i.incrementAndGet() == 1) {
-                                    return first.thenApply(o -> result());
-                                } else {
-                                    return now(result());
-                                }
-                            }
-                        };
-                    }
-                });
-
-        CompletableFuture<?> cf1 = ws.sendPing(ByteBuffer.allocate(0));
-        CompletableFuture<?> cf2 = ws.sendPong(ByteBuffer.allocate(0));
-        CompletableFuture<?> cf3 = ws.sendClose(NORMAL_CLOSURE, "");
-        CompletableFuture<?> cf4 = ws.sendClose(NORMAL_CLOSURE, "");
-        CompletableFuture<?> cf5 = ws.sendPing(ByteBuffer.allocate(0));
-        CompletableFuture<?> cf6 = ws.sendPong(ByteBuffer.allocate(0));
-
-        first.complete(null);
-        // Don't care about exceptional completion, only that all of them have
-        // completed
-        CompletableFuture.allOf(cf1, cf2, cf3, cf4, cf5, cf6)
-                .handle((v, e) -> null).join();
-
-        cf3.join(); /* Check that sendClose has completed normally */
-        cf4.join(); /* Check that repeated sendClose has completed normally */
-        assertCompletesExceptionally(IllegalStateException.class, cf5);
-        assertCompletesExceptionally(IllegalStateException.class, cf6);
-
-        assertEquals(listener.invocations(), List.of(onOpen(ws)));
-        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
-        assertEquals(transport.invocations().size(), 3); // 6 minus 3 that were not accepted
-    }
-
-    private static <T> CompletableFuture<T> seconds(long val, T result) {
-        return new CompletableFuture<T>()
-                .completeOnTimeout(result, val, TimeUnit.SECONDS);
-    }
-
-    private static <T> CompletableFuture<T> millis(long val, T result) {
-        return new CompletableFuture<T>()
-                .completeOnTimeout(result, val, TimeUnit.MILLISECONDS);
-    }
-
-    private static <T> CompletableFuture<T> now(T result) {
-        return CompletableFuture.completedFuture(result);
-    }
-
-    private static WebSocketImpl newInstance(
-            WebSocket.Listener listener,
-            Collection<CompletableFuture<Consumer<MessageStreamConsumer>>> input) {
-        TransportFactory factory = new TransportFactory() {
-            @Override
-            public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
-                                                    MessageStreamConsumer consumer) {
-                return new MockTransport<T>(sendResultSupplier, consumer) {
-                    @Override
-                    protected Collection<CompletableFuture<Consumer<MessageStreamConsumer>>> receive() {
-                        return input;
-                    }
-                };
-            }
-        };
-        return newInstance(listener, factory);
-    }
-
-    private static WebSocketImpl newInstance(WebSocket.Listener listener,
-                                             TransportFactory factory) {
-        URI uri = URI.create("ws://localhost");
-        String subprotocol = "";
-        return WebSocketImpl.newInstance(uri, subprotocol, listener, factory);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/BuildingWebSocketTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http.websocket;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static jdk.internal.net.http.websocket.TestSupport.assertCompletesExceptionally;
+import static jdk.internal.net.http.websocket.TestSupport.assertThrows;
+
+/*
+ * In some places in this class a new String is created out of a string literal.
+ * The idea is to make sure the code under test relies on something better than
+ * the reference equality ( == ) for string equality checks.
+ */
+public class BuildingWebSocketTest {
+
+    private final static URI VALID_URI = URI.create("ws://websocket.example.com");
+
+    @Test
+    public void nullArguments() {
+        HttpClient c = HttpClient.newHttpClient();
+
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .buildAsync(null, listener()));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .buildAsync(VALID_URI, null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .buildAsync(null, null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .header(null, "value"));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .header("name", null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .header(null, null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .subprotocols(null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .subprotocols(null, "sub2.example.com"));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .subprotocols("sub1.example.com", (String) null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .subprotocols("sub1.example.com", (String[]) null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .subprotocols("sub1.example.com", "sub2.example.com", null));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                             .subprotocols("sub1.example.com", null, "sub3.example.com"));
+        assertThrows(NullPointerException.class,
+                     () -> c.newWebSocketBuilder()
+                            .connectTimeout(null));
+    }
+
+    @Test(dataProvider = "badURIs")
+    void illegalURI(URI uri) {
+        WebSocket.Builder b = HttpClient.newHttpClient().newWebSocketBuilder();
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(uri, listener()));
+    }
+
+    @Test
+    public void illegalHeaders() {
+        List<String> headers =
+                List.of("Sec-WebSocket-Accept",
+                        "Sec-WebSocket-Extensions",
+                        "Sec-WebSocket-Key",
+                        "Sec-WebSocket-Protocol",
+                        "Sec-WebSocket-Version")
+                        .stream()
+                        .flatMap(s -> Stream.of(s, new String(s))) // a string and a copy of it
+                        .collect(Collectors.toList());
+
+        Function<String, CompletionStage<?>> f =
+                header -> HttpClient.newHttpClient()
+                        .newWebSocketBuilder()
+                        .header(header, "value")
+                        .buildAsync(VALID_URI, listener());
+
+        headers.forEach(h -> assertCompletesExceptionally(IllegalArgumentException.class, f.apply(h)));
+    }
+
+    // TODO: test for bad syntax headers
+    // TODO: test for overwrites (subprotocols) and additions (headers)
+
+    @Test(dataProvider = "badSubprotocols")
+    public void illegalSubprotocolsSyntax(String s) {
+        WebSocket.Builder b = HttpClient.newHttpClient()
+                .newWebSocketBuilder()
+                .subprotocols(s);
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(VALID_URI, listener()));
+    }
+
+    @Test(dataProvider = "duplicatingSubprotocols")
+    public void illegalSubprotocolsDuplicates(String mostPreferred,
+                                              String[] lesserPreferred) {
+        WebSocket.Builder b = HttpClient.newHttpClient()
+                .newWebSocketBuilder()
+                .subprotocols(mostPreferred, lesserPreferred);
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(VALID_URI, listener()));
+    }
+
+    @Test(dataProvider = "badConnectTimeouts")
+    public void illegalConnectTimeout(Duration d) {
+        WebSocket.Builder b = HttpClient.newHttpClient()
+                .newWebSocketBuilder()
+                .connectTimeout(d);
+        assertCompletesExceptionally(IllegalArgumentException.class,
+                                     b.buildAsync(VALID_URI, listener()));
+    }
+
+    @DataProvider
+    public Object[][] badURIs() {
+        return new Object[][]{
+                {URI.create("http://example.com")},
+                {URI.create("ftp://example.com")},
+                {URI.create("wss://websocket.example.com/hello#fragment")},
+                {URI.create("ws://websocket.example.com/hello#fragment")},
+        };
+    }
+
+    @DataProvider
+    public Object[][] badConnectTimeouts() {
+        return new Object[][]{
+                {Duration.ofDays   ( 0)},
+                {Duration.ofDays   (-1)},
+                {Duration.ofHours  ( 0)},
+                {Duration.ofHours  (-1)},
+                {Duration.ofMinutes( 0)},
+                {Duration.ofMinutes(-1)},
+                {Duration.ofSeconds( 0)},
+                {Duration.ofSeconds(-1)},
+                {Duration.ofMillis ( 0)},
+                {Duration.ofMillis (-1)},
+                {Duration.ofNanos  ( 0)},
+                {Duration.ofNanos  (-1)},
+                {Duration.ZERO},
+        };
+    }
+
+    // https://tools.ietf.org/html/rfc7230#section-3.2.6
+    // https://tools.ietf.org/html/rfc20
+    @DataProvider
+    public static Object[][] badSubprotocols() {
+        return new Object[][]{
+                {""},
+                {new String("")},
+                {"round-brackets("},
+                {"round-brackets)"},
+                {"comma,"},
+                {"slash/"},
+                {"colon:"},
+                {"semicolon;"},
+                {"angle-brackets<"},
+                {"angle-brackets>"},
+                {"equals="},
+                {"question-mark?"},
+                {"at@"},
+                {"brackets["},
+                {"backslash\\"},
+                {"brackets]"},
+                {"curly-brackets{"},
+                {"curly-brackets}"},
+                {"space "},
+                {"non-printable-character " + Character.toString((char) 31)},
+                {"non-printable-character " + Character.toString((char) 127)},
+        };
+    }
+
+    @DataProvider
+    public static Object[][] duplicatingSubprotocols() {
+        return new Object[][]{
+                {"a.b.c", new String[]{"a.b.c"}},
+                {"a.b.c", new String[]{"x.y.z", "p.q.r", "x.y.z"}},
+                {"a.b.c", new String[]{new String("a.b.c")}},
+        };
+    }
+
+    private static WebSocket.Listener listener() {
+        return new WebSocket.Listener() { };
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/HeaderWriterTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http.websocket;
+
+import org.testng.annotations.Test;
+import jdk.internal.net.http.websocket.Frame.HeaderWriter;
+import jdk.internal.net.http.websocket.Frame.Opcode;
+
+import java.nio.ByteBuffer;
+import java.util.OptionalInt;
+
+import static java.util.OptionalInt.empty;
+import static java.util.OptionalInt.of;
+import static org.testng.Assert.assertEquals;
+import static jdk.internal.net.http.websocket.TestSupport.assertThrows;
+import static jdk.internal.net.http.websocket.TestSupport.forEachPermutation;
+
+public class HeaderWriterTest {
+
+    private long cases, frames;
+
+    @Test
+    public void negativePayload() {
+        System.out.println("testing negative payload");
+        HeaderWriter w = new HeaderWriter();
+        assertThrows(IllegalArgumentException.class,
+                ".*(?i)negative.*",
+                () -> w.payloadLen(-1));
+    }
+
+    @Test
+    public void test() {
+        System.out.println("testing regular payloads");
+        final long[] payloads = {0, 126, 65536, Integer.MAX_VALUE + 1L};
+        final OptionalInt[] masks = {empty(), of(-1), of(0), of(0xCAFEBABE),
+                of(Integer.MAX_VALUE), of(Integer.MIN_VALUE)};
+        for (boolean fin : new boolean[]{true, false}) {
+            for (boolean rsv1 : new boolean[]{true, false}) {
+                for (boolean rsv2 : new boolean[]{true, false}) {
+                    for (boolean rsv3 : new boolean[]{true, false}) {
+                        for (Opcode opcode : Opcode.values()) {
+                            for (long payloadLen : payloads) {
+                                for (OptionalInt mask : masks) {
+                                    verify(fin, rsv1, rsv2, rsv3, opcode, payloadLen, mask);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        System.out.println("Frames: " + frames + ", Total cases: " + cases);
+    }
+
+    private void verify(boolean fin,
+                        boolean rsv1,
+                        boolean rsv2,
+                        boolean rsv3,
+                        Opcode opcode,
+                        long payloadLen,
+                        OptionalInt mask) {
+        frames++;
+        HeaderWriter writer = new HeaderWriter();
+        ByteBuffer expected = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
+        writer.fin(fin).rsv1(rsv1).rsv2(rsv2).rsv3(rsv3).opcode(opcode).payloadLen(payloadLen);
+        mask.ifPresentOrElse(writer::mask, writer::noMask);
+        writer.write(expected);
+        expected.flip();
+        verifyPermutations(expected, writer,
+                () -> writer.fin(fin),
+                () -> writer.rsv1(rsv1),
+                () -> writer.rsv2(rsv2),
+                () -> writer.rsv3(rsv3),
+                () -> writer.opcode(opcode),
+                () -> writer.payloadLen(payloadLen),
+                () -> mask.ifPresentOrElse(writer::mask, writer::noMask));
+    }
+
+    private void verifyPermutations(ByteBuffer expected,
+                                    HeaderWriter writer,
+                                    Runnable... actions) {
+        forEachPermutation(actions.length,
+                order -> {
+                    cases++;
+                    for (int i : order) {
+                        actions[i].run();
+                    }
+                    ByteBuffer actual = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES + 2);
+                    writer.write(actual);
+                    actual.flip();
+                    assertEquals(actual, expected);
+                });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/MaskerTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http.websocket;
+
+import org.testng.annotations.Test;
+
+import java.nio.ByteBuffer;
+import java.security.SecureRandom;
+import java.util.stream.IntStream;
+
+import static org.testng.Assert.assertEquals;
+import static jdk.internal.net.http.websocket.Frame.Masker.transferMasking;
+import static jdk.internal.net.http.websocket.TestSupport.forEachBufferPartition;
+import static jdk.internal.net.http.websocket.TestSupport.fullCopy;
+
+public class MaskerTest {
+
+    private static final SecureRandom random = new SecureRandom();
+
+    @Test
+    public void stateless() {
+        IntStream.iterate(0, i -> i + 1).limit(125).boxed()
+                .forEach(r -> {
+                    int m = random.nextInt();
+                    ByteBuffer src = createSourceBuffer(r);
+                    ByteBuffer dst = createDestinationBuffer(r);
+                    verify(src, dst, maskArray(m), 0,
+                            () -> transferMasking(src, dst, m));
+                });
+    }
+
+    /*
+     * Stateful masker to make sure setting a mask resets the state as if a new
+     * Masker instance is created each time
+     */
+    private final Frame.Masker masker = new Frame.Masker();
+
+    @Test
+    public void stateful0() {
+        // This size (17 = 8 + 8 + 1) should test all the stages
+        // (galloping/slow) of masking good enough
+        int N = 17;
+        ByteBuffer src = createSourceBuffer(N);
+        ByteBuffer dst = createDestinationBuffer(N);
+        int mask = random.nextInt();
+        forEachBufferPartition(src,
+                buffers -> {
+                    int offset = 0;
+                    masker.mask(mask);
+                    int[] maskBytes = maskArray(mask);
+                    for (ByteBuffer s : buffers) {
+                        offset = verify(s, dst, maskBytes, offset,
+                                () -> masker.transferMasking(s, dst));
+                    }
+                });
+    }
+
+    @Test
+    public void stateful1() {
+        int m = random.nextInt();
+        masker.mask(m);
+        ByteBuffer src = ByteBuffer.allocate(0);
+        ByteBuffer dst = ByteBuffer.allocate(16);
+        verify(src, dst, maskArray(m), 0,
+                () -> masker.transferMasking(src, dst));
+    }
+
+    private static int verify(ByteBuffer src,
+                              ByteBuffer dst,
+                              int[] maskBytes,
+                              int offset,
+                              Runnable masking) {
+        ByteBuffer srcCopy = fullCopy(src);
+        ByteBuffer dstCopy = fullCopy(dst);
+        masking.run();
+        int srcRemaining = srcCopy.remaining();
+        int dstRemaining = dstCopy.remaining();
+        int masked = Math.min(srcRemaining, dstRemaining);
+        // 1. position check
+        assertEquals(src.position(), srcCopy.position() + masked);
+        assertEquals(dst.position(), dstCopy.position() + masked);
+        // 2. masking check
+        src.position(srcCopy.position());
+        dst.position(dstCopy.position());
+        for (; src.hasRemaining() && dst.hasRemaining();
+             offset = (offset + 1) & 3) {
+            assertEquals(dst.get(), src.get() ^ maskBytes[offset]);
+        }
+        // 3. corruption check
+        // 3.1 src contents haven't changed
+        int srcPosition = src.position();
+        int srcLimit = src.limit();
+        src.clear();
+        srcCopy.clear();
+        assertEquals(src, srcCopy);
+        src.limit(srcLimit).position(srcPosition); // restore src
+        // 3.2 dst leading and trailing regions' contents haven't changed
+        int dstPosition = dst.position();
+        int dstInitialPosition = dstCopy.position();
+        int dstLimit = dst.limit();
+        // leading
+        dst.position(0).limit(dstInitialPosition);
+        dstCopy.position(0).limit(dstInitialPosition);
+        assertEquals(dst, dstCopy);
+        // trailing
+        dst.limit(dst.capacity()).position(dstLimit);
+        dstCopy.limit(dst.capacity()).position(dstLimit);
+        assertEquals(dst, dstCopy);
+        // restore dst
+        dst.position(dstPosition).limit(dstLimit);
+        return offset;
+    }
+
+    private static ByteBuffer createSourceBuffer(int remaining) {
+        int leading = random.nextInt(4);
+        int trailing = random.nextInt(4);
+        byte[] bytes = new byte[leading + remaining + trailing];
+        random.nextBytes(bytes);
+        return ByteBuffer.wrap(bytes).position(leading).limit(leading + remaining);
+    }
+
+    private static ByteBuffer createDestinationBuffer(int remaining) {
+        int leading = random.nextInt(4);
+        int trailing = random.nextInt(4);
+        return ByteBuffer.allocate(leading + remaining + trailing)
+                .position(leading).limit(leading + remaining);
+    }
+
+    private static int[] maskArray(int mask) {
+        return new int[]{
+                (byte) (mask >>> 24),
+                (byte) (mask >>> 16),
+                (byte) (mask >>>  8),
+                (byte) (mask >>>  0)
+        };
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/MockListener.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,402 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.WebSocket;
+import java.net.http.WebSocket.MessagePart;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+import static jdk.internal.net.http.websocket.TestSupport.fullCopy;
+
+public class MockListener implements WebSocket.Listener {
+
+    private final long bufferSize;
+    private long count;
+    private final List<Invocation> invocations = new ArrayList<>();
+    private final CompletableFuture<?> lastCall = new CompletableFuture<>();
+
+    /*
+     * Typical buffer sizes: 1, n, Long.MAX_VALUE
+     */
+    public MockListener(long bufferSize) {
+        if (bufferSize < 1) {
+            throw new IllegalArgumentException();
+        }
+        this.bufferSize = bufferSize;
+    }
+
+    @Override
+    public void onOpen(WebSocket webSocket) {
+        System.out.printf("onOpen(%s)%n", webSocket);
+        invocations.add(new OnOpen(webSocket));
+        onOpen0(webSocket);
+    }
+
+    protected void onOpen0(WebSocket webSocket) {
+        replenish(webSocket);
+    }
+
+    @Override
+    public CompletionStage<?> onText(WebSocket webSocket,
+                                     CharSequence message,
+                                     MessagePart part) {
+        System.out.printf("onText(%s, %s, %s)%n", webSocket, message, part);
+        invocations.add(new OnText(webSocket, message.toString(), part));
+        return onText0(webSocket, message, part);
+    }
+
+    protected CompletionStage<?> onText0(WebSocket webSocket,
+                                         CharSequence message,
+                                         MessagePart part) {
+        replenish(webSocket);
+        return null;
+    }
+
+    @Override
+    public CompletionStage<?> onBinary(WebSocket webSocket,
+                                       ByteBuffer message,
+                                       MessagePart part) {
+        System.out.printf("onBinary(%s, %s, %s)%n", webSocket, message, part);
+        invocations.add(new OnBinary(webSocket, fullCopy(message), part));
+        return onBinary0(webSocket, message, part);
+    }
+
+    protected CompletionStage<?> onBinary0(WebSocket webSocket,
+                                           ByteBuffer message,
+                                           MessagePart part) {
+        replenish(webSocket);
+        return null;
+    }
+
+    @Override
+    public CompletionStage<?> onPing(WebSocket webSocket, ByteBuffer message) {
+        System.out.printf("onPing(%s, %s)%n", webSocket, message);
+        invocations.add(new OnPing(webSocket, fullCopy(message)));
+        return onPing0(webSocket, message);
+    }
+
+    protected CompletionStage<?> onPing0(WebSocket webSocket, ByteBuffer message) {
+        replenish(webSocket);
+        return null;
+    }
+
+    @Override
+    public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+        System.out.printf("onPong(%s, %s)%n", webSocket, message);
+        invocations.add(new OnPong(webSocket, fullCopy(message)));
+        return onPong0(webSocket, message);
+    }
+
+    protected CompletionStage<?> onPong0(WebSocket webSocket, ByteBuffer message) {
+        replenish(webSocket);
+        return null;
+    }
+
+    @Override
+    public CompletionStage<?> onClose(WebSocket webSocket,
+                                      int statusCode,
+                                      String reason) {
+        System.out.printf("onClose(%s, %s, %s)%n", webSocket, statusCode, reason);
+        invocations.add(new OnClose(webSocket, statusCode, reason));
+        lastCall.complete(null);
+        return null;
+    }
+
+    @Override
+    public void onError(WebSocket webSocket, Throwable error) {
+        System.out.printf("onError(%s, %s)%n", webSocket, error);
+        invocations.add(new OnError(webSocket, error == null ? null : error.getClass()));
+        lastCall.complete(null);
+    }
+
+    public CompletableFuture<?> onCloseOrOnErrorCalled() {
+        return lastCall.copy();
+    }
+
+    protected void replenish(WebSocket webSocket) {
+        if (--count <= 0) {
+            count = bufferSize - bufferSize / 2;
+        }
+        webSocket.request(count);
+    }
+
+    public List<Invocation> invocations() {
+        return new ArrayList<>(invocations);
+    }
+
+    public abstract static class Invocation {
+
+        public static OnOpen onOpen(WebSocket webSocket) {
+            return new OnOpen(webSocket);
+        }
+
+        public static OnText onText(WebSocket webSocket,
+                                    String text,
+                                    MessagePart part) {
+            return new OnText(webSocket, text, part);
+        }
+
+        public static OnBinary onBinary(WebSocket webSocket,
+                                        ByteBuffer data,
+                                        MessagePart part) {
+            return new OnBinary(webSocket, data, part);
+        }
+
+        public static OnPing onPing(WebSocket webSocket,
+                                    ByteBuffer data) {
+            return new OnPing(webSocket, data);
+        }
+
+        public static OnPong onPong(WebSocket webSocket,
+                                    ByteBuffer data) {
+            return new OnPong(webSocket, data);
+        }
+
+        public static OnClose onClose(WebSocket webSocket,
+                                      int statusCode,
+                                      String reason) {
+            return new OnClose(webSocket, statusCode, reason);
+        }
+
+        public static OnError onError(WebSocket webSocket,
+                                      Class<? extends Throwable> clazz) {
+            return new OnError(webSocket, clazz);
+        }
+
+        final WebSocket webSocket;
+
+        private Invocation(WebSocket webSocket) {
+            this.webSocket = webSocket;
+        }
+    }
+
+    public static final class OnOpen extends Invocation {
+
+        public OnOpen(WebSocket webSocket) {
+            super(webSocket);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Invocation that = (Invocation) o;
+            return Objects.equals(webSocket, that.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(webSocket);
+        }
+    }
+
+    public static final class OnText extends Invocation {
+
+        final String text;
+        final MessagePart part;
+
+        public OnText(WebSocket webSocket, String text, MessagePart part) {
+            super(webSocket);
+            this.text = text;
+            this.part = part;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OnText onText = (OnText) o;
+            return Objects.equals(text, onText.text) &&
+                    part == onText.part &&
+                    Objects.equals(webSocket, onText.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(text, part, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onText(%s, %s, %s)", webSocket, text, part);
+        }
+    }
+
+    public static final class OnBinary extends Invocation {
+
+        final ByteBuffer data;
+        final MessagePart part;
+
+        public OnBinary(WebSocket webSocket, ByteBuffer data, MessagePart part) {
+            super(webSocket);
+            this.data = data;
+            this.part = part;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OnBinary onBinary = (OnBinary) o;
+            return Objects.equals(data, onBinary.data) &&
+                    part == onBinary.part &&
+                    Objects.equals(webSocket, onBinary.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data, part, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onBinary(%s, %s, %s)", webSocket, data, part);
+        }
+    }
+
+    public static final class OnPing extends Invocation {
+
+        final ByteBuffer data;
+
+        public OnPing(WebSocket webSocket, ByteBuffer data) {
+            super(webSocket);
+            this.data = data;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OnPing onPing = (OnPing) o;
+            return Objects.equals(data, onPing.data) &&
+                    Objects.equals(webSocket, onPing.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onPing(%s, %s)", webSocket, data);
+        }
+    }
+
+    public static final class OnPong extends Invocation {
+
+        final ByteBuffer data;
+
+        public OnPong(WebSocket webSocket, ByteBuffer data) {
+            super(webSocket);
+            this.data = data;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OnPong onPong = (OnPong) o;
+            return Objects.equals(data, onPong.data) &&
+                    Objects.equals(webSocket, onPong.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onPong(%s, %s)", webSocket, data);
+        }
+    }
+
+    public static final class OnClose extends Invocation {
+
+        final int statusCode;
+        final String reason;
+
+        public OnClose(WebSocket webSocket, int statusCode, String reason) {
+            super(webSocket);
+            this.statusCode = statusCode;
+            this.reason = reason;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OnClose onClose = (OnClose) o;
+            return statusCode == onClose.statusCode &&
+                    Objects.equals(reason, onClose.reason) &&
+                    Objects.equals(webSocket, onClose.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(statusCode, reason, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onClose(%s, %s, %s)", webSocket, statusCode, reason);
+        }
+    }
+
+    public static final class OnError extends Invocation {
+
+        final Class<? extends Throwable> clazz;
+
+        public OnError(WebSocket webSocket, Class<? extends Throwable> clazz) {
+            super(webSocket);
+            this.clazz = clazz;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            OnError onError = (OnError) o;
+            return Objects.equals(clazz, onError.clazz) &&
+                    Objects.equals(webSocket, onError.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(clazz, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onError(%s, %s)", webSocket, clazz);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/MockTransport.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,436 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.WebSocket.MessagePart;
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.SequentialScheduler;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static jdk.internal.net.http.websocket.TestSupport.fullCopy;
+
+public class MockTransport<T> implements Transport<T> {
+
+    private final long startTime = System.currentTimeMillis();
+    private final Queue<Invocation> output = new ConcurrentLinkedQueue<>();
+    private final Queue<CompletableFuture<Consumer<MessageStreamConsumer>>>
+            input = new ConcurrentLinkedQueue<>();
+    private final Supplier<T> supplier;
+    private final MessageStreamConsumer consumer;
+    private final SequentialScheduler scheduler
+            = new SequentialScheduler(new ReceiveTask());
+    private final Demand demand = new Demand();
+
+    public MockTransport(Supplier<T> sendResultSupplier,
+                         MessageStreamConsumer consumer) {
+        this.supplier = sendResultSupplier;
+        this.consumer = consumer;
+        input.addAll(receive());
+    }
+
+    @Override
+    public final CompletableFuture<T> sendText(CharSequence message,
+                                               boolean isLast) {
+        output.add(Invocation.sendText(message, isLast));
+        return send(String.format("sendText(%s, %s)", message, isLast),
+                    () -> sendText0(message, isLast));
+    }
+
+    protected CompletableFuture<T> sendText0(CharSequence message,
+                                             boolean isLast) {
+        return defaultSend();
+    }
+
+    protected CompletableFuture<T> defaultSend() {
+        return CompletableFuture.completedFuture(result());
+    }
+
+    @Override
+    public final CompletableFuture<T> sendBinary(ByteBuffer message,
+                                                 boolean isLast) {
+        output.add(Invocation.sendBinary(message, isLast));
+        return send(String.format("sendBinary(%s, %s)", message, isLast),
+                    () -> sendBinary0(message, isLast));
+    }
+
+    protected CompletableFuture<T> sendBinary0(ByteBuffer message,
+                                               boolean isLast) {
+        return defaultSend();
+    }
+
+    @Override
+    public final CompletableFuture<T> sendPing(ByteBuffer message) {
+        output.add(Invocation.sendPing(message));
+        return send(String.format("sendPing(%s)", message),
+                    () -> sendPing0(message));
+    }
+
+    protected CompletableFuture<T> sendPing0(ByteBuffer message) {
+        return defaultSend();
+    }
+
+    @Override
+    public final CompletableFuture<T> sendPong(ByteBuffer message) {
+        output.add(Invocation.sendPong(message));
+        return send(String.format("sendPong(%s)", message),
+                    () -> sendPong0(message));
+    }
+
+    protected CompletableFuture<T> sendPong0(ByteBuffer message) {
+        return defaultSend();
+    }
+
+    @Override
+    public final CompletableFuture<T> sendClose(int statusCode, String reason) {
+        output.add(Invocation.sendClose(statusCode, reason));
+        return send(String.format("sendClose(%s, %s)", statusCode, reason),
+                    () -> sendClose0(statusCode, reason));
+    }
+
+    protected CompletableFuture<T> sendClose0(int statusCode, String reason) {
+        return defaultSend();
+    }
+
+    protected Collection<CompletableFuture<Consumer<MessageStreamConsumer>>> receive() {
+        return List.of();
+    }
+
+    public static Consumer<MessageStreamConsumer> onText(CharSequence data,
+                                                         MessagePart part) {
+        return c -> c.onText(data.toString(), part);
+    }
+
+    public static Consumer<MessageStreamConsumer> onBinary(ByteBuffer data,
+                                                           MessagePart part) {
+        return c -> c.onBinary(fullCopy(data), part);
+    }
+
+    public static Consumer<MessageStreamConsumer> onPing(ByteBuffer data) {
+        return c -> c.onPing(fullCopy(data));
+    }
+
+    public static Consumer<MessageStreamConsumer> onPong(ByteBuffer data) {
+        return c -> c.onPong(fullCopy(data));
+    }
+
+    public static Consumer<MessageStreamConsumer> onClose(int statusCode,
+                                                          String reason) {
+        return c -> c.onClose(statusCode, reason);
+    }
+
+    public static Consumer<MessageStreamConsumer> onError(Throwable error) {
+        return c -> c.onError(error);
+    }
+
+    public static Consumer<MessageStreamConsumer> onComplete() {
+        return c -> c.onComplete();
+    }
+
+    @Override
+    public void request(long n) {
+        demand.increase(n);
+        scheduler.runOrSchedule();
+    }
+
+    @Override
+    public void acknowledgeReception() {
+        demand.tryDecrement();
+    }
+
+    @Override
+    public final void closeOutput() throws IOException {
+        output.add(Invocation.closeOutput());
+        begin("closeOutput()");
+        closeOutput0();
+        end("closeOutput()");
+    }
+
+    protected void closeOutput0() throws IOException {
+        defaultClose();
+    }
+
+    protected void defaultClose() throws IOException {
+    }
+
+    @Override
+    public final void closeInput() throws IOException {
+        output.add(Invocation.closeInput());
+        begin("closeInput()");
+        closeInput0();
+        end("closeInput()");
+    }
+
+    protected void closeInput0() throws IOException {
+        defaultClose();
+    }
+
+    public abstract static class Invocation {
+
+        static Invocation.SendText sendText(CharSequence message,
+                                            boolean isLast) {
+            return new SendText(message, isLast);
+        }
+
+        static Invocation.SendBinary sendBinary(ByteBuffer message,
+                                                boolean isLast) {
+            return new SendBinary(message, isLast);
+        }
+
+        static Invocation.SendPing sendPing(ByteBuffer message) {
+            return new SendPing(message);
+        }
+
+        static Invocation.SendPong sendPong(ByteBuffer message) {
+            return new SendPong(message);
+        }
+
+        static Invocation.SendClose sendClose(int statusCode, String reason) {
+            return new SendClose(statusCode, reason);
+        }
+
+        public static CloseOutput closeOutput() {
+            return new CloseOutput();
+        }
+
+        public static CloseInput closeInput() {
+            return new CloseInput();
+        }
+
+        public static final class SendText extends Invocation {
+
+            final CharSequence message;
+            final boolean isLast;
+
+            SendText(CharSequence message, boolean isLast) {
+                this.message = message.toString();
+                this.isLast = isLast;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) return true;
+                if (obj == null || getClass() != obj.getClass()) return false;
+                SendText sendText = (SendText) obj;
+                return isLast == sendText.isLast &&
+                        Objects.equals(message, sendText.message);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(isLast, message);
+            }
+        }
+
+        public static final class SendBinary extends Invocation {
+
+            final ByteBuffer message;
+            final boolean isLast;
+
+            SendBinary(ByteBuffer message, boolean isLast) {
+                this.message = fullCopy(message);
+                this.isLast = isLast;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) return true;
+                if (obj == null || getClass() != obj.getClass()) return false;
+                SendBinary that = (SendBinary) obj;
+                return isLast == that.isLast &&
+                        Objects.equals(message, that.message);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(message, isLast);
+            }
+        }
+
+        private static final class SendPing extends Invocation {
+
+            final ByteBuffer message;
+
+            SendPing(ByteBuffer message) {
+                this.message = fullCopy(message);
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) return true;
+                if (obj == null || getClass() != obj.getClass()) return false;
+                SendPing sendPing = (SendPing) obj;
+                return Objects.equals(message, sendPing.message);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(message);
+            }
+        }
+
+        private static final class SendPong extends Invocation {
+
+            final ByteBuffer message;
+
+            SendPong(ByteBuffer message) {
+                this.message = fullCopy(message);
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) return true;
+                if (obj == null || getClass() != obj.getClass()) return false;
+                SendPing sendPing = (SendPing) obj;
+                return Objects.equals(message, sendPing.message);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(message);
+            }
+        }
+
+        private static final class SendClose extends Invocation {
+
+            final int statusCode;
+            final String reason;
+
+            SendClose(int statusCode, String reason) {
+                this.statusCode = statusCode;
+                this.reason = reason;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj) return true;
+                if (obj == null || getClass() != obj.getClass()) return false;
+                SendClose sendClose = (SendClose) obj;
+                return statusCode == sendClose.statusCode &&
+                        Objects.equals(reason, sendClose.reason);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(statusCode, reason);
+            }
+        }
+
+        private static final class CloseOutput extends Invocation {
+
+            CloseOutput() { }
+
+            @Override
+            public int hashCode() {
+                return 0;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                return obj instanceof CloseOutput;
+            }
+        }
+
+        private static final class CloseInput extends Invocation {
+
+            CloseInput() { }
+
+            @Override
+            public int hashCode() {
+                return 0;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                return obj instanceof CloseInput;
+            }
+        }
+    }
+
+    public Queue<Invocation> invocations() {
+        return new LinkedList<>(output);
+    }
+
+    protected final T result() {
+        return supplier.get();
+    }
+
+    private CompletableFuture<T> send(String name,
+                                      Supplier<CompletableFuture<T>> supplier) {
+        begin(name);
+        CompletableFuture<T> cf = supplier.get().whenComplete((r, e) -> {
+            System.out.printf("[%6s ms.] complete %s%n", elapsedTime(), name);
+        });
+        end(name);
+        return cf;
+    }
+
+    private void begin(String name) {
+        System.out.printf("[%6s ms.] begin %s%n", elapsedTime(), name);
+    }
+
+    private void end(String name) {
+        System.out.printf("[%6s ms.] end %s%n", elapsedTime(), name);
+    }
+
+    private long elapsedTime() {
+        return System.currentTimeMillis() - startTime;
+    }
+
+    private final class ReceiveTask implements SequentialScheduler.RestartableTask {
+
+        @Override
+        public void run(SequentialScheduler.DeferredCompleter taskCompleter) {
+            if (!scheduler.isStopped() && !demand.isFulfilled() && !input.isEmpty()) {
+                CompletableFuture<Consumer<MessageStreamConsumer>> cf = input.remove();
+                if (cf.isDone()) { // Forcing synchronous execution
+                    cf.join().accept(consumer);
+                    repeat(taskCompleter);
+                } else {
+                    cf.whenCompleteAsync((r, e) -> {
+                        r.accept(consumer);
+                        repeat(taskCompleter);
+                    });
+                }
+            } else {
+                taskCompleter.complete();
+            }
+        }
+
+        private void repeat(SequentialScheduler.DeferredCompleter taskCompleter) {
+            taskCompleter.complete();
+            scheduler.runOrSchedule();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/ReaderTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,271 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http.websocket;
+
+import org.testng.annotations.Test;
+import jdk.internal.net.http.websocket.Frame.Opcode;
+
+import java.nio.ByteBuffer;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+import java.util.function.IntPredicate;
+import java.util.function.IntUnaryOperator;
+
+import static java.util.OptionalInt.empty;
+import static java.util.OptionalInt.of;
+import static org.testng.Assert.assertEquals;
+import static jdk.internal.net.http.websocket.TestSupport.assertThrows;
+import static jdk.internal.net.http.websocket.TestSupport.forEachBufferPartition;
+
+public class ReaderTest {
+
+    private long cases, frames;
+
+    @Test
+    void notMinimalEncoding01() {
+        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
+        h.put((byte) 0b1000_0000).put((byte) 0b0111_1110).putChar((char) 125).flip();
+        assertThrows(FailWebSocketException.class,
+                ".*(?i)minimally-encoded.*",
+                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
+    }
+
+    @Test
+    void notMinimalEncoding02() {
+        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
+        h.put((byte) 0b1000_0000).put((byte) 0b0111_1111).putLong(125).flip();
+        assertThrows(FailWebSocketException.class,
+                ".*(?i)minimally-encoded.*",
+                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
+    }
+
+    @Test
+    void notMinimalEncoding03() {
+        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
+        h.put((byte) 0b1000_0000).put((byte) 0b0111_1111).putLong(65535).flip();
+        assertThrows(FailWebSocketException.class,
+                ".*(?i)minimally-encoded.*",
+                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
+    }
+
+    @Test
+    public void negativePayload() {
+        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
+        h.put((byte) 0b1000_0000).put((byte) 0b0111_1111).putLong(-2L).flip();
+        assertThrows(FailWebSocketException.class,
+                ".*(?i)negative.*",
+                () -> new Frame.Reader().readFrame(h, new MockConsumer()));
+    }
+
+    @Test
+    public void frameStart() {
+        final long[] payloads = {0, 126, 65536, Integer.MAX_VALUE + 1L};
+        final OptionalInt[] masks = {empty(), of(-1), of(0), of(0xCAFEBABE),
+                of(Integer.MAX_VALUE), of(Integer.MIN_VALUE)};
+        for (boolean fin : new boolean[]{true, false}) {
+            for (boolean rsv1 : new boolean[]{true, false}) {
+                for (boolean rsv2 : new boolean[]{true, false}) {
+                    for (boolean rsv3 : new boolean[]{true, false}) {
+                        for (Opcode opcode : Opcode.values()) {
+                            for (long payloadLen : payloads) {
+                                for (OptionalInt mask : masks) {
+                                    verifyFrameStart(fin, rsv1, rsv2, rsv3, opcode, payloadLen, mask);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        System.out.println("Frames: " + frames + ", Total cases: " + cases);
+    }
+
+    /*
+     * Tests whether or not the frame starts properly.
+     * That is, a header and the first invocation of payloadData (if any).
+     */
+    private void verifyFrameStart(boolean fin,
+                                  boolean rsv1,
+                                  boolean rsv2,
+                                  boolean rsv3,
+                                  Opcode opcode,
+                                  long payloadLen,
+                                  OptionalInt mask) {
+        frames++;
+        Frame.HeaderWriter w = new Frame.HeaderWriter();
+        ByteBuffer h = ByteBuffer.allocate(Frame.MAX_HEADER_SIZE_BYTES);
+        w.fin(fin).rsv1(rsv1).rsv2(rsv2).rsv3(rsv3).opcode(opcode).payloadLen(payloadLen);
+        mask.ifPresentOrElse(w::mask, w::noMask);
+        w.write(h);
+        h.flip();
+        forEachBufferPartition(h,
+                buffers -> {
+                    cases++;
+                    Frame.Reader r = new Frame.Reader();
+                    MockConsumer c = new MockConsumer();
+                    for (ByteBuffer b : buffers) {
+                        r.readFrame(b, c);
+                    }
+                    assertEquals(fin, c.fin());
+                    assertEquals(rsv1, c.rsv1());
+                    assertEquals(rsv2, c.rsv2());
+                    assertEquals(rsv3, c.rsv3());
+                    assertEquals(opcode, c.opcode());
+                    assertEquals(mask.isPresent(), c.mask());
+                    assertEquals(payloadLen, c.payloadLen());
+                    assertEquals(mask, c.maskingKey());
+                    assertEquals(payloadLen == 0, c.isEndFrame());
+                });
+    }
+
+    /*
+     * Used to verify the order, the number of invocations as well as the
+     * arguments of each individual invocation to Frame.Consumer's methods.
+     */
+    private static class MockConsumer implements Frame.Consumer {
+
+        private int invocationOrder;
+
+        private Optional<Boolean> fin = Optional.empty();
+        private Optional<Boolean> rsv1 = Optional.empty();
+        private Optional<Boolean> rsv2 = Optional.empty();
+        private Optional<Boolean> rsv3 = Optional.empty();
+        private Optional<Opcode> opcode = Optional.empty();
+        private Optional<Boolean> mask = Optional.empty();
+        private OptionalLong payloadLen = OptionalLong.empty();
+        private OptionalInt maskingKey = OptionalInt.empty();
+
+        @Override
+        public void fin(boolean value) {
+            checkAndSetOrder(0, 1);
+            fin = Optional.of(value);
+        }
+
+        @Override
+        public void rsv1(boolean value) {
+            checkAndSetOrder(1, 2);
+            rsv1 = Optional.of(value);
+        }
+
+        @Override
+        public void rsv2(boolean value) {
+            checkAndSetOrder(2, 3);
+            rsv2 = Optional.of(value);
+        }
+
+        @Override
+        public void rsv3(boolean value) {
+            checkAndSetOrder(3, 4);
+            rsv3 = Optional.of(value);
+        }
+
+        @Override
+        public void opcode(Opcode value) {
+            checkAndSetOrder(4, 5);
+            opcode = Optional.of(value);
+        }
+
+        @Override
+        public void mask(boolean value) {
+            checkAndSetOrder(5, 6);
+            mask = Optional.of(value);
+        }
+
+        @Override
+        public void payloadLen(long value) {
+            checkAndSetOrder(p -> p == 5 || p == 6, n -> 7);
+            payloadLen = OptionalLong.of(value);
+        }
+
+        @Override
+        public void maskingKey(int value) {
+            checkAndSetOrder(7, 8);
+            maskingKey = of(value);
+        }
+
+        @Override
+        public void payloadData(ByteBuffer data) {
+            checkAndSetOrder(p -> p == 7 || p == 8, n -> 9);
+            assert payloadLen.isPresent();
+            if (payloadLen.getAsLong() != 0 && !data.hasRemaining()) {
+                throw new TestSupport.AssertionFailedException("Artefact of reading");
+            }
+        }
+
+        @Override
+        public void endFrame() {
+            checkAndSetOrder(9, 10);
+        }
+
+        boolean isEndFrame() {
+            return invocationOrder == 10;
+        }
+
+        public boolean fin() {
+            return fin.get();
+        }
+
+        public boolean rsv1() {
+            return rsv1.get();
+        }
+
+        public boolean rsv2() {
+            return rsv2.get();
+        }
+
+        public boolean rsv3() {
+            return rsv3.get();
+        }
+
+        public Opcode opcode() {
+            return opcode.get();
+        }
+
+        public boolean mask() {
+            return mask.get();
+        }
+
+        public long payloadLen() {
+            return payloadLen.getAsLong();
+        }
+
+        public OptionalInt maskingKey() {
+            return maskingKey;
+        }
+
+        private void checkAndSetOrder(int expectedValue, int newValue) {
+            checkAndSetOrder(p -> p == expectedValue, n -> newValue);
+        }
+
+        private void checkAndSetOrder(IntPredicate expectedValue,
+                                      IntUnaryOperator newValue) {
+            if (!expectedValue.test(invocationOrder)) {
+                throw new TestSupport.AssertionFailedException(
+                        expectedValue + " -> " + newValue);
+            }
+            invocationOrder = newValue.applyAsInt(invocationOrder);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/TestSupport.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,336 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.websocket;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Stack;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+import static java.util.List.of;
+import static java.util.Objects.requireNonNull;
+
+/*
+ * Auxiliary test infrastructure
+ */
+final class TestSupport {
+
+    private TestSupport() { }
+
+    static <A, B, R> Iterator<R> cartesianIterator(List<A> a,
+                                                   List<B> b,
+                                                   F2<A, B, R> f2) {
+        @SuppressWarnings("unchecked")
+        F<R> t = p -> f2.apply((A) p[0], (B) p[1]);
+        return cartesianIterator(of(a, b), t);
+    }
+
+    static <A, B, C, R> Iterator<R> cartesianIterator(List<A> a,
+                                                      List<B> b,
+                                                      List<C> c,
+                                                      F3<A, B, C, R> f3) {
+        @SuppressWarnings("unchecked")
+        F<R> t = p -> f3.apply((A) p[0], (B) p[1], (C) p[2]);
+        return cartesianIterator(of(a, b, c), t);
+    }
+
+    static <A, B, C, D, R> Iterator<R> cartesianIterator(List<A> a,
+                                                         List<B> b,
+                                                         List<C> c,
+                                                         List<D> d,
+                                                         F4<A, B, C, D, R> f4) {
+        @SuppressWarnings("unchecked")
+        F<R> t = p -> f4.apply((A) p[0], (B) p[1], (C) p[2], (D) p[3]);
+        return cartesianIterator(of(a, b, c, d), t);
+    }
+
+    static <A, B, C, D, E, R> Iterator<R> cartesianIterator(List<A> a,
+                                                            List<B> b,
+                                                            List<C> c,
+                                                            List<D> d,
+                                                            List<E> e,
+                                                            F5<A, B, C, D, E, R> f5) {
+        @SuppressWarnings("unchecked")
+        F<R> t = p -> f5.apply((A) p[0], (B) p[1], (C) p[2], (D) p[3], (E) p[4]);
+        return cartesianIterator(of(a, b, c, d, e), t);
+    }
+
+    static <R> Iterator<R> cartesianIterator(List<? extends List<?>> params,
+                                             F<R> function) {
+        if (params.isEmpty()) {
+            return Collections.emptyIterator();
+        }
+        for (List<?> l : params) {
+            if (l.isEmpty()) {
+                return Collections.emptyIterator();
+            }
+        }
+        // Assertion: if we are still here, there is at least a single element
+        // in the product
+        return new Iterator<>() {
+
+            private final int arity = params.size();
+            private final int[] coordinates = new int[arity];
+            private boolean hasNext = true;
+
+            @Override
+            public boolean hasNext() {
+                return hasNext;
+            }
+
+            @Override
+            public R next() {
+                if (!hasNext) {
+                    throw new NoSuchElementException();
+                }
+                Object[] array = new Object[arity];
+                for (int i = 0; i < arity; i++) {
+                    array[i] = params.get(i).get(coordinates[i]);
+                }
+                int p = arity - 1;
+                while (p >= 0 && coordinates[p] == params.get(p).size() - 1) {
+                    p--;
+                }
+                if (p < 0) {
+                    hasNext = false;
+                } else {
+                    coordinates[p]++;
+                    for (int i = p + 1; i < arity; i++) {
+                        coordinates[i] = 0;
+                    }
+                }
+                return function.apply(array);
+            }
+        };
+    }
+
+    @FunctionalInterface
+    public interface F1<A, R> {
+        R apply(A a);
+    }
+
+    @FunctionalInterface
+    public interface F2<A, B, R> {
+        R apply(A a, B b);
+    }
+
+    @FunctionalInterface
+    public interface F3<A, B, C, R> {
+        R apply(A a, B b, C c);
+    }
+
+    @FunctionalInterface
+    public interface F4<A, B, C, D, R> {
+        R apply(A a, B b, C c, D d);
+    }
+
+    @FunctionalInterface
+    public interface F5<A, B, C, D, E, R> {
+        R apply(A a, B b, C c, D d, E e);
+    }
+
+    @FunctionalInterface
+    public interface F<R> {
+        R apply(Object[] args);
+    }
+
+    static <T> Iterator<T> iteratorOf1(T element) {
+        return List.of(element).iterator();
+    }
+
+    @SafeVarargs
+    static <T> Iterator<T> iteratorOf(T... elements) {
+        return List.of(elements).iterator();
+    }
+
+    static <T> Iterator<T> limit(int maxElements, Iterator<? extends T> elements) {
+        return new Iterator<>() {
+
+            int count = maxElements;
+
+            @Override
+            public boolean hasNext() {
+                return count > 0 && elements.hasNext();
+            }
+
+            @Override
+            public T next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                count--;
+                return elements.next();
+            }
+        };
+    }
+
+    static ByteBuffer fullCopy(ByteBuffer src) {
+        ByteBuffer copy = ByteBuffer.allocate(src.capacity());
+        int p = src.position();
+        int l = src.limit();
+        src.clear();
+        copy.put(src).position(p).limit(l);
+        src.position(p).limit(l);
+        return copy;
+    }
+
+    static void forEachBufferPartition(ByteBuffer src,
+                                       Consumer<? super Iterable<? extends ByteBuffer>> action) {
+        forEachPartition(src.remaining(),
+                (lengths) -> {
+                    int end = src.position();
+                    List<ByteBuffer> buffers = new LinkedList<>();
+                    for (int len : lengths) {
+                        ByteBuffer d = src.duplicate();
+                        d.position(end);
+                        d.limit(end + len);
+                        end += len;
+                        buffers.add(d);
+                    }
+                    action.accept(buffers);
+                });
+    }
+
+    private static void forEachPartition(int n,
+                                         Consumer<? super Iterable<Integer>> action) {
+        forEachPartition(n, new Stack<>(), action);
+    }
+
+    private static void forEachPartition(int n,
+                                         Stack<Integer> path,
+                                         Consumer<? super Iterable<Integer>> action) {
+        if (n == 0) {
+            action.accept(path);
+        } else {
+            for (int i = 1; i <= n; i++) {
+                path.push(i);
+                forEachPartition(n - i, path, action);
+                path.pop();
+            }
+        }
+    }
+
+    static void forEachPermutation(int n, Consumer<? super int[]> c) {
+        int[] a = new int[n];
+        for (int i = 0; i < n; i++) {
+            a[i] = i;
+        }
+        permutations(0, a, c);
+    }
+
+    private static void permutations(int i, int[] a, Consumer<? super int[]> c) {
+        if (i == a.length) {
+            c.accept(Arrays.copyOf(a, a.length));
+            return;
+        }
+        for (int j = i; j < a.length; j++) {
+            swap(a, i, j);
+            permutations(i + 1, a, c);
+            swap(a, i, j);
+        }
+    }
+
+    private static void swap(int[] a, int i, int j) {
+        int x = a[i];
+        a[i] = a[j];
+        a[j] = x;
+    }
+
+    public static <T extends Throwable> T assertThrows(Class<? extends T> clazz,
+                                                       ThrowingProcedure code) {
+        @SuppressWarnings("unchecked")
+        T t = (T) assertThrows(clazz::isInstance, code);
+        return t;
+    }
+
+    /*
+     * The rationale behind asking for a regex is to not pollute variable names
+     * space in the scope of assertion: if it's something as simple as checking
+     * a message, we can do it inside
+     */
+    @SuppressWarnings("unchecked")
+    static <T extends Throwable> T assertThrows(Class<? extends T> clazz,
+                                                String messageRegex,
+                                                ThrowingProcedure code) {
+        requireNonNull(messageRegex, "messagePattern");
+        Predicate<Throwable> p = e -> clazz.isInstance(e)
+                && Pattern.matches(messageRegex, e.getMessage());
+        return (T) assertThrows(p, code);
+    }
+
+    static Throwable assertThrows(Predicate<? super Throwable> predicate,
+                                  ThrowingProcedure code) {
+        requireNonNull(predicate, "predicate");
+        requireNonNull(code, "code");
+        Throwable caught = null;
+        try {
+            code.run();
+        } catch (Throwable t) {
+            caught = t;
+        }
+        if (caught == null) {
+            throw new AssertionFailedException("No exception was thrown");
+        }
+        if (predicate.test(caught)) {
+            System.out.println("Got expected exception: " + caught);
+            return caught;
+        }
+        throw new AssertionFailedException("Caught exception didn't match the predicate", caught);
+    }
+
+    /*
+     * Blocking assertion, waits for completion
+     */
+    static Throwable assertCompletesExceptionally(Class<? extends Throwable> clazz,
+                                                  CompletionStage<?> stage) {
+        CompletableFuture<?> cf =
+                CompletableFuture.completedFuture(null).thenCompose(x -> stage);
+        return assertThrows(t -> clazz.isInstance(t.getCause()), cf::get);
+    }
+
+    interface ThrowingProcedure {
+        void run() throws Throwable;
+    }
+
+    static final class AssertionFailedException extends RuntimeException {
+
+        private static final long serialVersionUID = 1L;
+
+        AssertionFailedException(String message) {
+            super(message);
+        }
+
+        AssertionFailedException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/java.net.http/jdk/internal/net/http/websocket/WebSocketImplTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,453 @@
+/*
+ * 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.internal.net.http.websocket;
+
+import java.net.http.WebSocket;
+import org.testng.annotations.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static java.net.http.WebSocket.MessagePart.FIRST;
+import static java.net.http.WebSocket.MessagePart.LAST;
+import static java.net.http.WebSocket.MessagePart.PART;
+import static java.net.http.WebSocket.MessagePart.WHOLE;
+import static java.net.http.WebSocket.NORMAL_CLOSURE;
+import static jdk.internal.net.http.websocket.MockListener.Invocation.onClose;
+import static jdk.internal.net.http.websocket.MockListener.Invocation.onError;
+import static jdk.internal.net.http.websocket.MockListener.Invocation.onOpen;
+import static jdk.internal.net.http.websocket.MockListener.Invocation.onPing;
+import static jdk.internal.net.http.websocket.MockListener.Invocation.onPong;
+import static jdk.internal.net.http.websocket.MockListener.Invocation.onText;
+import static jdk.internal.net.http.websocket.MockTransport.onClose;
+import static jdk.internal.net.http.websocket.MockTransport.onPing;
+import static jdk.internal.net.http.websocket.MockTransport.onPong;
+import static jdk.internal.net.http.websocket.MockTransport.onText;
+import static jdk.internal.net.http.websocket.TestSupport.assertCompletesExceptionally;
+import static org.testng.Assert.assertEquals;
+
+/*
+ * Formatting in this file may seem strange:
+ *
+ *  (
+ *   ( ...)
+ *  ...
+ *  )
+ *  ...
+ *
+ *  However there is a rationale behind it. Sometimes the level of argument
+ *  nesting is high, which makes it hard to manage parentheses.
+ */
+public class WebSocketImplTest {
+
+    // TODO: request in onClose/onError
+    // TODO: throw exception in onClose/onError
+    // TODO: exception is thrown from request()
+    // TODO: repeated sendClose complete normally
+    // TODO: default Close message is sent if IAE is thrown from sendClose
+
+    @Test
+    public void testNonPositiveRequest() throws Exception {
+        MockListener listener = new MockListener(Long.MAX_VALUE) {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                webSocket.request(0);
+            }
+        };
+        WebSocket ws = newInstance(listener, List.of(now(onText("1", WHOLE))));
+        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
+        List<MockListener.Invocation> invocations = listener.invocations();
+        assertEquals(
+                invocations,
+                List.of(
+                        onOpen(ws),
+                        onError(ws, IllegalArgumentException.class)
+                )
+        );
+    }
+
+    @Test
+    public void testText1() throws Exception {
+        MockListener listener = new MockListener(Long.MAX_VALUE);
+        WebSocket ws = newInstance(
+                listener,
+                List.of(
+                        now(onText("1", FIRST)),
+                        now(onText("2", PART)),
+                        now(onText("3", LAST)),
+                        now(onClose(NORMAL_CLOSURE, "no reason"))
+                )
+        );
+        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
+        List<MockListener.Invocation> invocations = listener.invocations();
+        assertEquals(
+                invocations,
+                List.of(
+                        onOpen(ws),
+                        onText(ws, "1", FIRST),
+                        onText(ws, "2", PART),
+                        onText(ws, "3", LAST),
+                        onClose(ws, NORMAL_CLOSURE, "no reason")
+                )
+        );
+    }
+
+    @Test
+    public void testText2() throws Exception {
+        MockListener listener = new MockListener(Long.MAX_VALUE);
+        WebSocket ws = newInstance(
+                listener,
+                List.of(
+                        now(onText("1", FIRST)),
+                        seconds(1, onText("2", PART)),
+                        now(onText("3", LAST)),
+                        seconds(1, onClose(NORMAL_CLOSURE, "no reason"))
+                )
+        );
+        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
+        List<MockListener.Invocation> invocations = listener.invocations();
+        assertEquals(
+                invocations,
+                List.of(
+                        onOpen(ws),
+                        onText(ws, "1", FIRST),
+                        onText(ws, "2", PART),
+                        onText(ws, "3", LAST),
+                        onClose(ws, NORMAL_CLOSURE, "no reason")
+                )
+        );
+    }
+
+    @Test
+    public void testTextIntermixedWithPongs() throws Exception {
+        MockListener listener = new MockListener(Long.MAX_VALUE);
+        WebSocket ws = newInstance(
+                listener,
+                List.of(
+                        now(onText("1", FIRST)),
+                        now(onText("2", PART)),
+                        now(onPong(ByteBuffer.allocate(16))),
+                        seconds(1, onPong(ByteBuffer.allocate(32))),
+                        now(onText("3", LAST)),
+                        now(onPong(ByteBuffer.allocate(64))),
+                        now(onClose(NORMAL_CLOSURE, "no reason"))
+                )
+        );
+        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
+        List<MockListener.Invocation> invocations = listener.invocations();
+        assertEquals(
+                invocations,
+                List.of(
+                        onOpen(ws),
+                        onText(ws, "1", FIRST),
+                        onText(ws, "2", PART),
+                        onPong(ws, ByteBuffer.allocate(16)),
+                        onPong(ws, ByteBuffer.allocate(32)),
+                        onText(ws, "3", LAST),
+                        onPong(ws, ByteBuffer.allocate(64)),
+                        onClose(ws, NORMAL_CLOSURE, "no reason")
+                )
+        );
+    }
+
+    @Test
+    public void testTextIntermixedWithPings() throws Exception {
+        MockListener listener = new MockListener(Long.MAX_VALUE);
+        WebSocket ws = newInstance(
+                listener,
+                List.of(
+                        now(onText("1", FIRST)),
+                        now(onText("2", PART)),
+                        now(onPing(ByteBuffer.allocate(16))),
+                        seconds(1, onPing(ByteBuffer.allocate(32))),
+                        now(onText("3", LAST)),
+                        now(onPing(ByteBuffer.allocate(64))),
+                        now(onClose(NORMAL_CLOSURE, "no reason"))
+                )
+        );
+        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
+        List<MockListener.Invocation> invocations = listener.invocations();
+        assertEquals(
+                invocations,
+                List.of(
+                        onOpen(ws),
+                        onText(ws, "1", FIRST),
+                        onText(ws, "2", PART),
+                        onPing(ws, ByteBuffer.allocate(16)),
+                        onPing(ws, ByteBuffer.allocate(32)),
+                        onText(ws, "3", LAST),
+                        onPing(ws, ByteBuffer.allocate(64)),
+                        onClose(ws, NORMAL_CLOSURE, "no reason"))
+        );
+    }
+
+    // Tease out "java.lang.IllegalStateException: Send pending" due to possible
+    // race between sending a message and replenishing the permit
+    @Test
+    public void testManyTextMessages() {
+        WebSocketImpl ws = newInstance(
+                new MockListener(1),
+                new TransportFactory() {
+                    @Override
+                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                                            MessageStreamConsumer consumer) {
+
+                        final Random r = new Random();
+
+                        return new MockTransport<>(sendResultSupplier, consumer) {
+                            @Override
+                            protected CompletableFuture<T> defaultSend() {
+                                return millis(r.nextInt(100), result());
+                            }
+                        };
+                    }
+                });
+        int NUM_MESSAGES = 512;
+        CompletableFuture<WebSocket> current = CompletableFuture.completedFuture(ws);
+        for (int i = 0; i < NUM_MESSAGES; i++) {
+            current = current.thenCompose(w -> w.sendText(" ", true));
+        }
+        current.join();
+        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
+        assertEquals(transport.invocations().size(), NUM_MESSAGES);
+    }
+
+    @Test
+    public void testManyBinaryMessages() {
+        WebSocketImpl ws = newInstance(
+                new MockListener(1),
+                new TransportFactory() {
+                    @Override
+                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                                            MessageStreamConsumer consumer) {
+
+                        final Random r = new Random();
+
+                        return new MockTransport<>(sendResultSupplier, consumer) {
+                            @Override
+                            protected CompletableFuture<T> defaultSend() {
+                                return millis(r.nextInt(150), result());
+                            }
+                        };
+                    }
+                });
+        CompletableFuture<WebSocket> start = new CompletableFuture<>();
+
+        int NUM_MESSAGES = 512;
+        CompletableFuture<WebSocket> current = start;
+        for (int i = 0; i < NUM_MESSAGES; i++) {
+            current = current.thenComposeAsync(w -> w.sendBinary(ByteBuffer.allocate(1), true));
+        }
+
+        start.completeAsync(() -> ws);
+        current.join();
+
+        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
+        assertEquals(transport.invocations().size(), NUM_MESSAGES);
+    }
+
+
+    @Test
+    public void sendTextImmediately() {
+        WebSocketImpl ws = newInstance(
+                new MockListener(1),
+                new TransportFactory() {
+                    @Override
+                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                                            MessageStreamConsumer consumer) {
+                        return new MockTransport<>(sendResultSupplier, consumer);
+                    }
+                });
+        CompletableFuture.completedFuture(ws)
+                .thenCompose(w -> w.sendText("1", true))
+                .thenCompose(w -> w.sendText("2", true))
+                .thenCompose(w -> w.sendText("3", true))
+                .join();
+        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
+        assertEquals(transport.invocations().size(), 3);
+    }
+
+    @Test
+    public void sendTextWithDelay() {
+        MockListener listener = new MockListener(1);
+        WebSocketImpl ws = newInstance(
+                listener,
+                new TransportFactory() {
+                    @Override
+                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                                            MessageStreamConsumer consumer) {
+                        return new MockTransport<>(sendResultSupplier, consumer) {
+                            @Override
+                            protected CompletableFuture<T> defaultSend() {
+                                return seconds(1, result());
+                            }
+                        };
+                    }
+                });
+        CompletableFuture.completedFuture(ws)
+                .thenCompose(w -> w.sendText("1", true))
+                .thenCompose(w -> w.sendText("2", true))
+                .thenCompose(w -> w.sendText("3", true))
+                .join();
+        assertEquals(listener.invocations(), List.of(onOpen(ws)));
+        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
+        assertEquals(transport.invocations().size(), 3);
+    }
+
+    @Test
+    public void sendTextMixedDelay() {
+        MockListener listener = new MockListener(1);
+        WebSocketImpl ws = newInstance(
+                listener,
+                new TransportFactory() {
+
+                    final Random r = new Random();
+
+                    @Override
+                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                                            MessageStreamConsumer consumer) {
+                        return new MockTransport<>(sendResultSupplier, consumer) {
+                            @Override
+                            protected CompletableFuture<T> defaultSend() {
+                                return r.nextBoolean()
+                                        ? seconds(1, result())
+                                        : now(result());
+                            }
+                        };
+                    }
+                });
+        CompletableFuture.completedFuture(ws)
+                .thenCompose(w -> w.sendText("1", true))
+                .thenCompose(w -> w.sendText("2", true))
+                .thenCompose(w -> w.sendText("3", true))
+                .thenCompose(w -> w.sendText("4", true))
+                .thenCompose(w -> w.sendText("5", true))
+                .thenCompose(w -> w.sendText("6", true))
+                .thenCompose(w -> w.sendText("7", true))
+                .thenCompose(w -> w.sendText("8", true))
+                .thenCompose(w -> w.sendText("9", true))
+                .join();
+        assertEquals(listener.invocations(), List.of(onOpen(ws)));
+        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
+        assertEquals(transport.invocations().size(), 9);
+    }
+
+    @Test(enabled = false) // temporarily disabled
+    public void sendControlMessagesConcurrently() {
+        MockListener listener = new MockListener(1);
+
+        CompletableFuture<?> first = new CompletableFuture<>(); // barrier
+
+        WebSocketImpl ws = newInstance(
+                listener,
+                new TransportFactory() {
+
+                    final AtomicInteger i = new AtomicInteger();
+
+                    @Override
+                    public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                                            MessageStreamConsumer consumer) {
+                        return new MockTransport<>(sendResultSupplier, consumer) {
+                            @Override
+                            protected CompletableFuture<T> defaultSend() {
+                                if (i.incrementAndGet() == 1) {
+                                    return first.thenApply(o -> result());
+                                } else {
+                                    return now(result());
+                                }
+                            }
+                        };
+                    }
+                });
+
+        CompletableFuture<?> cf1 = ws.sendPing(ByteBuffer.allocate(0));
+        CompletableFuture<?> cf2 = ws.sendPong(ByteBuffer.allocate(0));
+        CompletableFuture<?> cf3 = ws.sendClose(NORMAL_CLOSURE, "");
+        CompletableFuture<?> cf4 = ws.sendClose(NORMAL_CLOSURE, "");
+        CompletableFuture<?> cf5 = ws.sendPing(ByteBuffer.allocate(0));
+        CompletableFuture<?> cf6 = ws.sendPong(ByteBuffer.allocate(0));
+
+        first.complete(null);
+        // Don't care about exceptional completion, only that all of them have
+        // completed
+        CompletableFuture.allOf(cf1, cf2, cf3, cf4, cf5, cf6)
+                .handle((v, e) -> null).join();
+
+        cf3.join(); /* Check that sendClose has completed normally */
+        cf4.join(); /* Check that repeated sendClose has completed normally */
+        assertCompletesExceptionally(IllegalStateException.class, cf5);
+        assertCompletesExceptionally(IllegalStateException.class, cf6);
+
+        assertEquals(listener.invocations(), List.of(onOpen(ws)));
+        MockTransport<WebSocket> transport = (MockTransport<WebSocket>) ws.transport();
+        assertEquals(transport.invocations().size(), 3); // 6 minus 3 that were not accepted
+    }
+
+    private static <T> CompletableFuture<T> seconds(long val, T result) {
+        return new CompletableFuture<T>()
+                .completeOnTimeout(result, val, TimeUnit.SECONDS);
+    }
+
+    private static <T> CompletableFuture<T> millis(long val, T result) {
+        return new CompletableFuture<T>()
+                .completeOnTimeout(result, val, TimeUnit.MILLISECONDS);
+    }
+
+    private static <T> CompletableFuture<T> now(T result) {
+        return CompletableFuture.completedFuture(result);
+    }
+
+    private static WebSocketImpl newInstance(
+            WebSocket.Listener listener,
+            Collection<CompletableFuture<Consumer<MessageStreamConsumer>>> input) {
+        TransportFactory factory = new TransportFactory() {
+            @Override
+            public <T> Transport<T> createTransport(Supplier<T> sendResultSupplier,
+                                                    MessageStreamConsumer consumer) {
+                return new MockTransport<T>(sendResultSupplier, consumer) {
+                    @Override
+                    protected Collection<CompletableFuture<Consumer<MessageStreamConsumer>>> receive() {
+                        return input;
+                    }
+                };
+            }
+        };
+        return newInstance(listener, factory);
+    }
+
+    private static WebSocketImpl newInstance(WebSocket.Listener listener,
+                                             TransportFactory factory) {
+        URI uri = URI.create("ws://localhost");
+        String subprotocol = "";
+        return WebSocketImpl.newInstance(uri, subprotocol, listener, factory);
+    }
+}
--- a/test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -27,9 +27,9 @@
  * @summary Verifies that the ConnectionPool correctly handle
  *          connection deadlines and purges the right connections
  *          from the cache.
- * @modules java.net.http/java.net.http.internal
+ * @modules java.net.http/jdk.internal.net.http
   *         java.management
  * @run main/othervm
  *       --add-reads java.net.http=java.management
- *       java.net.http/java.net.http.internal.ConnectionPoolTest
+ *       java.net.http/jdk.internal.net.http.ConnectionPoolTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/DemandTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/DemandTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -23,6 +23,6 @@
 
 /*
  * @test
- * @modules java.net.http/java.net.http.internal.common
- * @run testng java.net.http/java.net.http.internal.common.DemandTest
+ * @modules java.net.http/jdk.internal.net.http.common
+ * @run testng java.net.http/jdk.internal.net.http.common.DemandTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/Driver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/Driver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8151299 8164704
- * @modules java.net.http/java.net.http.internal
- * @run testng java.net.http/java.net.http.internal.SelectorTest
- * @run testng java.net.http/java.net.http.internal.RawChannelTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.SelectorTest
+ * @run testng java.net.http/jdk.internal.net.http.RawChannelTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/FlowTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/FlowTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -23,6 +23,6 @@
 
 /*
  * @test
- * @modules java.net.http
- * @run testng java.net.http/java.net.http.FlowTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.FlowTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/FramesDecoderTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/FramesDecoderTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,9 +24,9 @@
 /*
  * @test
  * @bug 8195823
- * @modules java.net.http/java.net.http.internal.frame
+ * @modules java.net.http/jdk.internal.net.http.frame
  * @run testng/othervm
  *       -Djdk.internal.httpclient.debug=true
- *       java.net.http/java.net.http.internal.frame.FramesDecoderTest
+ *       java.net.http/jdk.internal.net.http.frame.FramesDecoderTest
  */
 
--- a/test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -24,6 +24,6 @@
 /*
  * @test
  * @bug 8195138
- * @modules java.net.http/java.net.http.internal
- * @run testng java.net.http/java.net.http.internal.Http1HeaderParserTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.Http1HeaderParserTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/MinimalFutureTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/MinimalFutureTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -23,6 +23,6 @@
 
 /*
  * @test
- * @modules java.net.http/java.net.http.internal.common
- * @run testng java.net.http/java.net.http.internal.common.MinimalFutureTest
+ * @modules java.net.http/jdk.internal.net.http.common
+ * @run testng java.net.http/jdk.internal.net.http.common.MinimalFutureTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/SSLEchoTubeTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/SSLEchoTubeTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -23,6 +23,8 @@
 
 /*
  * @test
- * @modules java.net.http
- * @run testng/othervm -Djdk.internal.httpclient.debug=true java.net.http/java.net.http.SSLEchoTubeTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      java.net.http/jdk.internal.net.http.SSLEchoTubeTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/SSLTubeTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/SSLTubeTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -23,6 +23,8 @@
 
 /*
  * @test
- * @modules java.net.http
- * @run testng/othervm -Djdk.internal.httpclient.debug=true java.net.http/java.net.http.SSLTubeTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      java.net.http/jdk.internal.net.http.SSLTubeTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/WrapperTestDriver.java	Wed Feb 07 15:46:30 2018 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/WrapperTestDriver.java	Wed Feb 07 21:45:37 2018 +0000
@@ -23,6 +23,6 @@
 
 /*
  * @test
- * @modules java.net.http
- * @run testng java.net.http/java.net.http.WrapperTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.WrapperTest
  */
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/AbstractRandomTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +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 java.net.http;
-
-import java.util.Random;
-
-/** Abstract supertype for tests that need random numbers within a given range. */
-public class AbstractRandomTest {
-
-    private static Long getSystemSeed() {
-        Long seed = null;
-        try {
-            // note that Long.valueOf(null) also throws a NumberFormatException
-            // so if the property is undefined this will still work correctly
-            seed = Long.valueOf(System.getProperty("seed"));
-        } catch (NumberFormatException e) {
-            // do nothing: seed is still null
-        }
-        return seed;
-    }
-
-    private static long getSeed() {
-        Long seed = getSystemSeed();
-        if (seed == null) {
-            seed = (new Random()).nextLong();
-        }
-        System.out.println("Seed from AbstractRandomTest.getSeed = "+seed+"L");
-        return seed;
-    }
-
-    private static Random random = new Random(getSeed());
-
-    protected static int randomRange(int lower, int upper) {
-        if (lower > upper)
-            throw new IllegalArgumentException("lower > upper");
-        int diff = upper - lower;
-        int r = lower + random.nextInt(diff);
-        return r - (r % 8); // round down to multiple of 8 (align for longs)
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/AbstractSSLTubeTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,319 +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 java.net.http;
-
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.SSLTube;
-import java.net.http.internal.common.Utils;
-import org.testng.annotations.Test;
-
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.TrustManagerFactory;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.util.List;
-import java.util.StringTokenizer;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Flow;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.SubmissionPublisher;
-import java.util.concurrent.atomic.AtomicLong;
-
-public class AbstractSSLTubeTest extends AbstractRandomTest {
-
-    public static final long COUNTER = 600;
-    public static final int LONGS_PER_BUF = 800;
-    public static final long TOTAL_LONGS = COUNTER * LONGS_PER_BUF;
-    public static final ByteBuffer SENTINEL = ByteBuffer.allocate(0);
-    // This is a hack to work around an issue with SubmissionPublisher.
-    // SubmissionPublisher will call onComplete immediately without forwarding
-    // remaining pending data if SubmissionPublisher.close() is called when
-    // there is no demand. In other words, it doesn't wait for the subscriber
-    // to pull all the data before calling onComplete.
-    // We use a CountDownLatch to figure out when it is safe to call close().
-    // This may cause the test to hang if data are buffered.
-    protected final CountDownLatch allBytesReceived = new CountDownLatch(1);
-
-
-    protected static ByteBuffer getBuffer(long startingAt) {
-        ByteBuffer buf = ByteBuffer.allocate(LONGS_PER_BUF * 8);
-        for (int j = 0; j < LONGS_PER_BUF; j++) {
-            buf.putLong(startingAt++);
-        }
-        buf.flip();
-        return buf;
-    }
-
-    protected void run(FlowTube server,
-                       ExecutorService sslExecutor,
-                       CountDownLatch allBytesReceived) throws IOException {
-        FlowTube client = new SSLTube(createSSLEngine(true),
-                                      sslExecutor,
-                                      server);
-        SubmissionPublisher<List<ByteBuffer>> p =
-                new SubmissionPublisher<>(ForkJoinPool.commonPool(),
-                                          Integer.MAX_VALUE);
-        FlowTube.TubePublisher begin = p::subscribe;
-        CompletableFuture<Void> completion = new CompletableFuture<>();
-        EndSubscriber end = new EndSubscriber(TOTAL_LONGS, completion, allBytesReceived);
-        client.connectFlows(begin, end);
-        /* End of wiring */
-
-        long count = 0;
-        System.out.printf("Submitting %d buffer arrays\n", COUNTER);
-        System.out.printf("LoopCount should be %d\n", TOTAL_LONGS);
-        for (long i = 0; i < COUNTER; i++) {
-            ByteBuffer b = getBuffer(count);
-            count += LONGS_PER_BUF;
-            p.submit(List.of(b));
-        }
-        System.out.println("Finished submission. Waiting for loopback");
-        completion.whenComplete((r,t) -> allBytesReceived.countDown());
-        try {
-            allBytesReceived.await();
-        } catch (InterruptedException e) {
-            throw new IOException(e);
-        }
-        p.close();
-        System.out.println("All bytes received: calling publisher.close()");
-        try {
-            completion.join();
-            System.out.println("OK");
-        } finally {
-            sslExecutor.shutdownNow();
-        }
-    }
-
-    protected static void sleep(long millis) {
-        try {
-            Thread.sleep(millis);
-        } catch (InterruptedException e) {
-
-        }
-    }
-
-    /**
-     * The final subscriber which receives the decrypted looped-back data. Just
-     * needs to compare the data with what was sent. The given CF is either
-     * completed exceptionally with an error or normally on success.
-     */
-    protected static class EndSubscriber implements FlowTube.TubeSubscriber {
-
-        private static final int REQUEST_WINDOW = 13;
-
-        private final long nbytes;
-        private final AtomicLong counter = new AtomicLong();
-        private final CompletableFuture<?> completion;
-        private final CountDownLatch allBytesReceived;
-        private volatile Flow.Subscription subscription;
-        private long unfulfilled;
-
-        EndSubscriber(long nbytes, CompletableFuture<?> completion,
-                      CountDownLatch allBytesReceived) {
-            this.nbytes = nbytes;
-            this.completion = completion;
-            this.allBytesReceived = allBytesReceived;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            this.subscription = subscription;
-            unfulfilled = REQUEST_WINDOW;
-            System.out.println("EndSubscriber request " + REQUEST_WINDOW);
-            subscription.request(REQUEST_WINDOW);
-        }
-
-        public static String info(List<ByteBuffer> i) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("size: ").append(Integer.toString(i.size()));
-            int x = 0;
-            for (ByteBuffer b : i)
-                x += b.remaining();
-            sb.append(" bytes: ").append(x);
-            return sb.toString();
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> buffers) {
-            if (--unfulfilled == (REQUEST_WINDOW / 2)) {
-                long req = REQUEST_WINDOW - unfulfilled;
-                System.out.println("EndSubscriber request " + req);
-                unfulfilled = REQUEST_WINDOW;
-                subscription.request(req);
-            }
-
-            long currval = counter.get();
-            if (currval % 500 == 0) {
-                System.out.println("EndSubscriber: " + currval);
-            }
-            System.out.println("EndSubscriber onNext " + Utils.remaining(buffers));
-
-            for (ByteBuffer buf : buffers) {
-                while (buf.hasRemaining()) {
-                    long n = buf.getLong();
-                    if (currval > (TOTAL_LONGS - 50)) {
-                        System.out.println("End: " + currval);
-                    }
-                    if (n != currval++) {
-                        System.out.println("ERROR at " + n + " != " + (currval - 1));
-                        completion.completeExceptionally(new RuntimeException("ERROR"));
-                        subscription.cancel();
-                        return;
-                    }
-                }
-            }
-
-            counter.set(currval);
-            if (currval >= TOTAL_LONGS) {
-                allBytesReceived.countDown();
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            System.out.println("EndSubscriber onError " + throwable);
-            completion.completeExceptionally(throwable);
-            allBytesReceived.countDown();
-        }
-
-        @Override
-        public void onComplete() {
-            long n = counter.get();
-            if (n != nbytes) {
-                System.out.printf("nbytes=%d n=%d\n", nbytes, n);
-                completion.completeExceptionally(new RuntimeException("ERROR AT END"));
-            } else {
-                System.out.println("DONE OK");
-                completion.complete(null);
-            }
-            allBytesReceived.countDown();
-        }
-
-        @Override
-        public String toString() {
-            return "EndSubscriber";
-        }
-    }
-
-    protected static SSLEngine createSSLEngine(boolean client) throws IOException {
-        SSLContext context = (new SimpleSSLContext()).get();
-        SSLEngine engine = context.createSSLEngine();
-        SSLParameters params = context.getSupportedSSLParameters();
-        params.setProtocols(new String[]{"TLSv1.2"}); // TODO: This is essential. Needs to be protocol impl
-        if (client) {
-            params.setApplicationProtocols(new String[]{"proto1", "proto2"}); // server will choose proto2
-        } else {
-            params.setApplicationProtocols(new String[]{"proto2"}); // server will choose proto2
-        }
-        engine.setSSLParameters(params);
-        engine.setUseClientMode(client);
-        return engine;
-    }
-
-    /**
-     * Creates a simple usable SSLContext for SSLSocketFactory or a HttpsServer
-     * using either a given keystore or a default one in the test tree.
-     *
-     * Using this class with a security manager requires the following
-     * permissions to be granted:
-     *
-     * permission "java.util.PropertyPermission" "test.src.path", "read";
-     * permission java.io.FilePermission "${test.src}/../../../../lib/testlibrary/jdk/testlibrary/testkeys",
-     * "read"; The exact path above depends on the location of the test.
-     */
-    protected static class SimpleSSLContext {
-
-        private final SSLContext ssl;
-
-        /**
-         * Loads default keystore from SimpleSSLContext source directory
-         */
-        public SimpleSSLContext() throws IOException {
-            String paths = System.getProperty("test.src.path");
-            StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
-            boolean securityExceptions = false;
-            SSLContext sslContext = null;
-            while (st.hasMoreTokens()) {
-                String path = st.nextToken();
-                try {
-                    File f = new File(path, "../../../../lib/testlibrary/jdk/testlibrary/testkeys");
-                    if (f.exists()) {
-                        try (FileInputStream fis = new FileInputStream(f)) {
-                            sslContext = init(fis);
-                            break;
-                        }
-                    }
-                } catch (SecurityException e) {
-                    // catch and ignore because permission only required
-                    // for one entry on path (at most)
-                    securityExceptions = true;
-                }
-            }
-            if (securityExceptions) {
-                System.err.println("SecurityExceptions thrown on loading testkeys");
-            }
-            ssl = sslContext;
-        }
-
-        private SSLContext init(InputStream i) throws IOException {
-            try {
-                char[] passphrase = "passphrase".toCharArray();
-                KeyStore ks = KeyStore.getInstance("JKS");
-                ks.load(i, passphrase);
-
-                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
-                kmf.init(ks, passphrase);
-
-                TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
-                tmf.init(ks);
-
-                SSLContext ssl = SSLContext.getInstance("TLS");
-                ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
-                return ssl;
-            } catch (KeyManagementException | KeyStoreException |
-                    UnrecoverableKeyException | CertificateException |
-                    NoSuchAlgorithmException e) {
-                throw new RuntimeException(e.getMessage());
-            }
-        }
-
-        public SSLContext get() {
-            return ssl;
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/FlowTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,547 +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 java.net.http;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.util.List;
-import java.util.Random;
-import java.util.StringTokenizer;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.SubmissionPublisher;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.*;
-import javax.net.ssl.TrustManagerFactory;
-import java.net.http.internal.common.Utils;
-import org.testng.annotations.Test;
-import java.net.http.internal.common.SSLFlowDelegate;
-
-@Test
-public class FlowTest extends AbstractRandomTest {
-
-    private final SubmissionPublisher<List<ByteBuffer>> srcPublisher;
-    private final ExecutorService executor;
-    private static final long COUNTER = 3000;
-    private static final int LONGS_PER_BUF = 800;
-    static final long TOTAL_LONGS = COUNTER * LONGS_PER_BUF;
-    public static final ByteBuffer SENTINEL = ByteBuffer.allocate(0);
-    static volatile String alpn;
-
-    // This is a hack to work around an issue with SubmissionPublisher.
-    // SubmissionPublisher will call onComplete immediately without forwarding
-    // remaining pending data if SubmissionPublisher.close() is called when
-    // there is no demand. In other words, it doesn't wait for the subscriber
-    // to pull all the data before calling onComplete.
-    // We use a CountDownLatch to figure out when it is safe to call close().
-    // This may cause the test to hang if data are buffered.
-    final CountDownLatch allBytesReceived = new CountDownLatch(1);
-
-    private final CompletableFuture<Void> completion;
-
-    public FlowTest() throws IOException {
-        executor = Executors.newCachedThreadPool();
-        srcPublisher = new SubmissionPublisher<>(executor, 20,
-                                                 this::handlePublisherException);
-        SSLContext ctx = (new SimpleSSLContext()).get();
-        SSLEngine engineClient = ctx.createSSLEngine();
-        SSLParameters params = ctx.getSupportedSSLParameters();
-        params.setApplicationProtocols(new String[]{"proto1", "proto2"}); // server will choose proto2
-        params.setProtocols(new String[]{"TLSv1.2"}); // TODO: This is essential. Needs to be protocol impl
-        engineClient.setSSLParameters(params);
-        engineClient.setUseClientMode(true);
-        completion = new CompletableFuture<>();
-        SSLLoopbackSubscriber looper = new SSLLoopbackSubscriber(ctx, executor, allBytesReceived);
-        looper.start();
-        EndSubscriber end = new EndSubscriber(TOTAL_LONGS, completion, allBytesReceived);
-        SSLFlowDelegate sslClient = new SSLFlowDelegate(engineClient, executor, end, looper);
-        // going to measure how long handshake takes
-        final long start = System.currentTimeMillis();
-        sslClient.alpn().whenComplete((String s, Throwable t) -> {
-            if (t != null)
-                t.printStackTrace();
-            long endTime = System.currentTimeMillis();
-            alpn = s;
-            System.out.println("ALPN: " + alpn);
-            long period = (endTime - start);
-            System.out.printf("Handshake took %d ms\n", period);
-        });
-        Subscriber<List<ByteBuffer>> reader = sslClient.upstreamReader();
-        Subscriber<List<ByteBuffer>> writer = sslClient.upstreamWriter();
-        looper.setReturnSubscriber(reader);
-        // now connect all the pieces
-        srcPublisher.subscribe(writer);
-        String aa = sslClient.alpn().join();
-        System.out.println("AAALPN = " + aa);
-    }
-
-    private void handlePublisherException(Object o, Throwable t) {
-        System.out.println("Src Publisher exception");
-        t.printStackTrace(System.out);
-    }
-
-    private static ByteBuffer getBuffer(long startingAt) {
-        ByteBuffer buf = ByteBuffer.allocate(LONGS_PER_BUF * 8);
-        for (int j = 0; j < LONGS_PER_BUF; j++) {
-            buf.putLong(startingAt++);
-        }
-        buf.flip();
-        return buf;
-    }
-
-    @Test
-    public void run() {
-        long count = 0;
-        System.out.printf("Submitting %d buffer arrays\n", COUNTER);
-        System.out.printf("LoopCount should be %d\n", TOTAL_LONGS);
-        for (long i = 0; i < COUNTER; i++) {
-            ByteBuffer b = getBuffer(count);
-            count += LONGS_PER_BUF;
-            srcPublisher.submit(List.of(b));
-        }
-        System.out.println("Finished submission. Waiting for loopback");
-        // make sure we don't wait for allBytesReceived in case of error.
-        completion.whenComplete((r,t) -> allBytesReceived.countDown());
-        try {
-            allBytesReceived.await();
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-        System.out.println("All bytes received: ");
-        srcPublisher.close();
-        try {
-            completion.join();
-            if (!alpn.equals("proto2")) {
-                throw new RuntimeException("wrong alpn received");
-            }
-            System.out.println("OK");
-        } finally {
-            executor.shutdownNow();
-        }
-    }
-
-/*
-    public static void main(String[]args) throws Exception {
-        FlowTest test = new FlowTest();
-        test.run();
-    }
-*/
-
-    /**
-     * This Subscriber simulates an SSL loopback network. The object itself
-     * accepts outgoing SSL encrypted data which is looped back via two sockets
-     * (one of which is an SSLSocket emulating a server). The method
-     * {@link #setReturnSubscriber(java.util.concurrent.Flow.Subscriber) }
-     * is used to provide the Subscriber which feeds the incoming side
-     * of SSLFlowDelegate. Three threads are used to implement this behavior
-     * and a SubmissionPublisher drives the incoming read side.
-     * <p>
-     * A thread reads from the buffer, writes
-     * to the client j.n.Socket which is connected to a SSLSocket operating
-     * in server mode. A second thread loops back data read from the SSLSocket back to the
-     * client again. A third thread reads the client socket and pushes the data to
-     * a SubmissionPublisher that drives the reader side of the SSLFlowDelegate
-     */
-    static class SSLLoopbackSubscriber implements Subscriber<List<ByteBuffer>> {
-        private final BlockingQueue<ByteBuffer> buffer;
-        private final Socket clientSock;
-        private final SSLSocket serverSock;
-        private final Thread thread1, thread2, thread3;
-        private volatile Flow.Subscription clientSubscription;
-        private final SubmissionPublisher<List<ByteBuffer>> publisher;
-        private final CountDownLatch allBytesReceived;
-
-        SSLLoopbackSubscriber(SSLContext ctx,
-                              ExecutorService exec,
-                              CountDownLatch allBytesReceived) throws IOException {
-            SSLServerSocketFactory fac = ctx.getServerSocketFactory();
-            SSLServerSocket serv = (SSLServerSocket) fac.createServerSocket(0);
-            SSLParameters params = serv.getSSLParameters();
-            params.setApplicationProtocols(new String[]{"proto2"});
-            serv.setSSLParameters(params);
-
-
-            int serverPort = serv.getLocalPort();
-            clientSock = new Socket("127.0.0.1", serverPort);
-            serverSock = (SSLSocket) serv.accept();
-            this.buffer = new LinkedBlockingQueue<>();
-            this.allBytesReceived = allBytesReceived;
-            thread1 = new Thread(this::clientWriter, "clientWriter");
-            thread2 = new Thread(this::serverLoopback, "serverLoopback");
-            thread3 = new Thread(this::clientReader, "clientReader");
-            publisher = new SubmissionPublisher<>(exec, Flow.defaultBufferSize(),
-                    this::handlePublisherException);
-            SSLFlowDelegate.Monitor.add(this::monitor);
-        }
-
-        public void start() {
-            thread1.start();
-            thread2.start();
-            thread3.start();
-        }
-
-        private void handlePublisherException(Object o, Throwable t) {
-            System.out.println("Loopback Publisher exception");
-            t.printStackTrace(System.out);
-        }
-
-        private final AtomicInteger readCount = new AtomicInteger();
-
-        // reads off the SSLSocket the data from the "server"
-        private void clientReader() {
-            try {
-                InputStream is = clientSock.getInputStream();
-                final int bufsize = FlowTest.randomRange(512, 16 * 1024);
-                System.out.println("clientReader: bufsize = " + bufsize);
-                while (true) {
-                    byte[] buf = new byte[bufsize];
-                    int n = is.read(buf);
-                    if (n == -1) {
-                        System.out.println("clientReader close: read "
-                                + readCount.get() + " bytes");
-                        System.out.println("clientReader: got EOF. "
-                                            + "Waiting signal to close publisher.");
-                        allBytesReceived.await();
-                        System.out.println("clientReader: closing publisher");
-                        publisher.close();
-                        sleep(2000);
-                        Utils.close(is, clientSock);
-                        return;
-                    }
-                    ByteBuffer bb = ByteBuffer.wrap(buf, 0, n);
-                    readCount.addAndGet(n);
-                    publisher.submit(List.of(bb));
-                }
-            } catch (Throwable e) {
-                e.printStackTrace();
-                Utils.close(clientSock);
-            }
-        }
-
-        // writes the encrypted data from SSLFLowDelegate to the j.n.Socket
-        // which is connected to the SSLSocket emulating a server.
-        private void clientWriter() {
-            long nbytes = 0;
-            try {
-                OutputStream os =
-                        new BufferedOutputStream(clientSock.getOutputStream());
-
-                while (true) {
-                    ByteBuffer buf = buffer.take();
-                    if (buf == FlowTest.SENTINEL) {
-                        // finished
-                        //Utils.sleep(2000);
-                        System.out.println("clientWriter close: " + nbytes + " written");
-                        clientSock.shutdownOutput();
-                        System.out.println("clientWriter close return");
-                        return;
-                    }
-                    int len = buf.remaining();
-                    int written = writeToStream(os, buf);
-                    assert len == written;
-                    nbytes += len;
-                    assert !buf.hasRemaining()
-                            : "buffer has " + buf.remaining() + " bytes left";
-                    clientSubscription.request(1);
-                }
-            } catch (Throwable e) {
-                e.printStackTrace();
-            }
-        }
-
-        private int writeToStream(OutputStream os, ByteBuffer buf) throws IOException {
-            byte[] b = buf.array();
-            int offset = buf.arrayOffset() + buf.position();
-            int n = buf.limit() - buf.position();
-            os.write(b, offset, n);
-            buf.position(buf.limit());
-            os.flush();
-            return n;
-        }
-
-        private final AtomicInteger loopCount = new AtomicInteger();
-
-        public String monitor() {
-            return "serverLoopback: loopcount = " + loopCount.toString()
-                    + " clientRead: count = " + readCount.toString();
-        }
-
-        // thread2
-        private void serverLoopback() {
-            try {
-                InputStream is = serverSock.getInputStream();
-                OutputStream os = serverSock.getOutputStream();
-                final int bufsize = FlowTest.randomRange(512, 16 * 1024);
-                System.out.println("serverLoopback: bufsize = " + bufsize);
-                byte[] bb = new byte[bufsize];
-                while (true) {
-                    int n = is.read(bb);
-                    if (n == -1) {
-                        sleep(2000);
-                        is.close();
-                        serverSock.close();
-                        return;
-                    }
-                    os.write(bb, 0, n);
-                    os.flush();
-                    loopCount.addAndGet(n);
-                }
-            } catch (Throwable e) {
-                e.printStackTrace();
-            }
-        }
-
-
-        /**
-         * This needs to be called before the chain is subscribed. It can't be
-         * supplied in the constructor.
-         */
-        public void setReturnSubscriber(Subscriber<List<ByteBuffer>> returnSubscriber) {
-            publisher.subscribe(returnSubscriber);
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            clientSubscription = subscription;
-            clientSubscription.request(5);
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            try {
-                for (ByteBuffer b : item)
-                    buffer.put(b);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-                Utils.close(clientSock);
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            throwable.printStackTrace();
-            Utils.close(clientSock);
-        }
-
-        @Override
-        public void onComplete() {
-            try {
-                buffer.put(FlowTest.SENTINEL);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-                Utils.close(clientSock);
-            }
-        }
-    }
-
-    /**
-     * The final subscriber which receives the decrypted looped-back data.
-     * Just needs to compare the data with what was sent. The given CF is
-     * either completed exceptionally with an error or normally on success.
-     */
-    static class EndSubscriber implements Subscriber<List<ByteBuffer>> {
-
-        private final long nbytes;
-
-        private final AtomicLong counter;
-        private volatile Flow.Subscription subscription;
-        private final CompletableFuture<Void> completion;
-        private final CountDownLatch allBytesReceived;
-
-        EndSubscriber(long nbytes,
-                      CompletableFuture<Void> completion,
-                      CountDownLatch allBytesReceived) {
-            counter = new AtomicLong(0);
-            this.nbytes = nbytes;
-            this.completion = completion;
-            this.allBytesReceived = allBytesReceived;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            this.subscription = subscription;
-            subscription.request(5);
-        }
-
-        public static String info(List<ByteBuffer> i) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("size: ").append(Integer.toString(i.size()));
-            int x = 0;
-            for (ByteBuffer b : i)
-                x += b.remaining();
-            sb.append(" bytes: " + Integer.toString(x));
-            return sb.toString();
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> buffers) {
-            long currval = counter.get();
-            //if (currval % 500 == 0) {
-            //System.out.println("End: " + currval);
-            //}
-
-            for (ByteBuffer buf : buffers) {
-                while (buf.hasRemaining()) {
-                    long n = buf.getLong();
-                    //if (currval > (FlowTest.TOTAL_LONGS - 50)) {
-                    //System.out.println("End: " + currval);
-                    //}
-                    if (n != currval++) {
-                        System.out.println("ERROR at " + n + " != " + (currval - 1));
-                        completion.completeExceptionally(new RuntimeException("ERROR"));
-                        subscription.cancel();
-                        return;
-                    }
-                }
-            }
-
-            counter.set(currval);
-            subscription.request(1);
-            if (currval >= TOTAL_LONGS) {
-                allBytesReceived.countDown();
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            allBytesReceived.countDown();
-            completion.completeExceptionally(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            long n = counter.get();
-            if (n != nbytes) {
-                System.out.printf("nbytes=%d n=%d\n", nbytes, n);
-                completion.completeExceptionally(new RuntimeException("ERROR AT END"));
-            } else {
-                System.out.println("DONE OK: counter = " + n);
-                allBytesReceived.countDown();
-                completion.complete(null);
-            }
-        }
-    }
-
-    /**
-     * Creates a simple usable SSLContext for SSLSocketFactory
-     * or a HttpsServer using either a given keystore or a default
-     * one in the test tree.
-     * <p>
-     * Using this class with a security manager requires the following
-     * permissions to be granted:
-     * <p>
-     * permission "java.util.PropertyPermission" "test.src.path", "read";
-     * permission java.io.FilePermission
-     * "${test.src}/../../../../lib/testlibrary/jdk/testlibrary/testkeys", "read";
-     * The exact path above depends on the location of the test.
-     */
-    static class SimpleSSLContext {
-
-        private final SSLContext ssl;
-
-        /**
-         * Loads default keystore from SimpleSSLContext source directory
-         */
-        public SimpleSSLContext() throws IOException {
-            String paths = System.getProperty("test.src.path");
-            StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
-            boolean securityExceptions = false;
-            SSLContext sslContext = null;
-            while (st.hasMoreTokens()) {
-                String path = st.nextToken();
-                try {
-                    File f = new File(path, "../../../../lib/testlibrary/jdk/testlibrary/testkeys");
-                    if (f.exists()) {
-                        try (FileInputStream fis = new FileInputStream(f)) {
-                            sslContext = init(fis);
-                            break;
-                        }
-                    }
-                } catch (SecurityException e) {
-                    // catch and ignore because permission only required
-                    // for one entry on path (at most)
-                    securityExceptions = true;
-                }
-            }
-            if (securityExceptions) {
-                System.out.println("SecurityExceptions thrown on loading testkeys");
-            }
-            ssl = sslContext;
-        }
-
-        private SSLContext init(InputStream i) throws IOException {
-            try {
-                char[] passphrase = "passphrase".toCharArray();
-                KeyStore ks = KeyStore.getInstance("JKS");
-                ks.load(i, passphrase);
-
-                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
-                kmf.init(ks, passphrase);
-
-                TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
-                tmf.init(ks);
-
-                SSLContext ssl = SSLContext.getInstance("TLS");
-                ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
-                return ssl;
-            } catch (KeyManagementException | KeyStoreException |
-                    UnrecoverableKeyException | CertificateException |
-                    NoSuchAlgorithmException e) {
-                throw new RuntimeException(e.getMessage());
-            }
-        }
-
-        public SSLContext get() {
-            return ssl;
-        }
-    }
-
-    private static void sleep(int millis) {
-        try {
-            Thread.sleep(millis);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/SSLEchoTubeTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,420 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http;
-
-import java.net.http.internal.common.Demand;
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.SSLTube;
-import java.net.http.internal.common.SequentialScheduler;
-import java.net.http.internal.common.Utils;
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-
-@Test
-public class SSLEchoTubeTest extends AbstractSSLTubeTest {
-
-    @Test
-    public void runWithEchoServer() throws IOException {
-        ExecutorService sslExecutor = Executors.newCachedThreadPool();
-
-        /* Start of wiring */
-        /* Emulates an echo server */
-        FlowTube server = crossOverEchoServer(sslExecutor);
-
-        run(server, sslExecutor, allBytesReceived);
-    }
-
-    /**
-     * Creates a cross-over FlowTube than can be plugged into a client-side
-     * SSLTube (in place of the SSLLoopbackSubscriber).
-     * Note that the only method that can be called on the return tube
-     * is connectFlows(). Calling any other method will trigger an
-     * InternalError.
-     * @param sslExecutor an executor
-     * @return a cross-over FlowTube connected to an EchoTube.
-     * @throws IOException
-     */
-    private FlowTube crossOverEchoServer(Executor sslExecutor) throws IOException {
-        LateBindingTube crossOver = new LateBindingTube();
-        FlowTube server = new SSLTube(createSSLEngine(false),
-                                      sslExecutor,
-                                      crossOver);
-        EchoTube echo = new EchoTube(6);
-        server.connectFlows(FlowTube.asTubePublisher(echo), FlowTube.asTubeSubscriber(echo));
-
-        return new CrossOverTube(crossOver);
-    }
-
-    /**
-     * A cross-over FlowTube that makes it possible to reverse the direction
-     * of flows. The typical usage is to connect an two opposite SSLTube,
-     * one encrypting, one decrypting, to e.g. an EchoTube, with the help
-     * of a LateBindingTube:
-     * {@code
-     * client app => SSLTube => CrossOverTube <= LateBindingTube <= SSLTube <= EchoTube
-     * }
-     * <p>
-     * Note that the only method that can be called on the CrossOverTube is
-     * connectFlows(). Calling any other method will cause an InternalError to
-     * be thrown.
-     * Also connectFlows() can be called only once.
-     */
-    private static final class CrossOverTube implements FlowTube {
-        final LateBindingTube tube;
-        CrossOverTube(LateBindingTube tube) {
-            this.tube = tube;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            throw newInternalError();
-        }
-
-        @Override
-        public void connectFlows(TubePublisher writePublisher, TubeSubscriber readSubscriber) {
-            tube.start(writePublisher, readSubscriber);
-        }
-
-        @Override
-        public boolean isFinished() {
-            return tube.isFinished();
-        }
-
-        Error newInternalError() {
-            InternalError error = new InternalError();
-            error.printStackTrace(System.out);
-            return error;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            throw newInternalError();
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            throw newInternalError();
-        }
-
-        @Override
-        public void onComplete() {
-            throw newInternalError();
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            throw newInternalError();
-        }
-    }
-
-    /**
-     * A late binding tube that makes it possible to create an
-     * SSLTube before the right-hand-side tube has been created.
-     * The typical usage is to make it possible to connect two
-     * opposite SSLTube (one encrypting, one decrypting) through a
-     * CrossOverTube:
-     * {@code
-     * client app => SSLTube => CrossOverTube <= LateBindingTube <= SSLTube <= EchoTube
-     * }
-     * <p>
-     * Note that this class only supports a single call to start(): it cannot be
-     * subscribed more than once from its left-hand-side (the cross over tube side).
-     */
-    private static class LateBindingTube implements FlowTube {
-
-        final CompletableFuture<Flow.Publisher<List<ByteBuffer>>> futurePublisher
-                = new CompletableFuture<>();
-        final ConcurrentLinkedQueue<Consumer<Flow.Subscriber<? super List<ByteBuffer>>>> queue
-                = new ConcurrentLinkedQueue<>();
-        AtomicReference<Flow.Subscriber<? super List<ByteBuffer>>> subscriberRef = new AtomicReference<>();
-        SequentialScheduler scheduler = SequentialScheduler.synchronizedScheduler(this::loop);
-        AtomicReference<Throwable> errorRef = new AtomicReference<>();
-        private volatile boolean finished;
-        private volatile boolean completed;
-
-
-        public void start(Flow.Publisher<List<ByteBuffer>> publisher,
-                          Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            subscriberRef.set(subscriber);
-            futurePublisher.complete(publisher);
-            scheduler.runOrSchedule();
-        }
-
-        @Override
-        public boolean isFinished() {
-            return finished;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            futurePublisher.thenAccept((p) -> p.subscribe(subscriber));
-            scheduler.runOrSchedule();
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            queue.add((s) -> s.onSubscribe(subscription));
-            scheduler.runOrSchedule();
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            queue.add((s) -> s.onNext(item));
-            scheduler.runOrSchedule();
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            System.out.println("LateBindingTube onError");
-            throwable.printStackTrace(System.out);
-            queue.add((s) -> {
-                errorRef.compareAndSet(null, throwable);
-                try {
-                    System.out.println("LateBindingTube subscriber onError: " + throwable);
-                    s.onError(errorRef.get());
-                } finally {
-                    finished = true;
-                    System.out.println("LateBindingTube finished");
-                }
-            });
-            scheduler.runOrSchedule();
-        }
-
-        @Override
-        public void onComplete() {
-            System.out.println("LateBindingTube completing");
-            queue.add((s) -> {
-                completed = true;
-                try {
-                    System.out.println("LateBindingTube complete subscriber");
-                    s.onComplete();
-                } finally {
-                    finished = true;
-                    System.out.println("LateBindingTube finished");
-                }
-            });
-            scheduler.runOrSchedule();
-        }
-
-        private void loop() {
-            if (finished) {
-                scheduler.stop();
-                return;
-            }
-            Flow.Subscriber<? super List<ByteBuffer>> subscriber = subscriberRef.get();
-            if (subscriber == null) return;
-            try {
-                Consumer<Flow.Subscriber<? super List<ByteBuffer>>> s;
-                while ((s = queue.poll()) != null) {
-                    s.accept(subscriber);
-                }
-            } catch (Throwable t) {
-                if (errorRef.compareAndSet(null, t)) {
-                    onError(t);
-                }
-            }
-        }
-    }
-
-    /**
-     * An echo tube that just echoes back whatever bytes it receives.
-     * This cannot be plugged to the right-hand-side of an SSLTube
-     * since handshake data cannot be simply echoed back, and
-     * application data most likely also need to be decrypted and
-     * re-encrypted.
-     */
-    private static final class EchoTube implements FlowTube {
-
-        private final static Object EOF = new Object();
-        private final Executor executor = Executors.newSingleThreadExecutor();
-
-        private final Queue<Object> queue = new ConcurrentLinkedQueue<>();
-        private final int maxQueueSize;
-        private final SequentialScheduler processingScheduler =
-                new SequentialScheduler(createProcessingTask());
-
-        /* Writing into this tube */
-        private volatile long requested;
-        private Flow.Subscription subscription;
-
-        /* Reading from this tube */
-        private final Demand demand = new Demand();
-        private final AtomicBoolean cancelled = new AtomicBoolean();
-        private Flow.Subscriber<? super List<ByteBuffer>> subscriber;
-
-        private EchoTube(int maxBufferSize) {
-            if (maxBufferSize < 1)
-                throw new IllegalArgumentException();
-            this.maxQueueSize = maxBufferSize;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            this.subscriber = subscriber;
-            System.out.println("EchoTube got subscriber: " + subscriber);
-            this.subscriber.onSubscribe(new InternalSubscription());
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            System.out.println("EchoTube request: " + maxQueueSize);
-            (this.subscription = subscription).request(requested = maxQueueSize);
-        }
-
-        private void requestMore() {
-            Flow.Subscription s = subscription;
-            if (s == null || cancelled.get()) return;
-            long unfulfilled = queue.size() + --requested;
-            if (unfulfilled <= maxQueueSize/2) {
-                long req = maxQueueSize - unfulfilled;
-                requested += req;
-                s.request(req);
-                System.out.printf("EchoTube request: %s [requested:%s, queue:%s, unfulfilled:%s]%n",
-                        req, requested-req, queue.size(), unfulfilled );
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            System.out.printf("EchoTube add %s [requested:%s, queue:%s]%n",
-                    Utils.remaining(item), requested, queue.size());
-            queue.add(item);
-            processingScheduler.runOrSchedule(executor);
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            System.out.println("EchoTube add " + throwable);
-            queue.add(throwable);
-            processingScheduler.runOrSchedule(executor);
-        }
-
-        @Override
-        public void onComplete() {
-            System.out.println("EchoTube add EOF");
-            queue.add(EOF);
-            processingScheduler.runOrSchedule(executor);
-        }
-
-        @Override
-        public boolean isFinished() {
-            return cancelled.get();
-        }
-
-        private class InternalSubscription implements Flow.Subscription {
-
-            @Override
-            public void request(long n) {
-                System.out.println("EchoTube got request: " + n);
-                if (n <= 0) {
-                    throw new InternalError();
-                }
-                if (demand.increase(n)) {
-                    processingScheduler.runOrSchedule(executor);
-                }
-            }
-
-            @Override
-            public void cancel() {
-                cancelled.set(true);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "EchoTube";
-        }
-
-        int transmitted = 0;
-        private SequentialScheduler.RestartableTask createProcessingTask() {
-            return new SequentialScheduler.CompleteRestartableTask() {
-
-                @Override
-                protected void run() {
-                    try {
-                        while (!cancelled.get()) {
-                            Object item = queue.peek();
-                            if (item == null) {
-                                System.out.printf("EchoTube: queue empty, requested=%s, demand=%s, transmitted=%s%n",
-                                        requested, demand.get(), transmitted);
-                                requestMore();
-                                return;
-                            }
-                            try {
-                                System.out.printf("EchoTube processing item, requested=%s, demand=%s, transmitted=%s%n",
-                                        requested, demand.get(), transmitted);
-                                if (item instanceof List) {
-                                    if (!demand.tryDecrement()) {
-                                        System.out.println("EchoTube no demand");
-                                        return;
-                                    }
-                                    @SuppressWarnings("unchecked")
-                                    List<ByteBuffer> bytes = (List<ByteBuffer>) item;
-                                    Object removed = queue.remove();
-                                    assert removed == item;
-                                    System.out.println("EchoTube processing "
-                                            + Utils.remaining(bytes));
-                                    transmitted++;
-                                    subscriber.onNext(bytes);
-                                    requestMore();
-                                } else if (item instanceof Throwable) {
-                                    cancelled.set(true);
-                                    Object removed = queue.remove();
-                                    assert removed == item;
-                                    System.out.println("EchoTube processing " + item);
-                                    subscriber.onError((Throwable) item);
-                                } else if (item == EOF) {
-                                    cancelled.set(true);
-                                    Object removed = queue.remove();
-                                    assert removed == item;
-                                    System.out.println("EchoTube processing EOF");
-                                    subscriber.onComplete();
-                                } else {
-                                    throw new InternalError(String.valueOf(item));
-                                }
-                            } finally {
-                            }
-                        }
-                    } catch(Throwable t) {
-                        t.printStackTrace();
-                        throw t;
-                    }
-                }
-            };
-        }
-    }
- }
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/SSLTubeTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,275 +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 java.net.http;
-
-import java.net.http.internal.common.FlowTube;
-import java.net.http.internal.common.SSLFlowDelegate;
-import java.net.http.internal.common.Utils;
-import org.testng.annotations.Test;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLServerSocketFactory;
-import javax.net.ssl.SSLSocket;
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Flow;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.SubmissionPublisher;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@Test
-public class SSLTubeTest extends AbstractSSLTubeTest {
-
-    @Test
-    public void runWithSSLLoopackServer() throws IOException {
-        ExecutorService sslExecutor = Executors.newCachedThreadPool();
-
-        /* Start of wiring */
-        /* Emulates an echo server */
-        SSLLoopbackSubscriber server =
-                new SSLLoopbackSubscriber((new SimpleSSLContext()).get(),
-                        sslExecutor,
-                        allBytesReceived);
-        server.start();
-
-        run(server, sslExecutor, allBytesReceived);
-    }
-
-    /**
-     * This is a copy of the SSLLoopbackSubscriber used in FlowTest
-     */
-    private static class SSLLoopbackSubscriber implements FlowTube {
-        private final BlockingQueue<ByteBuffer> buffer;
-        private final Socket clientSock;
-        private final SSLSocket serverSock;
-        private final Thread thread1, thread2, thread3;
-        private volatile Flow.Subscription clientSubscription;
-        private final SubmissionPublisher<List<ByteBuffer>> publisher;
-        private final CountDownLatch allBytesReceived;
-
-        SSLLoopbackSubscriber(SSLContext ctx,
-                              ExecutorService exec,
-                              CountDownLatch allBytesReceived) throws IOException {
-            SSLServerSocketFactory fac = ctx.getServerSocketFactory();
-            SSLServerSocket serv = (SSLServerSocket) fac.createServerSocket(0);
-            SSLParameters params = serv.getSSLParameters();
-            params.setApplicationProtocols(new String[]{"proto2"});
-            serv.setSSLParameters(params);
-
-
-            int serverPort = serv.getLocalPort();
-            clientSock = new Socket("127.0.0.1", serverPort);
-            serverSock = (SSLSocket) serv.accept();
-            this.buffer = new LinkedBlockingQueue<>();
-            this.allBytesReceived = allBytesReceived;
-            thread1 = new Thread(this::clientWriter, "clientWriter");
-            thread2 = new Thread(this::serverLoopback, "serverLoopback");
-            thread3 = new Thread(this::clientReader, "clientReader");
-            publisher = new SubmissionPublisher<>(exec, Flow.defaultBufferSize(),
-                    this::handlePublisherException);
-            SSLFlowDelegate.Monitor.add(this::monitor);
-        }
-
-        public void start() {
-            thread1.start();
-            thread2.start();
-            thread3.start();
-        }
-
-        private void handlePublisherException(Object o, Throwable t) {
-            System.out.println("Loopback Publisher exception");
-            t.printStackTrace(System.out);
-        }
-
-        private final AtomicInteger readCount = new AtomicInteger();
-
-        // reads off the SSLSocket the data from the "server"
-        private void clientReader() {
-            try {
-                InputStream is = clientSock.getInputStream();
-                final int bufsize = randomRange(512, 16 * 1024);
-                System.out.println("clientReader: bufsize = " + bufsize);
-                while (true) {
-                    byte[] buf = new byte[bufsize];
-                    int n = is.read(buf);
-                    if (n == -1) {
-                        System.out.println("clientReader close: read "
-                                + readCount.get() + " bytes");
-                        System.out.println("clientReader: waiting signal to close publisher");
-                        allBytesReceived.await();
-                        System.out.println("clientReader: closing publisher");
-                        publisher.close();
-                        sleep(2000);
-                        Utils.close(is, clientSock);
-                        return;
-                    }
-                    ByteBuffer bb = ByteBuffer.wrap(buf, 0, n);
-                    readCount.addAndGet(n);
-                    publisher.submit(List.of(bb));
-                }
-            } catch (Throwable e) {
-                e.printStackTrace();
-                Utils.close(clientSock);
-            }
-        }
-
-        // writes the encrypted data from SSLFLowDelegate to the j.n.Socket
-        // which is connected to the SSLSocket emulating a server.
-        private void clientWriter() {
-            long nbytes = 0;
-            try {
-                OutputStream os =
-                        new BufferedOutputStream(clientSock.getOutputStream());
-
-                while (true) {
-                    ByteBuffer buf = buffer.take();
-                    if (buf == SENTINEL) {
-                        // finished
-                        //Utils.sleep(2000);
-                        System.out.println("clientWriter close: " + nbytes + " written");
-                        clientSock.shutdownOutput();
-                        System.out.println("clientWriter close return");
-                        return;
-                    }
-                    int len = buf.remaining();
-                    int written = writeToStream(os, buf);
-                    assert len == written;
-                    nbytes += len;
-                    assert !buf.hasRemaining()
-                            : "buffer has " + buf.remaining() + " bytes left";
-                    clientSubscription.request(1);
-                }
-            } catch (Throwable e) {
-                e.printStackTrace();
-            }
-        }
-
-        private int writeToStream(OutputStream os, ByteBuffer buf) throws IOException {
-            byte[] b = buf.array();
-            int offset = buf.arrayOffset() + buf.position();
-            int n = buf.limit() - buf.position();
-            os.write(b, offset, n);
-            buf.position(buf.limit());
-            os.flush();
-            return n;
-        }
-
-        private final AtomicInteger loopCount = new AtomicInteger();
-
-        public String monitor() {
-            return "serverLoopback: loopcount = " + loopCount.toString()
-                    + " clientRead: count = " + readCount.toString();
-        }
-
-        // thread2
-        private void serverLoopback() {
-            try {
-                InputStream is = serverSock.getInputStream();
-                OutputStream os = serverSock.getOutputStream();
-                final int bufsize = randomRange(512, 16 * 1024);
-                System.out.println("serverLoopback: bufsize = " + bufsize);
-                byte[] bb = new byte[bufsize];
-                while (true) {
-                    int n = is.read(bb);
-                    if (n == -1) {
-                        sleep(2000);
-                        is.close();
-                        os.close();
-                        serverSock.close();
-                        return;
-                    }
-                    os.write(bb, 0, n);
-                    os.flush();
-                    loopCount.addAndGet(n);
-                }
-            } catch (Throwable e) {
-                e.printStackTrace();
-            }
-        }
-
-
-        /**
-         * This needs to be called before the chain is subscribed. It can't be
-         * supplied in the constructor.
-         */
-        public void setReturnSubscriber(Flow.Subscriber<List<ByteBuffer>> returnSubscriber) {
-            publisher.subscribe(returnSubscriber);
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            clientSubscription = subscription;
-            clientSubscription.request(5);
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            try {
-                for (ByteBuffer b : item)
-                    buffer.put(b);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-                Utils.close(clientSock);
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            throwable.printStackTrace();
-            Utils.close(clientSock);
-        }
-
-        @Override
-        public void onComplete() {
-            try {
-                buffer.put(SENTINEL);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-                Utils.close(clientSock);
-            }
-        }
-
-        @Override
-        public boolean isFinished() {
-            return false;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            publisher.subscribe(subscriber);
-        }
-    }
-
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/WrapperTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,263 +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 java.net.http;
-
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.*;
-import org.testng.annotations.Test;
-import java.net.http.internal.common.SubscriberWrapper;
-
-@Test
-public class WrapperTest {
-    static final int LO_PRI = 1;
-    static final int HI_PRI = 2;
-    static final int NUM_HI_PRI = 240;
-    static final int BUFSIZE = 1016;
-    static final int BUFSIZE_INT = BUFSIZE/4;
-    static final int HI_PRI_FREQ = 40;
-
-    static final int TOTAL = 10000;
-    //static final int TOTAL = 500;
-
-    final SubmissionPublisher<List<ByteBuffer>> publisher;
-    final SubscriberWrapper sub1, sub2, sub3;
-    final ExecutorService executor = Executors.newCachedThreadPool();
-    volatile int hipricount = 0;
-
-    void errorHandler(Flow.Subscriber<? super List<ByteBuffer>> sub, Throwable t) {
-        System.err.printf("Exception from %s : %s\n", sub.toString(), t.toString());
-    }
-
-    public WrapperTest() {
-        publisher = new SubmissionPublisher<>(executor, 600,
-                (a, b) -> {
-                    errorHandler(a, b);
-                });
-
-        CompletableFuture<Void> notif = new CompletableFuture<>();
-        LastSubscriber ls = new LastSubscriber(notif);
-        sub1 = new Filter1(ls);
-        sub2 = new Filter2(sub1);
-        sub3 = new Filter2(sub2);
-    }
-
-    public class Filter2 extends SubscriberWrapper {
-        Filter2(SubscriberWrapper wrapper) {
-            super(wrapper);
-        }
-
-        // reverse the order of the bytes in each buffer
-        public void incoming(List<ByteBuffer> list, boolean complete) {
-            List<ByteBuffer> out = new LinkedList<>();
-            for (ByteBuffer inbuf : list) {
-                int size = inbuf.remaining();
-                ByteBuffer outbuf = ByteBuffer.allocate(size);
-                for (int i=size; i>0; i--) {
-                    byte b = inbuf.get(i-1);
-                    outbuf.put(b);
-                }
-                outbuf.flip();
-                out.add(outbuf);
-            }
-            if (complete) System.out.println("Filter2.complete");
-            outgoing(out, complete);
-        }
-
-        protected long windowUpdate(long currval) {
-            return currval == 0 ? 1 : 0;
-        }
-    }
-
-    volatile int filter1Calls = 0; // every third call we insert hi pri data
-
-    ByteBuffer getHiPri(int val) {
-        ByteBuffer buf = ByteBuffer.allocate(8);
-        buf.putInt(HI_PRI);
-        buf.putInt(val);
-        buf.flip();
-        return buf;
-    }
-
-    volatile int hiPriAdded = 0;
-
-    public class Filter1 extends SubscriberWrapper {
-        Filter1(Flow.Subscriber<List<ByteBuffer>> downstreamSubscriber)
-        {
-            super();
-            subscribe(downstreamSubscriber);
-        }
-
-        // Inserts up to NUM_HI_PRI hi priority buffers into flow
-        protected void incoming(List<ByteBuffer> in, boolean complete) {
-            if ((++filter1Calls % HI_PRI_FREQ) == 0 && (hiPriAdded++ < NUM_HI_PRI)) {
-                sub1.outgoing(getHiPri(hipricount++), false);
-            }
-            // pass data thru
-            if (complete) System.out.println("Filter1.complete");
-            outgoing(in, complete);
-        }
-
-        protected long windowUpdate(long currval) {
-            return currval == 0 ? 1 : 0;
-        }
-    }
-
-    /**
-     * Final subscriber in the chain. Compares the data sent by the original
-     * publisher.
-     */
-    static public class LastSubscriber implements Flow.Subscriber<List<ByteBuffer>> {
-        volatile Flow.Subscription subscription;
-        volatile int hipriCounter=0;
-        volatile int lopriCounter=0;
-        final CompletableFuture<Void> cf;
-
-        LastSubscriber(CompletableFuture<Void> cf) {
-            this.cf = cf;
-        }
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            this.subscription = subscription;
-            subscription.request(50); // say
-        }
-
-        private void error(String...args) {
-            StringBuilder sb = new StringBuilder();
-            for (String s : args) {
-                sb.append(s);
-                sb.append(' ');
-            }
-            String msg = sb.toString();
-            System.out.println("Error: " + msg);
-            RuntimeException e = new RuntimeException(msg);
-            cf.completeExceptionally(e);
-            subscription.cancel(); // This is where we need a variant that include exception
-        }
-
-        private void check(ByteBuffer buf) {
-            int type = buf.getInt();
-            if (type == HI_PRI) {
-                // check next int is hi pri counter
-                int c = buf.getInt();
-                if (c != hipriCounter)
-                    error("hi pri counter", Integer.toString(c), Integer.toString(hipriCounter));
-                hipriCounter++;
-            } else {
-                while (buf.hasRemaining()) {
-                    if (buf.getInt() != lopriCounter)
-                        error("lo pri counter", Integer.toString(lopriCounter));
-                    lopriCounter++;
-                }
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> items) {
-            for (ByteBuffer item : items)
-                check(item);
-            subscription.request(1);
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            error(throwable.getMessage());
-        }
-
-        @Override
-        public void onComplete() {
-            if (hipriCounter != NUM_HI_PRI)
-                error("hi pri at end wrong", Integer.toString(hipriCounter), Integer.toString(NUM_HI_PRI));
-            else {
-                System.out.println("LastSubscriber.complete");
-                cf.complete(null); // success
-            }
-        }
-    }
-
-    List<ByteBuffer> getBuffer(int c) {
-        ByteBuffer buf = ByteBuffer.allocate(BUFSIZE+4);
-        buf.putInt(LO_PRI);
-        for (int i=0; i<BUFSIZE_INT; i++) {
-            buf.putInt(c++);
-        }
-        buf.flip();
-        return List.of(buf);
-    }
-
-    boolean errorTest = false;
-
-    @Test
-    public void run() throws InterruptedException {
-        try {
-            CompletableFuture<Void> completion = sub3.completion();
-            publisher.subscribe(sub3);
-            // now submit a load of data
-            int counter = 0;
-            for (int i = 0; i < TOTAL; i++) {
-                List<ByteBuffer> bufs = getBuffer(counter);
-                //if (i==2)
-                    //bufs.get(0).putInt(41, 1234); // error
-                counter += BUFSIZE_INT;
-                publisher.submit(bufs);
-                //if (i % 1000 == 0)
-                    //Thread.sleep(1000);
-                //if (i == 99) {
-                    //publisher.closeExceptionally(new RuntimeException("Test error"));
-                    //errorTest = true;
-                    //break;
-                //}
-            }
-            if (!errorTest) {
-                publisher.close();
-            }
-            System.out.println("Publisher completed");
-            completion.join();
-            System.out.println("Subscribers completed ok");
-        } finally {
-            executor.shutdownNow();
-        }
-    }
-
-    static void display(CompletableFuture<?> cf) {
-        System.out.print (cf);
-        if (!cf.isDone())
-            return;
-        try {
-            cf.join(); // wont block
-        } catch (Exception e) {
-            System.out.println(" " + e);
-        }
-    }
-
-/*
-    public static void main(String[] args) throws InterruptedException {
-        WrapperTest test = new WrapperTest();
-        test.run();
-    }
-*/
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/ConnectionPoolTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.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 java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.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/java.net.http/java/net/http/internal/Http1HeaderParserTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,379 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.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 */ }
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/RawChannelTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,333 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.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 java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.internal.websocket.RawChannel;
-import java.net.http.internal.websocket.WebSocketRequest;
-import org.testng.annotations.Test;
-import static java.net.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());
-            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/java.net.http/java/net/http/internal/SelectorTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,224 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.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 java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import org.testng.annotations.Test;
-import java.net.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 java.net.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());
-        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);
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/common/DemandTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +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 java.net.http.internal.common;
-
-import org.testng.annotations.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
-public class DemandTest {
-
-    @Test
-    public void test01() {
-        assertTrue(new Demand().isFulfilled());
-    }
-
-    @Test
-    public void test011() {
-        Demand d = new Demand();
-        d.increase(3);
-        d.decreaseAndGet(3);
-        assertTrue(d.isFulfilled());
-    }
-
-    @Test
-    public void test02() {
-        Demand d = new Demand();
-        d.increase(1);
-        assertFalse(d.isFulfilled());
-    }
-
-    @Test
-    public void test03() {
-        Demand d = new Demand();
-        d.increase(3);
-        assertEquals(d.decreaseAndGet(3), 3);
-    }
-
-    @Test
-    public void test04() {
-        Demand d = new Demand();
-        d.increase(3);
-        assertEquals(d.decreaseAndGet(5), 3);
-    }
-
-    @Test
-    public void test05() {
-        Demand d = new Demand();
-        d.increase(7);
-        assertEquals(d.decreaseAndGet(4), 4);
-    }
-
-    @Test
-    public void test06() {
-        Demand d = new Demand();
-        assertEquals(d.decreaseAndGet(3), 0);
-    }
-
-    @Test(expectedExceptions = IllegalArgumentException.class)
-    public void test07() {
-        Demand d = new Demand();
-        d.increase(0);
-    }
-
-    @Test(expectedExceptions = IllegalArgumentException.class)
-    public void test08() {
-        Demand d = new Demand();
-        d.increase(-1);
-    }
-
-    @Test(expectedExceptions = IllegalArgumentException.class)
-    public void test09() {
-        Demand d = new Demand();
-        d.increase(10);
-        d.decreaseAndGet(0);
-    }
-
-    @Test(expectedExceptions = IllegalArgumentException.class)
-    public void test10() {
-        Demand d = new Demand();
-        d.increase(13);
-        d.decreaseAndGet(-3);
-    }
-
-    @Test
-    public void test11() {
-        Demand d = new Demand();
-        d.increase(1);
-        assertTrue(d.tryDecrement());
-    }
-
-    @Test
-    public void test12() {
-        Demand d = new Demand();
-        d.increase(2);
-        assertTrue(d.tryDecrement());
-    }
-
-    @Test
-    public void test14() {
-        Demand d = new Demand();
-        assertFalse(d.tryDecrement());
-    }
-
-    @Test
-    public void test141() {
-        Demand d = new Demand();
-        d.increase(Long.MAX_VALUE);
-        assertFalse(d.isFulfilled());
-    }
-
-    @Test
-    public void test142() {
-        Demand d = new Demand();
-        d.increase(Long.MAX_VALUE);
-        d.increase(1);
-        assertFalse(d.isFulfilled());
-    }
-
-    @Test
-    public void test143() {
-        Demand d = new Demand();
-        d.increase(Long.MAX_VALUE);
-        d.increase(1);
-        assertFalse(d.isFulfilled());
-    }
-
-    @Test
-    public void test144() {
-        Demand d = new Demand();
-        d.increase(Long.MAX_VALUE);
-        d.increase(Long.MAX_VALUE);
-        d.decreaseAndGet(3);
-        d.decreaseAndGet(5);
-        assertFalse(d.isFulfilled());
-    }
-
-    @Test
-    public void test145() {
-        Demand d = new Demand();
-        d.increase(Long.MAX_VALUE);
-        d.decreaseAndGet(Long.MAX_VALUE);
-        assertTrue(d.isFulfilled());
-    }
-
-    @Test(invocationCount = 32)
-    public void test15() throws InterruptedException {
-        int N = Math.max(2, Runtime.getRuntime().availableProcessors() + 1);
-        int M = ((N + 1) * N) / 2; // 1 + 2 + 3 + ... N
-        Demand d = new Demand();
-        d.increase(M);
-        CyclicBarrier start = new CyclicBarrier(N);
-        CountDownLatch stop = new CountDownLatch(N);
-        AtomicReference<Throwable> error = new AtomicReference<>();
-        for (int i = 0; i < N; i++) {
-            int j = i + 1;
-            new Thread(() -> {
-                try {
-                    start.await();
-                } catch (Exception e) {
-                    error.compareAndSet(null, e);
-                }
-                try {
-                    assertEquals(d.decreaseAndGet(j), j);
-                } catch (Throwable t) {
-                    error.compareAndSet(null, t);
-                } finally {
-                    stop.countDown();
-                }
-            }).start();
-        }
-        stop.await();
-        assertTrue(d.isFulfilled());
-        assertEquals(error.get(), null);
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/common/MinimalFutureTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.internal.common;
-
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import static org.testng.Assert.assertThrows;
-
-public class MinimalFutureTest {
-
-    @Test(dataProvider = "futures")
-    public void test(CompletableFuture<Object> mf) {
-        ExecutorService executor = Executors.newSingleThreadExecutor();
-        try {
-            assertNoObtrusion(mf.thenApply(MinimalFutureTest::apply));
-            assertNoObtrusion(mf.thenApplyAsync(MinimalFutureTest::apply));
-            assertNoObtrusion(mf.thenApplyAsync(MinimalFutureTest::apply, executor));
-
-            assertNoObtrusion(mf.thenAccept(MinimalFutureTest::accept));
-            assertNoObtrusion(mf.thenAcceptAsync(MinimalFutureTest::accept));
-            assertNoObtrusion(mf.thenAcceptAsync(MinimalFutureTest::accept, executor));
-
-            assertNoObtrusion(mf.thenRun(MinimalFutureTest::run));
-            assertNoObtrusion(mf.thenRunAsync(MinimalFutureTest::run));
-            assertNoObtrusion(mf.thenRunAsync(MinimalFutureTest::run, executor));
-
-            assertNoObtrusion(mf.thenCombine(otherFuture(), MinimalFutureTest::apply));
-            assertNoObtrusion(mf.thenCombineAsync(otherFuture(), MinimalFutureTest::apply));
-            assertNoObtrusion(mf.thenCombineAsync(otherFuture(), MinimalFutureTest::apply, executor));
-
-            assertNoObtrusion(mf.thenAcceptBoth(otherFuture(), MinimalFutureTest::accept));
-            assertNoObtrusion(mf.thenAcceptBothAsync(otherFuture(), MinimalFutureTest::accept));
-            assertNoObtrusion(mf.thenAcceptBothAsync(otherFuture(), MinimalFutureTest::accept, executor));
-
-            assertNoObtrusion(mf.runAfterBoth(otherFuture(), MinimalFutureTest::run));
-            assertNoObtrusion(mf.runAfterBothAsync(otherFuture(), MinimalFutureTest::run));
-            assertNoObtrusion(mf.runAfterBothAsync(otherFuture(), MinimalFutureTest::run, executor));
-
-            // "either" methods may return something else if otherFuture() is
-            // not MinimalFuture
-
-            assertNoObtrusion(mf.applyToEither(otherFuture(), MinimalFutureTest::apply));
-            assertNoObtrusion(mf.applyToEitherAsync(otherFuture(), MinimalFutureTest::apply));
-            assertNoObtrusion(mf.applyToEitherAsync(otherFuture(), MinimalFutureTest::apply, executor));
-
-            assertNoObtrusion(mf.acceptEither(otherFuture(), MinimalFutureTest::accept));
-            assertNoObtrusion(mf.acceptEitherAsync(otherFuture(), MinimalFutureTest::accept));
-            assertNoObtrusion(mf.acceptEitherAsync(otherFuture(), MinimalFutureTest::accept, executor));
-
-            assertNoObtrusion(mf.runAfterEither(otherFuture(), MinimalFutureTest::run));
-            assertNoObtrusion(mf.runAfterEitherAsync(otherFuture(), MinimalFutureTest::run));
-            assertNoObtrusion(mf.runAfterEitherAsync(otherFuture(), MinimalFutureTest::run, executor));
-
-            assertNoObtrusion(mf.thenCompose(MinimalFutureTest::completionStageOf));
-            assertNoObtrusion(mf.thenComposeAsync(MinimalFutureTest::completionStageOf));
-            assertNoObtrusion(mf.thenComposeAsync(MinimalFutureTest::completionStageOf, executor));
-
-            assertNoObtrusion(mf.handle(MinimalFutureTest::relay));
-            assertNoObtrusion(mf.handleAsync(MinimalFutureTest::relay));
-            assertNoObtrusion(mf.handleAsync(MinimalFutureTest::relay, executor));
-
-            assertNoObtrusion(mf.whenComplete(MinimalFutureTest::accept));
-            assertNoObtrusion(mf.whenCompleteAsync(MinimalFutureTest::accept));
-            assertNoObtrusion(mf.whenCompleteAsync(MinimalFutureTest::accept, executor));
-
-            assertNoObtrusion(mf.toCompletableFuture());
-            assertNoObtrusion(mf.exceptionally(t -> null));
-
-            assertNoObtrusion(mf);
-            assertNoObtrusion(mf.copy());
-            assertNoObtrusion(mf.newIncompleteFuture());
-        } finally {
-            executor.shutdownNow();
-        }
-    }
-
-    private static CompletableFuture<Object> otherFuture() {
-        return MinimalFuture.completedFuture(new Object());
-    }
-
-    private static Object relay(Object r, Throwable e) {
-        if (e != null)
-            throw new CompletionException(e);
-        else
-            return r;
-    }
-
-    private static CompletableFuture<?> completionStageOf(Object r) {
-        return new CompletableFuture<>();
-    }
-
-    private static void accept(Object arg) {
-    }
-
-    private static void accept(Object arg1, Object arg2) {
-    }
-
-    private static void run() {
-    }
-
-    private static Object apply(Object arg) {
-        return new Object();
-    }
-
-    private static Object apply(Object arg1, Object arg2) {
-        return new Object();
-    }
-
-
-    @DataProvider(name = "futures")
-    public Object[][] futures() {
-
-        MinimalFuture<Object> mf = new MinimalFuture<>();
-        mf.completeExceptionally(new Throwable());
-
-        MinimalFuture<Object> mf1 = new MinimalFuture<>();
-        mf1.complete(new Object());
-
-        return new Object[][]{
-                new Object[]{new MinimalFuture<>()},
-                new Object[]{MinimalFuture.failedFuture(new Throwable())},
-                new Object[]{MinimalFuture.completedFuture(new Object())},
-                new Object[]{mf},
-                new Object[]{mf1},
-        };
-    }
-
-    private void assertNoObtrusion(CompletableFuture<?> cf) {
-        assertThrows(UnsupportedOperationException.class,
-                     () -> cf.obtrudeValue(null));
-        assertThrows(UnsupportedOperationException.class,
-                     () -> cf.obtrudeException(new RuntimeException()));
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/java.net.http/java/net/http/internal/frame/FramesDecoderTest.java	Wed Feb 07 15:46:30 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +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.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.net.http.internal.frame;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-import static java.lang.System.out;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.testng.Assert.*;
-
-public class FramesDecoderTest {
-
-    abstract class TestFrameProcessor implements FramesDecoder.FrameProcessor {
-        protected volatile int count;
-        public int numberOfFramesDecoded() { return count; }
-    }
-
-    /**
-     * Verifies that a ByteBuffer containing more that one frame, destined
-     * to be returned to the user's subscriber, i.e. a data frame, does not
-     * inadvertently expose the following frame ( between its limit and
-     * capacity ).
-     */
-    @Test
-    public void decodeDataFrameFollowedByAnother() throws Exception {
-        // input frames for to the decoder
-        List<ByteBuffer> data1 = List.of(ByteBuffer.wrap("XXXX".getBytes(UTF_8)));
-        DataFrame dataFrame1 = new DataFrame(1, 0, data1);
-        List<ByteBuffer> data2 = List.of(ByteBuffer.wrap("YYYY".getBytes(UTF_8)));
-        DataFrame dataFrame2 = new DataFrame(1, 0, data2);
-
-        List<ByteBuffer> buffers = new ArrayList<>();
-        FramesEncoder encoder = new FramesEncoder();
-        buffers.addAll(encoder.encodeFrame(dataFrame1));
-        buffers.addAll(encoder.encodeFrame(dataFrame2));
-
-        ByteBuffer combined = ByteBuffer.allocate(1024);
-        buffers.stream().forEach(combined::put);
-        combined.flip();
-
-        TestFrameProcessor testFrameProcessor = new TestFrameProcessor() {
-            @Override
-            public void processFrame(Http2Frame frame) throws IOException {
-                assertTrue(frame instanceof DataFrame);
-                DataFrame dataFrame = (DataFrame) frame;
-                List<ByteBuffer> list = dataFrame.getData();
-                assertEquals(list.size(), 1);
-                ByteBuffer data = list.get(0);
-                byte[] bytes = new byte[data.remaining()];
-                data.get(bytes);
-                if (count == 0) {
-                    assertEquals(new String(bytes, UTF_8), "XXXX");
-                    out.println("First data received:" + data);
-                    assertEquals(data.position(), data.limit());  // since bytes read
-                    assertEquals(data.limit(), data.capacity());
-                } else {
-                    assertEquals(new String(bytes, UTF_8), "YYYY");
-                    out.println("Second data received:" + data);
-                }
-                count++;
-            }
-        };
-        FramesDecoder decoder = new FramesDecoder(testFrameProcessor);
-
-        out.println("Sending " + combined + " to decoder: ");
-        decoder.decode(combined);
-        Assert.assertEquals(testFrameProcessor.numberOfFramesDecoded(), 2);
-    }
-
-
-    /**
-     * Verifies that a ByteBuffer containing ONLY data one frame, destined
-     * to be returned to the user's subscriber, does not restrict the capacity.
-     * The complete buffer ( all its capacity ), since no longer used by the
-     * HTTP Client, should be returned to the user.
-     */
-    @Test
-    public void decodeDataFrameEnsureNotCapped() throws Exception {
-        // input frames for to the decoder
-        List<ByteBuffer> data1 = List.of(ByteBuffer.wrap("XXXX".getBytes(UTF_8)));
-        DataFrame dataFrame1 = new DataFrame(1, 0, data1);
-
-        List<ByteBuffer> buffers = new ArrayList<>();
-        FramesEncoder encoder = new FramesEncoder();
-        buffers.addAll(encoder.encodeFrame(dataFrame1));
-
-        ByteBuffer combined = ByteBuffer.allocate(1024);
-        buffers.stream().forEach(combined::put);
-        combined.flip();
-
-        TestFrameProcessor testFrameProcessor = new TestFrameProcessor() {
-            @Override
-            public void processFrame(Http2Frame frame) throws IOException {
-                assertTrue(frame instanceof DataFrame);
-                DataFrame dataFrame = (DataFrame) frame;
-                List<ByteBuffer> list = dataFrame.getData();
-                assertEquals(list.size(), 1);
-                ByteBuffer data = list.get(0);
-                byte[] bytes = new byte[data.remaining()];
-                data.get(bytes);
-                assertEquals(new String(bytes, UTF_8), "XXXX");
-                out.println("First data received:" + data);
-                assertEquals(data.position(), data.limit());  // since bytes read
-                //assertNotEquals(data.limit(), data.capacity());
-                assertEquals(data.capacity(), 1024 - 9 /*frame header*/);
-                count++;
-            }
-        };
-        FramesDecoder decoder = new FramesDecoder(testFrameProcessor);
-
-        out.println("Sending " + combined + " to decoder: ");
-        decoder.decode(combined);
-        Assert.assertEquals(testFrameProcessor.numberOfFramesDecoded(), 1);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/AbstractRandomTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.net.http;
+
+import java.util.Random;
+
+/** Abstract supertype for tests that need random numbers within a given range. */
+public class AbstractRandomTest {
+
+    private static Long getSystemSeed() {
+        Long seed = null;
+        try {
+            // note that Long.valueOf(null) also throws a NumberFormatException
+            // so if the property is undefined this will still work correctly
+            seed = Long.valueOf(System.getProperty("seed"));
+        } catch (NumberFormatException e) {
+            // do nothing: seed is still null
+        }
+        return seed;
+    }
+
+    private static long getSeed() {
+        Long seed = getSystemSeed();
+        if (seed == null) {
+            seed = (new Random()).nextLong();
+        }
+        System.out.println("Seed from AbstractRandomTest.getSeed = "+seed+"L");
+        return seed;
+    }
+
+    private static Random random = new Random(getSeed());
+
+    protected static int randomRange(int lower, int upper) {
+        if (lower > upper)
+            throw new IllegalArgumentException("lower > upper");
+        int diff = upper - lower;
+        int r = lower + random.nextInt(diff);
+        return r - (r % 8); // round down to multiple of 8 (align for longs)
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/AbstractSSLTubeTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,319 @@
+/*
+ * 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.internal.net.http;
+
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.SSLTube;
+import jdk.internal.net.http.common.Utils;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Flow;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.SubmissionPublisher;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class AbstractSSLTubeTest extends AbstractRandomTest {
+
+    public static final long COUNTER = 600;
+    public static final int LONGS_PER_BUF = 800;
+    public static final long TOTAL_LONGS = COUNTER * LONGS_PER_BUF;
+    public static final ByteBuffer SENTINEL = ByteBuffer.allocate(0);
+    // This is a hack to work around an issue with SubmissionPublisher.
+    // SubmissionPublisher will call onComplete immediately without forwarding
+    // remaining pending data if SubmissionPublisher.close() is called when
+    // there is no demand. In other words, it doesn't wait for the subscriber
+    // to pull all the data before calling onComplete.
+    // We use a CountDownLatch to figure out when it is safe to call close().
+    // This may cause the test to hang if data are buffered.
+    protected final CountDownLatch allBytesReceived = new CountDownLatch(1);
+
+
+    protected static ByteBuffer getBuffer(long startingAt) {
+        ByteBuffer buf = ByteBuffer.allocate(LONGS_PER_BUF * 8);
+        for (int j = 0; j < LONGS_PER_BUF; j++) {
+            buf.putLong(startingAt++);
+        }
+        buf.flip();
+        return buf;
+    }
+
+    protected void run(FlowTube server,
+                       ExecutorService sslExecutor,
+                       CountDownLatch allBytesReceived) throws IOException {
+        FlowTube client = new SSLTube(createSSLEngine(true),
+                                      sslExecutor,
+                                      server);
+        SubmissionPublisher<List<ByteBuffer>> p =
+                new SubmissionPublisher<>(ForkJoinPool.commonPool(),
+                                          Integer.MAX_VALUE);
+        FlowTube.TubePublisher begin = p::subscribe;
+        CompletableFuture<Void> completion = new CompletableFuture<>();
+        EndSubscriber end = new EndSubscriber(TOTAL_LONGS, completion, allBytesReceived);
+        client.connectFlows(begin, end);
+        /* End of wiring */
+
+        long count = 0;
+        System.out.printf("Submitting %d buffer arrays\n", COUNTER);
+        System.out.printf("LoopCount should be %d\n", TOTAL_LONGS);
+        for (long i = 0; i < COUNTER; i++) {
+            ByteBuffer b = getBuffer(count);
+            count += LONGS_PER_BUF;
+            p.submit(List.of(b));
+        }
+        System.out.println("Finished submission. Waiting for loopback");
+        completion.whenComplete((r,t) -> allBytesReceived.countDown());
+        try {
+            allBytesReceived.await();
+        } catch (InterruptedException e) {
+            throw new IOException(e);
+        }
+        p.close();
+        System.out.println("All bytes received: calling publisher.close()");
+        try {
+            completion.join();
+            System.out.println("OK");
+        } finally {
+            sslExecutor.shutdownNow();
+        }
+    }
+
+    protected static void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+
+        }
+    }
+
+    /**
+     * The final subscriber which receives the decrypted looped-back data. Just
+     * needs to compare the data with what was sent. The given CF is either
+     * completed exceptionally with an error or normally on success.
+     */
+    protected static class EndSubscriber implements FlowTube.TubeSubscriber {
+
+        private static final int REQUEST_WINDOW = 13;
+
+        private final long nbytes;
+        private final AtomicLong counter = new AtomicLong();
+        private final CompletableFuture<?> completion;
+        private final CountDownLatch allBytesReceived;
+        private volatile Flow.Subscription subscription;
+        private long unfulfilled;
+
+        EndSubscriber(long nbytes, CompletableFuture<?> completion,
+                      CountDownLatch allBytesReceived) {
+            this.nbytes = nbytes;
+            this.completion = completion;
+            this.allBytesReceived = allBytesReceived;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            this.subscription = subscription;
+            unfulfilled = REQUEST_WINDOW;
+            System.out.println("EndSubscriber request " + REQUEST_WINDOW);
+            subscription.request(REQUEST_WINDOW);
+        }
+
+        public static String info(List<ByteBuffer> i) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("size: ").append(Integer.toString(i.size()));
+            int x = 0;
+            for (ByteBuffer b : i)
+                x += b.remaining();
+            sb.append(" bytes: ").append(x);
+            return sb.toString();
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> buffers) {
+            if (--unfulfilled == (REQUEST_WINDOW / 2)) {
+                long req = REQUEST_WINDOW - unfulfilled;
+                System.out.println("EndSubscriber request " + req);
+                unfulfilled = REQUEST_WINDOW;
+                subscription.request(req);
+            }
+
+            long currval = counter.get();
+            if (currval % 500 == 0) {
+                System.out.println("EndSubscriber: " + currval);
+            }
+            System.out.println("EndSubscriber onNext " + Utils.remaining(buffers));
+
+            for (ByteBuffer buf : buffers) {
+                while (buf.hasRemaining()) {
+                    long n = buf.getLong();
+                    if (currval > (TOTAL_LONGS - 50)) {
+                        System.out.println("End: " + currval);
+                    }
+                    if (n != currval++) {
+                        System.out.println("ERROR at " + n + " != " + (currval - 1));
+                        completion.completeExceptionally(new RuntimeException("ERROR"));
+                        subscription.cancel();
+                        return;
+                    }
+                }
+            }
+
+            counter.set(currval);
+            if (currval >= TOTAL_LONGS) {
+                allBytesReceived.countDown();
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            System.out.println("EndSubscriber onError " + throwable);
+            completion.completeExceptionally(throwable);
+            allBytesReceived.countDown();
+        }
+
+        @Override
+        public void onComplete() {
+            long n = counter.get();
+            if (n != nbytes) {
+                System.out.printf("nbytes=%d n=%d\n", nbytes, n);
+                completion.completeExceptionally(new RuntimeException("ERROR AT END"));
+            } else {
+                System.out.println("DONE OK");
+                completion.complete(null);
+            }
+            allBytesReceived.countDown();
+        }
+
+        @Override
+        public String toString() {
+            return "EndSubscriber";
+        }
+    }
+
+    protected static SSLEngine createSSLEngine(boolean client) throws IOException {
+        SSLContext context = (new SimpleSSLContext()).get();
+        SSLEngine engine = context.createSSLEngine();
+        SSLParameters params = context.getSupportedSSLParameters();
+        params.setProtocols(new String[]{"TLSv1.2"}); // TODO: This is essential. Needs to be protocol impl
+        if (client) {
+            params.setApplicationProtocols(new String[]{"proto1", "proto2"}); // server will choose proto2
+        } else {
+            params.setApplicationProtocols(new String[]{"proto2"}); // server will choose proto2
+        }
+        engine.setSSLParameters(params);
+        engine.setUseClientMode(client);
+        return engine;
+    }
+
+    /**
+     * Creates a simple usable SSLContext for SSLSocketFactory or a HttpsServer
+     * using either a given keystore or a default one in the test tree.
+     *
+     * Using this class with a security manager requires the following
+     * permissions to be granted:
+     *
+     * permission "java.util.PropertyPermission" "test.src.path", "read";
+     * permission java.io.FilePermission "${test.src}/../../../../lib/testlibrary/jdk/testlibrary/testkeys",
+     * "read"; The exact path above depends on the location of the test.
+     */
+    protected static class SimpleSSLContext {
+
+        private final SSLContext ssl;
+
+        /**
+         * Loads default keystore from SimpleSSLContext source directory
+         */
+        public SimpleSSLContext() throws IOException {
+            String paths = System.getProperty("test.src.path");
+            StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
+            boolean securityExceptions = false;
+            SSLContext sslContext = null;
+            while (st.hasMoreTokens()) {
+                String path = st.nextToken();
+                try {
+                    File f = new File(path, "../../../../lib/testlibrary/jdk/testlibrary/testkeys");
+                    if (f.exists()) {
+                        try (FileInputStream fis = new FileInputStream(f)) {
+                            sslContext = init(fis);
+                            break;
+                        }
+                    }
+                } catch (SecurityException e) {
+                    // catch and ignore because permission only required
+                    // for one entry on path (at most)
+                    securityExceptions = true;
+                }
+            }
+            if (securityExceptions) {
+                System.err.println("SecurityExceptions thrown on loading testkeys");
+            }
+            ssl = sslContext;
+        }
+
+        private SSLContext init(InputStream i) throws IOException {
+            try {
+                char[] passphrase = "passphrase".toCharArray();
+                KeyStore ks = KeyStore.getInstance("JKS");
+                ks.load(i, passphrase);
+
+                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+                kmf.init(ks, passphrase);
+
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+                tmf.init(ks);
+
+                SSLContext ssl = SSLContext.getInstance("TLS");
+                ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+                return ssl;
+            } catch (KeyManagementException | KeyStoreException |
+                    UnrecoverableKeyException | CertificateException |
+                    NoSuchAlgorithmException e) {
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+
+        public SSLContext get() {
+            return ssl;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/ConnectionPoolTest.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import jdk.internal.net.http.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/java.net.http/jdk/internal/net/http/FlowTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,547 @@
+/*
+ * 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.internal.net.http;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.SubmissionPublisher;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.*;
+import javax.net.ssl.TrustManagerFactory;
+import jdk.internal.net.http.common.Utils;
+import org.testng.annotations.Test;
+import jdk.internal.net.http.common.SSLFlowDelegate;
+
+@Test
+public class FlowTest extends AbstractRandomTest {
+
+    private final SubmissionPublisher<List<ByteBuffer>> srcPublisher;
+    private final ExecutorService executor;
+    private static final long COUNTER = 3000;
+    private static final int LONGS_PER_BUF = 800;
+    static final long TOTAL_LONGS = COUNTER * LONGS_PER_BUF;
+    public static final ByteBuffer SENTINEL = ByteBuffer.allocate(0);
+    static volatile String alpn;
+
+    // This is a hack to work around an issue with SubmissionPublisher.
+    // SubmissionPublisher will call onComplete immediately without forwarding
+    // remaining pending data if SubmissionPublisher.close() is called when
+    // there is no demand. In other words, it doesn't wait for the subscriber
+    // to pull all the data before calling onComplete.
+    // We use a CountDownLatch to figure out when it is safe to call close().
+    // This may cause the test to hang if data are buffered.
+    final CountDownLatch allBytesReceived = new CountDownLatch(1);
+
+    private final CompletableFuture<Void> completion;
+
+    public FlowTest() throws IOException {
+        executor = Executors.newCachedThreadPool();
+        srcPublisher = new SubmissionPublisher<>(executor, 20,
+                                                 this::handlePublisherException);
+        SSLContext ctx = (new SimpleSSLContext()).get();
+        SSLEngine engineClient = ctx.createSSLEngine();
+        SSLParameters params = ctx.getSupportedSSLParameters();
+        params.setApplicationProtocols(new String[]{"proto1", "proto2"}); // server will choose proto2
+        params.setProtocols(new String[]{"TLSv1.2"}); // TODO: This is essential. Needs to be protocol impl
+        engineClient.setSSLParameters(params);
+        engineClient.setUseClientMode(true);
+        completion = new CompletableFuture<>();
+        SSLLoopbackSubscriber looper = new SSLLoopbackSubscriber(ctx, executor, allBytesReceived);
+        looper.start();
+        EndSubscriber end = new EndSubscriber(TOTAL_LONGS, completion, allBytesReceived);
+        SSLFlowDelegate sslClient = new SSLFlowDelegate(engineClient, executor, end, looper);
+        // going to measure how long handshake takes
+        final long start = System.currentTimeMillis();
+        sslClient.alpn().whenComplete((String s, Throwable t) -> {
+            if (t != null)
+                t.printStackTrace();
+            long endTime = System.currentTimeMillis();
+            alpn = s;
+            System.out.println("ALPN: " + alpn);
+            long period = (endTime - start);
+            System.out.printf("Handshake took %d ms\n", period);
+        });
+        Subscriber<List<ByteBuffer>> reader = sslClient.upstreamReader();
+        Subscriber<List<ByteBuffer>> writer = sslClient.upstreamWriter();
+        looper.setReturnSubscriber(reader);
+        // now connect all the pieces
+        srcPublisher.subscribe(writer);
+        String aa = sslClient.alpn().join();
+        System.out.println("AAALPN = " + aa);
+    }
+
+    private void handlePublisherException(Object o, Throwable t) {
+        System.out.println("Src Publisher exception");
+        t.printStackTrace(System.out);
+    }
+
+    private static ByteBuffer getBuffer(long startingAt) {
+        ByteBuffer buf = ByteBuffer.allocate(LONGS_PER_BUF * 8);
+        for (int j = 0; j < LONGS_PER_BUF; j++) {
+            buf.putLong(startingAt++);
+        }
+        buf.flip();
+        return buf;
+    }
+
+    @Test
+    public void run() {
+        long count = 0;
+        System.out.printf("Submitting %d buffer arrays\n", COUNTER);
+        System.out.printf("LoopCount should be %d\n", TOTAL_LONGS);
+        for (long i = 0; i < COUNTER; i++) {
+            ByteBuffer b = getBuffer(count);
+            count += LONGS_PER_BUF;
+            srcPublisher.submit(List.of(b));
+        }
+        System.out.println("Finished submission. Waiting for loopback");
+        // make sure we don't wait for allBytesReceived in case of error.
+        completion.whenComplete((r,t) -> allBytesReceived.countDown());
+        try {
+            allBytesReceived.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("All bytes received: ");
+        srcPublisher.close();
+        try {
+            completion.join();
+            if (!alpn.equals("proto2")) {
+                throw new RuntimeException("wrong alpn received");
+            }
+            System.out.println("OK");
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+/*
+    public static void main(String[]args) throws Exception {
+        FlowTest test = new FlowTest();
+        test.run();
+    }
+*/
+
+    /**
+     * This Subscriber simulates an SSL loopback network. The object itself
+     * accepts outgoing SSL encrypted data which is looped back via two sockets
+     * (one of which is an SSLSocket emulating a server). The method
+     * {@link #setReturnSubscriber(java.util.concurrent.Flow.Subscriber) }
+     * is used to provide the Subscriber which feeds the incoming side
+     * of SSLFlowDelegate. Three threads are used to implement this behavior
+     * and a SubmissionPublisher drives the incoming read side.
+     * <p>
+     * A thread reads from the buffer, writes
+     * to the client j.n.Socket which is connected to a SSLSocket operating
+     * in server mode. A second thread loops back data read from the SSLSocket back to the
+     * client again. A third thread reads the client socket and pushes the data to
+     * a SubmissionPublisher that drives the reader side of the SSLFlowDelegate
+     */
+    static class SSLLoopbackSubscriber implements Subscriber<List<ByteBuffer>> {
+        private final BlockingQueue<ByteBuffer> buffer;
+        private final Socket clientSock;
+        private final SSLSocket serverSock;
+        private final Thread thread1, thread2, thread3;
+        private volatile Flow.Subscription clientSubscription;
+        private final SubmissionPublisher<List<ByteBuffer>> publisher;
+        private final CountDownLatch allBytesReceived;
+
+        SSLLoopbackSubscriber(SSLContext ctx,
+                              ExecutorService exec,
+                              CountDownLatch allBytesReceived) throws IOException {
+            SSLServerSocketFactory fac = ctx.getServerSocketFactory();
+            SSLServerSocket serv = (SSLServerSocket) fac.createServerSocket(0);
+            SSLParameters params = serv.getSSLParameters();
+            params.setApplicationProtocols(new String[]{"proto2"});
+            serv.setSSLParameters(params);
+
+
+            int serverPort = serv.getLocalPort();
+            clientSock = new Socket("127.0.0.1", serverPort);
+            serverSock = (SSLSocket) serv.accept();
+            this.buffer = new LinkedBlockingQueue<>();
+            this.allBytesReceived = allBytesReceived;
+            thread1 = new Thread(this::clientWriter, "clientWriter");
+            thread2 = new Thread(this::serverLoopback, "serverLoopback");
+            thread3 = new Thread(this::clientReader, "clientReader");
+            publisher = new SubmissionPublisher<>(exec, Flow.defaultBufferSize(),
+                    this::handlePublisherException);
+            SSLFlowDelegate.Monitor.add(this::monitor);
+        }
+
+        public void start() {
+            thread1.start();
+            thread2.start();
+            thread3.start();
+        }
+
+        private void handlePublisherException(Object o, Throwable t) {
+            System.out.println("Loopback Publisher exception");
+            t.printStackTrace(System.out);
+        }
+
+        private final AtomicInteger readCount = new AtomicInteger();
+
+        // reads off the SSLSocket the data from the "server"
+        private void clientReader() {
+            try {
+                InputStream is = clientSock.getInputStream();
+                final int bufsize = FlowTest.randomRange(512, 16 * 1024);
+                System.out.println("clientReader: bufsize = " + bufsize);
+                while (true) {
+                    byte[] buf = new byte[bufsize];
+                    int n = is.read(buf);
+                    if (n == -1) {
+                        System.out.println("clientReader close: read "
+                                + readCount.get() + " bytes");
+                        System.out.println("clientReader: got EOF. "
+                                            + "Waiting signal to close publisher.");
+                        allBytesReceived.await();
+                        System.out.println("clientReader: closing publisher");
+                        publisher.close();
+                        sleep(2000);
+                        Utils.close(is, clientSock);
+                        return;
+                    }
+                    ByteBuffer bb = ByteBuffer.wrap(buf, 0, n);
+                    readCount.addAndGet(n);
+                    publisher.submit(List.of(bb));
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+                Utils.close(clientSock);
+            }
+        }
+
+        // writes the encrypted data from SSLFLowDelegate to the j.n.Socket
+        // which is connected to the SSLSocket emulating a server.
+        private void clientWriter() {
+            long nbytes = 0;
+            try {
+                OutputStream os =
+                        new BufferedOutputStream(clientSock.getOutputStream());
+
+                while (true) {
+                    ByteBuffer buf = buffer.take();
+                    if (buf == FlowTest.SENTINEL) {
+                        // finished
+                        //Utils.sleep(2000);
+                        System.out.println("clientWriter close: " + nbytes + " written");
+                        clientSock.shutdownOutput();
+                        System.out.println("clientWriter close return");
+                        return;
+                    }
+                    int len = buf.remaining();
+                    int written = writeToStream(os, buf);
+                    assert len == written;
+                    nbytes += len;
+                    assert !buf.hasRemaining()
+                            : "buffer has " + buf.remaining() + " bytes left";
+                    clientSubscription.request(1);
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+
+        private int writeToStream(OutputStream os, ByteBuffer buf) throws IOException {
+            byte[] b = buf.array();
+            int offset = buf.arrayOffset() + buf.position();
+            int n = buf.limit() - buf.position();
+            os.write(b, offset, n);
+            buf.position(buf.limit());
+            os.flush();
+            return n;
+        }
+
+        private final AtomicInteger loopCount = new AtomicInteger();
+
+        public String monitor() {
+            return "serverLoopback: loopcount = " + loopCount.toString()
+                    + " clientRead: count = " + readCount.toString();
+        }
+
+        // thread2
+        private void serverLoopback() {
+            try {
+                InputStream is = serverSock.getInputStream();
+                OutputStream os = serverSock.getOutputStream();
+                final int bufsize = FlowTest.randomRange(512, 16 * 1024);
+                System.out.println("serverLoopback: bufsize = " + bufsize);
+                byte[] bb = new byte[bufsize];
+                while (true) {
+                    int n = is.read(bb);
+                    if (n == -1) {
+                        sleep(2000);
+                        is.close();
+                        serverSock.close();
+                        return;
+                    }
+                    os.write(bb, 0, n);
+                    os.flush();
+                    loopCount.addAndGet(n);
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+
+
+        /**
+         * This needs to be called before the chain is subscribed. It can't be
+         * supplied in the constructor.
+         */
+        public void setReturnSubscriber(Subscriber<List<ByteBuffer>> returnSubscriber) {
+            publisher.subscribe(returnSubscriber);
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            clientSubscription = subscription;
+            clientSubscription.request(5);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            try {
+                for (ByteBuffer b : item)
+                    buffer.put(b);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+                Utils.close(clientSock);
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            throwable.printStackTrace();
+            Utils.close(clientSock);
+        }
+
+        @Override
+        public void onComplete() {
+            try {
+                buffer.put(FlowTest.SENTINEL);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+                Utils.close(clientSock);
+            }
+        }
+    }
+
+    /**
+     * The final subscriber which receives the decrypted looped-back data.
+     * Just needs to compare the data with what was sent. The given CF is
+     * either completed exceptionally with an error or normally on success.
+     */
+    static class EndSubscriber implements Subscriber<List<ByteBuffer>> {
+
+        private final long nbytes;
+
+        private final AtomicLong counter;
+        private volatile Flow.Subscription subscription;
+        private final CompletableFuture<Void> completion;
+        private final CountDownLatch allBytesReceived;
+
+        EndSubscriber(long nbytes,
+                      CompletableFuture<Void> completion,
+                      CountDownLatch allBytesReceived) {
+            counter = new AtomicLong(0);
+            this.nbytes = nbytes;
+            this.completion = completion;
+            this.allBytesReceived = allBytesReceived;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            this.subscription = subscription;
+            subscription.request(5);
+        }
+
+        public static String info(List<ByteBuffer> i) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("size: ").append(Integer.toString(i.size()));
+            int x = 0;
+            for (ByteBuffer b : i)
+                x += b.remaining();
+            sb.append(" bytes: " + Integer.toString(x));
+            return sb.toString();
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> buffers) {
+            long currval = counter.get();
+            //if (currval % 500 == 0) {
+            //System.out.println("End: " + currval);
+            //}
+
+            for (ByteBuffer buf : buffers) {
+                while (buf.hasRemaining()) {
+                    long n = buf.getLong();
+                    //if (currval > (FlowTest.TOTAL_LONGS - 50)) {
+                    //System.out.println("End: " + currval);
+                    //}
+                    if (n != currval++) {
+                        System.out.println("ERROR at " + n + " != " + (currval - 1));
+                        completion.completeExceptionally(new RuntimeException("ERROR"));
+                        subscription.cancel();
+                        return;
+                    }
+                }
+            }
+
+            counter.set(currval);
+            subscription.request(1);
+            if (currval >= TOTAL_LONGS) {
+                allBytesReceived.countDown();
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            allBytesReceived.countDown();
+            completion.completeExceptionally(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            long n = counter.get();
+            if (n != nbytes) {
+                System.out.printf("nbytes=%d n=%d\n", nbytes, n);
+                completion.completeExceptionally(new RuntimeException("ERROR AT END"));
+            } else {
+                System.out.println("DONE OK: counter = " + n);
+                allBytesReceived.countDown();
+                completion.complete(null);
+            }
+        }
+    }
+
+    /**
+     * Creates a simple usable SSLContext for SSLSocketFactory
+     * or a HttpsServer using either a given keystore or a default
+     * one in the test tree.
+     * <p>
+     * Using this class with a security manager requires the following
+     * permissions to be granted:
+     * <p>
+     * permission "java.util.PropertyPermission" "test.src.path", "read";
+     * permission java.io.FilePermission
+     * "${test.src}/../../../../lib/testlibrary/jdk/testlibrary/testkeys", "read";
+     * The exact path above depends on the location of the test.
+     */
+    static class SimpleSSLContext {
+
+        private final SSLContext ssl;
+
+        /**
+         * Loads default keystore from SimpleSSLContext source directory
+         */
+        public SimpleSSLContext() throws IOException {
+            String paths = System.getProperty("test.src.path");
+            StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
+            boolean securityExceptions = false;
+            SSLContext sslContext = null;
+            while (st.hasMoreTokens()) {
+                String path = st.nextToken();
+                try {
+                    File f = new File(path, "../../../../lib/testlibrary/jdk/testlibrary/testkeys");
+                    if (f.exists()) {
+                        try (FileInputStream fis = new FileInputStream(f)) {
+                            sslContext = init(fis);
+                            break;
+                        }
+                    }
+                } catch (SecurityException e) {
+                    // catch and ignore because permission only required
+                    // for one entry on path (at most)
+                    securityExceptions = true;
+                }
+            }
+            if (securityExceptions) {
+                System.out.println("SecurityExceptions thrown on loading testkeys");
+            }
+            ssl = sslContext;
+        }
+
+        private SSLContext init(InputStream i) throws IOException {
+            try {
+                char[] passphrase = "passphrase".toCharArray();
+                KeyStore ks = KeyStore.getInstance("JKS");
+                ks.load(i, passphrase);
+
+                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+                kmf.init(ks, passphrase);
+
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+                tmf.init(ks);
+
+                SSLContext ssl = SSLContext.getInstance("TLS");
+                ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+                return ssl;
+            } catch (KeyManagementException | KeyStoreException |
+                    UnrecoverableKeyException | CertificateException |
+                    NoSuchAlgorithmException e) {
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+
+        public SSLContext get() {
+            return ssl;
+        }
+    }
+
+    private static void sleep(int millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/Http1HeaderParserTest.java	Wed Feb 07 21:45:37 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.internal.net.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 */ }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/RawChannelTest.java	Wed Feb 07 21:45:37 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.internal.net.http;
+
+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 java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import jdk.internal.net.http.websocket.RawChannel;
+import jdk.internal.net.http.websocket.WebSocketRequest;
+import org.testng.annotations.Test;
+import static java.net.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());
+            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/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,420 @@
+/*
+ * 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.internal.net.http;
+
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.SSLTube;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.Utils;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+@Test
+public class SSLEchoTubeTest extends AbstractSSLTubeTest {
+
+    @Test
+    public void runWithEchoServer() throws IOException {
+        ExecutorService sslExecutor = Executors.newCachedThreadPool();
+
+        /* Start of wiring */
+        /* Emulates an echo server */
+        FlowTube server = crossOverEchoServer(sslExecutor);
+
+        run(server, sslExecutor, allBytesReceived);
+    }
+
+    /**
+     * Creates a cross-over FlowTube than can be plugged into a client-side
+     * SSLTube (in place of the SSLLoopbackSubscriber).
+     * Note that the only method that can be called on the return tube
+     * is connectFlows(). Calling any other method will trigger an
+     * InternalError.
+     * @param sslExecutor an executor
+     * @return a cross-over FlowTube connected to an EchoTube.
+     * @throws IOException
+     */
+    private FlowTube crossOverEchoServer(Executor sslExecutor) throws IOException {
+        LateBindingTube crossOver = new LateBindingTube();
+        FlowTube server = new SSLTube(createSSLEngine(false),
+                                      sslExecutor,
+                                      crossOver);
+        EchoTube echo = new EchoTube(6);
+        server.connectFlows(FlowTube.asTubePublisher(echo), FlowTube.asTubeSubscriber(echo));
+
+        return new CrossOverTube(crossOver);
+    }
+
+    /**
+     * A cross-over FlowTube that makes it possible to reverse the direction
+     * of flows. The typical usage is to connect an two opposite SSLTube,
+     * one encrypting, one decrypting, to e.g. an EchoTube, with the help
+     * of a LateBindingTube:
+     * {@code
+     * client app => SSLTube => CrossOverTube <= LateBindingTube <= SSLTube <= EchoTube
+     * }
+     * <p>
+     * Note that the only method that can be called on the CrossOverTube is
+     * connectFlows(). Calling any other method will cause an InternalError to
+     * be thrown.
+     * Also connectFlows() can be called only once.
+     */
+    private static final class CrossOverTube implements FlowTube {
+        final LateBindingTube tube;
+        CrossOverTube(LateBindingTube tube) {
+            this.tube = tube;
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            throw newInternalError();
+        }
+
+        @Override
+        public void connectFlows(TubePublisher writePublisher, TubeSubscriber readSubscriber) {
+            tube.start(writePublisher, readSubscriber);
+        }
+
+        @Override
+        public boolean isFinished() {
+            return tube.isFinished();
+        }
+
+        Error newInternalError() {
+            InternalError error = new InternalError();
+            error.printStackTrace(System.out);
+            return error;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            throw newInternalError();
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            throw newInternalError();
+        }
+
+        @Override
+        public void onComplete() {
+            throw newInternalError();
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            throw newInternalError();
+        }
+    }
+
+    /**
+     * A late binding tube that makes it possible to create an
+     * SSLTube before the right-hand-side tube has been created.
+     * The typical usage is to make it possible to connect two
+     * opposite SSLTube (one encrypting, one decrypting) through a
+     * CrossOverTube:
+     * {@code
+     * client app => SSLTube => CrossOverTube <= LateBindingTube <= SSLTube <= EchoTube
+     * }
+     * <p>
+     * Note that this class only supports a single call to start(): it cannot be
+     * subscribed more than once from its left-hand-side (the cross over tube side).
+     */
+    private static class LateBindingTube implements FlowTube {
+
+        final CompletableFuture<Flow.Publisher<List<ByteBuffer>>> futurePublisher
+                = new CompletableFuture<>();
+        final ConcurrentLinkedQueue<Consumer<Flow.Subscriber<? super List<ByteBuffer>>>> queue
+                = new ConcurrentLinkedQueue<>();
+        AtomicReference<Flow.Subscriber<? super List<ByteBuffer>>> subscriberRef = new AtomicReference<>();
+        SequentialScheduler scheduler = SequentialScheduler.synchronizedScheduler(this::loop);
+        AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        private volatile boolean finished;
+        private volatile boolean completed;
+
+
+        public void start(Flow.Publisher<List<ByteBuffer>> publisher,
+                          Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            subscriberRef.set(subscriber);
+            futurePublisher.complete(publisher);
+            scheduler.runOrSchedule();
+        }
+
+        @Override
+        public boolean isFinished() {
+            return finished;
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            futurePublisher.thenAccept((p) -> p.subscribe(subscriber));
+            scheduler.runOrSchedule();
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            queue.add((s) -> s.onSubscribe(subscription));
+            scheduler.runOrSchedule();
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            queue.add((s) -> s.onNext(item));
+            scheduler.runOrSchedule();
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            System.out.println("LateBindingTube onError");
+            throwable.printStackTrace(System.out);
+            queue.add((s) -> {
+                errorRef.compareAndSet(null, throwable);
+                try {
+                    System.out.println("LateBindingTube subscriber onError: " + throwable);
+                    s.onError(errorRef.get());
+                } finally {
+                    finished = true;
+                    System.out.println("LateBindingTube finished");
+                }
+            });
+            scheduler.runOrSchedule();
+        }
+
+        @Override
+        public void onComplete() {
+            System.out.println("LateBindingTube completing");
+            queue.add((s) -> {
+                completed = true;
+                try {
+                    System.out.println("LateBindingTube complete subscriber");
+                    s.onComplete();
+                } finally {
+                    finished = true;
+                    System.out.println("LateBindingTube finished");
+                }
+            });
+            scheduler.runOrSchedule();
+        }
+
+        private void loop() {
+            if (finished) {
+                scheduler.stop();
+                return;
+            }
+            Flow.Subscriber<? super List<ByteBuffer>> subscriber = subscriberRef.get();
+            if (subscriber == null) return;
+            try {
+                Consumer<Flow.Subscriber<? super List<ByteBuffer>>> s;
+                while ((s = queue.poll()) != null) {
+                    s.accept(subscriber);
+                }
+            } catch (Throwable t) {
+                if (errorRef.compareAndSet(null, t)) {
+                    onError(t);
+                }
+            }
+        }
+    }
+
+    /**
+     * An echo tube that just echoes back whatever bytes it receives.
+     * This cannot be plugged to the right-hand-side of an SSLTube
+     * since handshake data cannot be simply echoed back, and
+     * application data most likely also need to be decrypted and
+     * re-encrypted.
+     */
+    private static final class EchoTube implements FlowTube {
+
+        private final static Object EOF = new Object();
+        private final Executor executor = Executors.newSingleThreadExecutor();
+
+        private final Queue<Object> queue = new ConcurrentLinkedQueue<>();
+        private final int maxQueueSize;
+        private final SequentialScheduler processingScheduler =
+                new SequentialScheduler(createProcessingTask());
+
+        /* Writing into this tube */
+        private volatile long requested;
+        private Flow.Subscription subscription;
+
+        /* Reading from this tube */
+        private final Demand demand = new Demand();
+        private final AtomicBoolean cancelled = new AtomicBoolean();
+        private Flow.Subscriber<? super List<ByteBuffer>> subscriber;
+
+        private EchoTube(int maxBufferSize) {
+            if (maxBufferSize < 1)
+                throw new IllegalArgumentException();
+            this.maxQueueSize = maxBufferSize;
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            this.subscriber = subscriber;
+            System.out.println("EchoTube got subscriber: " + subscriber);
+            this.subscriber.onSubscribe(new InternalSubscription());
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            System.out.println("EchoTube request: " + maxQueueSize);
+            (this.subscription = subscription).request(requested = maxQueueSize);
+        }
+
+        private void requestMore() {
+            Flow.Subscription s = subscription;
+            if (s == null || cancelled.get()) return;
+            long unfulfilled = queue.size() + --requested;
+            if (unfulfilled <= maxQueueSize/2) {
+                long req = maxQueueSize - unfulfilled;
+                requested += req;
+                s.request(req);
+                System.out.printf("EchoTube request: %s [requested:%s, queue:%s, unfulfilled:%s]%n",
+                        req, requested-req, queue.size(), unfulfilled );
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            System.out.printf("EchoTube add %s [requested:%s, queue:%s]%n",
+                    Utils.remaining(item), requested, queue.size());
+            queue.add(item);
+            processingScheduler.runOrSchedule(executor);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            System.out.println("EchoTube add " + throwable);
+            queue.add(throwable);
+            processingScheduler.runOrSchedule(executor);
+        }
+
+        @Override
+        public void onComplete() {
+            System.out.println("EchoTube add EOF");
+            queue.add(EOF);
+            processingScheduler.runOrSchedule(executor);
+        }
+
+        @Override
+        public boolean isFinished() {
+            return cancelled.get();
+        }
+
+        private class InternalSubscription implements Flow.Subscription {
+
+            @Override
+            public void request(long n) {
+                System.out.println("EchoTube got request: " + n);
+                if (n <= 0) {
+                    throw new InternalError();
+                }
+                if (demand.increase(n)) {
+                    processingScheduler.runOrSchedule(executor);
+                }
+            }
+
+            @Override
+            public void cancel() {
+                cancelled.set(true);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "EchoTube";
+        }
+
+        int transmitted = 0;
+        private SequentialScheduler.RestartableTask createProcessingTask() {
+            return new SequentialScheduler.CompleteRestartableTask() {
+
+                @Override
+                protected void run() {
+                    try {
+                        while (!cancelled.get()) {
+                            Object item = queue.peek();
+                            if (item == null) {
+                                System.out.printf("EchoTube: queue empty, requested=%s, demand=%s, transmitted=%s%n",
+                                        requested, demand.get(), transmitted);
+                                requestMore();
+                                return;
+                            }
+                            try {
+                                System.out.printf("EchoTube processing item, requested=%s, demand=%s, transmitted=%s%n",
+                                        requested, demand.get(), transmitted);
+                                if (item instanceof List) {
+                                    if (!demand.tryDecrement()) {
+                                        System.out.println("EchoTube no demand");
+                                        return;
+                                    }
+                                    @SuppressWarnings("unchecked")
+                                    List<ByteBuffer> bytes = (List<ByteBuffer>) item;
+                                    Object removed = queue.remove();
+                                    assert removed == item;
+                                    System.out.println("EchoTube processing "
+                                            + Utils.remaining(bytes));
+                                    transmitted++;
+                                    subscriber.onNext(bytes);
+                                    requestMore();
+                                } else if (item instanceof Throwable) {
+                                    cancelled.set(true);
+                                    Object removed = queue.remove();
+                                    assert removed == item;
+                                    System.out.println("EchoTube processing " + item);
+                                    subscriber.onError((Throwable) item);
+                                } else if (item == EOF) {
+                                    cancelled.set(true);
+                                    Object removed = queue.remove();
+                                    assert removed == item;
+                                    System.out.println("EchoTube processing EOF");
+                                    subscriber.onComplete();
+                                } else {
+                                    throw new InternalError(String.valueOf(item));
+                                }
+                            } finally {
+                            }
+                        }
+                    } catch(Throwable t) {
+                        t.printStackTrace();
+                        throw t;
+                    }
+                }
+            };
+        }
+    }
+ }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLTubeTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,275 @@
+/*
+ * 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.internal.net.http;
+
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.SSLFlowDelegate;
+import jdk.internal.net.http.common.Utils;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.SubmissionPublisher;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Test
+public class SSLTubeTest extends AbstractSSLTubeTest {
+
+    @Test
+    public void runWithSSLLoopackServer() throws IOException {
+        ExecutorService sslExecutor = Executors.newCachedThreadPool();
+
+        /* Start of wiring */
+        /* Emulates an echo server */
+        SSLLoopbackSubscriber server =
+                new SSLLoopbackSubscriber((new SimpleSSLContext()).get(),
+                        sslExecutor,
+                        allBytesReceived);
+        server.start();
+
+        run(server, sslExecutor, allBytesReceived);
+    }
+
+    /**
+     * This is a copy of the SSLLoopbackSubscriber used in FlowTest
+     */
+    private static class SSLLoopbackSubscriber implements FlowTube {
+        private final BlockingQueue<ByteBuffer> buffer;
+        private final Socket clientSock;
+        private final SSLSocket serverSock;
+        private final Thread thread1, thread2, thread3;
+        private volatile Flow.Subscription clientSubscription;
+        private final SubmissionPublisher<List<ByteBuffer>> publisher;
+        private final CountDownLatch allBytesReceived;
+
+        SSLLoopbackSubscriber(SSLContext ctx,
+                              ExecutorService exec,
+                              CountDownLatch allBytesReceived) throws IOException {
+            SSLServerSocketFactory fac = ctx.getServerSocketFactory();
+            SSLServerSocket serv = (SSLServerSocket) fac.createServerSocket(0);
+            SSLParameters params = serv.getSSLParameters();
+            params.setApplicationProtocols(new String[]{"proto2"});
+            serv.setSSLParameters(params);
+
+
+            int serverPort = serv.getLocalPort();
+            clientSock = new Socket("127.0.0.1", serverPort);
+            serverSock = (SSLSocket) serv.accept();
+            this.buffer = new LinkedBlockingQueue<>();
+            this.allBytesReceived = allBytesReceived;
+            thread1 = new Thread(this::clientWriter, "clientWriter");
+            thread2 = new Thread(this::serverLoopback, "serverLoopback");
+            thread3 = new Thread(this::clientReader, "clientReader");
+            publisher = new SubmissionPublisher<>(exec, Flow.defaultBufferSize(),
+                    this::handlePublisherException);
+            SSLFlowDelegate.Monitor.add(this::monitor);
+        }
+
+        public void start() {
+            thread1.start();
+            thread2.start();
+            thread3.start();
+        }
+
+        private void handlePublisherException(Object o, Throwable t) {
+            System.out.println("Loopback Publisher exception");
+            t.printStackTrace(System.out);
+        }
+
+        private final AtomicInteger readCount = new AtomicInteger();
+
+        // reads off the SSLSocket the data from the "server"
+        private void clientReader() {
+            try {
+                InputStream is = clientSock.getInputStream();
+                final int bufsize = randomRange(512, 16 * 1024);
+                System.out.println("clientReader: bufsize = " + bufsize);
+                while (true) {
+                    byte[] buf = new byte[bufsize];
+                    int n = is.read(buf);
+                    if (n == -1) {
+                        System.out.println("clientReader close: read "
+                                + readCount.get() + " bytes");
+                        System.out.println("clientReader: waiting signal to close publisher");
+                        allBytesReceived.await();
+                        System.out.println("clientReader: closing publisher");
+                        publisher.close();
+                        sleep(2000);
+                        Utils.close(is, clientSock);
+                        return;
+                    }
+                    ByteBuffer bb = ByteBuffer.wrap(buf, 0, n);
+                    readCount.addAndGet(n);
+                    publisher.submit(List.of(bb));
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+                Utils.close(clientSock);
+            }
+        }
+
+        // writes the encrypted data from SSLFLowDelegate to the j.n.Socket
+        // which is connected to the SSLSocket emulating a server.
+        private void clientWriter() {
+            long nbytes = 0;
+            try {
+                OutputStream os =
+                        new BufferedOutputStream(clientSock.getOutputStream());
+
+                while (true) {
+                    ByteBuffer buf = buffer.take();
+                    if (buf == SENTINEL) {
+                        // finished
+                        //Utils.sleep(2000);
+                        System.out.println("clientWriter close: " + nbytes + " written");
+                        clientSock.shutdownOutput();
+                        System.out.println("clientWriter close return");
+                        return;
+                    }
+                    int len = buf.remaining();
+                    int written = writeToStream(os, buf);
+                    assert len == written;
+                    nbytes += len;
+                    assert !buf.hasRemaining()
+                            : "buffer has " + buf.remaining() + " bytes left";
+                    clientSubscription.request(1);
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+
+        private int writeToStream(OutputStream os, ByteBuffer buf) throws IOException {
+            byte[] b = buf.array();
+            int offset = buf.arrayOffset() + buf.position();
+            int n = buf.limit() - buf.position();
+            os.write(b, offset, n);
+            buf.position(buf.limit());
+            os.flush();
+            return n;
+        }
+
+        private final AtomicInteger loopCount = new AtomicInteger();
+
+        public String monitor() {
+            return "serverLoopback: loopcount = " + loopCount.toString()
+                    + " clientRead: count = " + readCount.toString();
+        }
+
+        // thread2
+        private void serverLoopback() {
+            try {
+                InputStream is = serverSock.getInputStream();
+                OutputStream os = serverSock.getOutputStream();
+                final int bufsize = randomRange(512, 16 * 1024);
+                System.out.println("serverLoopback: bufsize = " + bufsize);
+                byte[] bb = new byte[bufsize];
+                while (true) {
+                    int n = is.read(bb);
+                    if (n == -1) {
+                        sleep(2000);
+                        is.close();
+                        os.close();
+                        serverSock.close();
+                        return;
+                    }
+                    os.write(bb, 0, n);
+                    os.flush();
+                    loopCount.addAndGet(n);
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+
+
+        /**
+         * This needs to be called before the chain is subscribed. It can't be
+         * supplied in the constructor.
+         */
+        public void setReturnSubscriber(Flow.Subscriber<List<ByteBuffer>> returnSubscriber) {
+            publisher.subscribe(returnSubscriber);
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            clientSubscription = subscription;
+            clientSubscription.request(5);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            try {
+                for (ByteBuffer b : item)
+                    buffer.put(b);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+                Utils.close(clientSock);
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            throwable.printStackTrace();
+            Utils.close(clientSock);
+        }
+
+        @Override
+        public void onComplete() {
+            try {
+                buffer.put(SENTINEL);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+                Utils.close(clientSock);
+            }
+        }
+
+        @Override
+        public boolean isFinished() {
+            return false;
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            publisher.subscribe(subscriber);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SelectorTest.java	Wed Feb 07 21:45:37 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.internal.net.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 java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import org.testng.annotations.Test;
+import jdk.internal.net.http.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 java.net.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());
+        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/java.net.http/jdk/internal/net/http/WrapperTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,263 @@
+/*
+ * 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.internal.net.http;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+import org.testng.annotations.Test;
+import jdk.internal.net.http.common.SubscriberWrapper;
+
+@Test
+public class WrapperTest {
+    static final int LO_PRI = 1;
+    static final int HI_PRI = 2;
+    static final int NUM_HI_PRI = 240;
+    static final int BUFSIZE = 1016;
+    static final int BUFSIZE_INT = BUFSIZE/4;
+    static final int HI_PRI_FREQ = 40;
+
+    static final int TOTAL = 10000;
+    //static final int TOTAL = 500;
+
+    final SubmissionPublisher<List<ByteBuffer>> publisher;
+    final SubscriberWrapper sub1, sub2, sub3;
+    final ExecutorService executor = Executors.newCachedThreadPool();
+    volatile int hipricount = 0;
+
+    void errorHandler(Flow.Subscriber<? super List<ByteBuffer>> sub, Throwable t) {
+        System.err.printf("Exception from %s : %s\n", sub.toString(), t.toString());
+    }
+
+    public WrapperTest() {
+        publisher = new SubmissionPublisher<>(executor, 600,
+                (a, b) -> {
+                    errorHandler(a, b);
+                });
+
+        CompletableFuture<Void> notif = new CompletableFuture<>();
+        LastSubscriber ls = new LastSubscriber(notif);
+        sub1 = new Filter1(ls);
+        sub2 = new Filter2(sub1);
+        sub3 = new Filter2(sub2);
+    }
+
+    public class Filter2 extends SubscriberWrapper {
+        Filter2(SubscriberWrapper wrapper) {
+            super(wrapper);
+        }
+
+        // reverse the order of the bytes in each buffer
+        public void incoming(List<ByteBuffer> list, boolean complete) {
+            List<ByteBuffer> out = new LinkedList<>();
+            for (ByteBuffer inbuf : list) {
+                int size = inbuf.remaining();
+                ByteBuffer outbuf = ByteBuffer.allocate(size);
+                for (int i=size; i>0; i--) {
+                    byte b = inbuf.get(i-1);
+                    outbuf.put(b);
+                }
+                outbuf.flip();
+                out.add(outbuf);
+            }
+            if (complete) System.out.println("Filter2.complete");
+            outgoing(out, complete);
+        }
+
+        protected long windowUpdate(long currval) {
+            return currval == 0 ? 1 : 0;
+        }
+    }
+
+    volatile int filter1Calls = 0; // every third call we insert hi pri data
+
+    ByteBuffer getHiPri(int val) {
+        ByteBuffer buf = ByteBuffer.allocate(8);
+        buf.putInt(HI_PRI);
+        buf.putInt(val);
+        buf.flip();
+        return buf;
+    }
+
+    volatile int hiPriAdded = 0;
+
+    public class Filter1 extends SubscriberWrapper {
+        Filter1(Flow.Subscriber<List<ByteBuffer>> downstreamSubscriber)
+        {
+            super();
+            subscribe(downstreamSubscriber);
+        }
+
+        // Inserts up to NUM_HI_PRI hi priority buffers into flow
+        protected void incoming(List<ByteBuffer> in, boolean complete) {
+            if ((++filter1Calls % HI_PRI_FREQ) == 0 && (hiPriAdded++ < NUM_HI_PRI)) {
+                sub1.outgoing(getHiPri(hipricount++), false);
+            }
+            // pass data thru
+            if (complete) System.out.println("Filter1.complete");
+            outgoing(in, complete);
+        }
+
+        protected long windowUpdate(long currval) {
+            return currval == 0 ? 1 : 0;
+        }
+    }
+
+    /**
+     * Final subscriber in the chain. Compares the data sent by the original
+     * publisher.
+     */
+    static public class LastSubscriber implements Flow.Subscriber<List<ByteBuffer>> {
+        volatile Flow.Subscription subscription;
+        volatile int hipriCounter=0;
+        volatile int lopriCounter=0;
+        final CompletableFuture<Void> cf;
+
+        LastSubscriber(CompletableFuture<Void> cf) {
+            this.cf = cf;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            this.subscription = subscription;
+            subscription.request(50); // say
+        }
+
+        private void error(String...args) {
+            StringBuilder sb = new StringBuilder();
+            for (String s : args) {
+                sb.append(s);
+                sb.append(' ');
+            }
+            String msg = sb.toString();
+            System.out.println("Error: " + msg);
+            RuntimeException e = new RuntimeException(msg);
+            cf.completeExceptionally(e);
+            subscription.cancel(); // This is where we need a variant that include exception
+        }
+
+        private void check(ByteBuffer buf) {
+            int type = buf.getInt();
+            if (type == HI_PRI) {
+                // check next int is hi pri counter
+                int c = buf.getInt();
+                if (c != hipriCounter)
+                    error("hi pri counter", Integer.toString(c), Integer.toString(hipriCounter));
+                hipriCounter++;
+            } else {
+                while (buf.hasRemaining()) {
+                    if (buf.getInt() != lopriCounter)
+                        error("lo pri counter", Integer.toString(lopriCounter));
+                    lopriCounter++;
+                }
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> items) {
+            for (ByteBuffer item : items)
+                check(item);
+            subscription.request(1);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            error(throwable.getMessage());
+        }
+
+        @Override
+        public void onComplete() {
+            if (hipriCounter != NUM_HI_PRI)
+                error("hi pri at end wrong", Integer.toString(hipriCounter), Integer.toString(NUM_HI_PRI));
+            else {
+                System.out.println("LastSubscriber.complete");
+                cf.complete(null); // success
+            }
+        }
+    }
+
+    List<ByteBuffer> getBuffer(int c) {
+        ByteBuffer buf = ByteBuffer.allocate(BUFSIZE+4);
+        buf.putInt(LO_PRI);
+        for (int i=0; i<BUFSIZE_INT; i++) {
+            buf.putInt(c++);
+        }
+        buf.flip();
+        return List.of(buf);
+    }
+
+    boolean errorTest = false;
+
+    @Test
+    public void run() throws InterruptedException {
+        try {
+            CompletableFuture<Void> completion = sub3.completion();
+            publisher.subscribe(sub3);
+            // now submit a load of data
+            int counter = 0;
+            for (int i = 0; i < TOTAL; i++) {
+                List<ByteBuffer> bufs = getBuffer(counter);
+                //if (i==2)
+                    //bufs.get(0).putInt(41, 1234); // error
+                counter += BUFSIZE_INT;
+                publisher.submit(bufs);
+                //if (i % 1000 == 0)
+                    //Thread.sleep(1000);
+                //if (i == 99) {
+                    //publisher.closeExceptionally(new RuntimeException("Test error"));
+                    //errorTest = true;
+                    //break;
+                //}
+            }
+            if (!errorTest) {
+                publisher.close();
+            }
+            System.out.println("Publisher completed");
+            completion.join();
+            System.out.println("Subscribers completed ok");
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    static void display(CompletableFuture<?> cf) {
+        System.out.print (cf);
+        if (!cf.isDone())
+            return;
+        try {
+            cf.join(); // wont block
+        } catch (Exception e) {
+            System.out.println(" " + e);
+        }
+    }
+
+/*
+    public static void main(String[] args) throws InterruptedException {
+        WrapperTest test = new WrapperTest();
+        test.run();
+    }
+*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/DemandTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,202 @@
+/*
+ * 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.internal.net.http.common;
+
+import org.testng.annotations.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+public class DemandTest {
+
+    @Test
+    public void test01() {
+        assertTrue(new Demand().isFulfilled());
+    }
+
+    @Test
+    public void test011() {
+        Demand d = new Demand();
+        d.increase(3);
+        d.decreaseAndGet(3);
+        assertTrue(d.isFulfilled());
+    }
+
+    @Test
+    public void test02() {
+        Demand d = new Demand();
+        d.increase(1);
+        assertFalse(d.isFulfilled());
+    }
+
+    @Test
+    public void test03() {
+        Demand d = new Demand();
+        d.increase(3);
+        assertEquals(d.decreaseAndGet(3), 3);
+    }
+
+    @Test
+    public void test04() {
+        Demand d = new Demand();
+        d.increase(3);
+        assertEquals(d.decreaseAndGet(5), 3);
+    }
+
+    @Test
+    public void test05() {
+        Demand d = new Demand();
+        d.increase(7);
+        assertEquals(d.decreaseAndGet(4), 4);
+    }
+
+    @Test
+    public void test06() {
+        Demand d = new Demand();
+        assertEquals(d.decreaseAndGet(3), 0);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void test07() {
+        Demand d = new Demand();
+        d.increase(0);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void test08() {
+        Demand d = new Demand();
+        d.increase(-1);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void test09() {
+        Demand d = new Demand();
+        d.increase(10);
+        d.decreaseAndGet(0);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void test10() {
+        Demand d = new Demand();
+        d.increase(13);
+        d.decreaseAndGet(-3);
+    }
+
+    @Test
+    public void test11() {
+        Demand d = new Demand();
+        d.increase(1);
+        assertTrue(d.tryDecrement());
+    }
+
+    @Test
+    public void test12() {
+        Demand d = new Demand();
+        d.increase(2);
+        assertTrue(d.tryDecrement());
+    }
+
+    @Test
+    public void test14() {
+        Demand d = new Demand();
+        assertFalse(d.tryDecrement());
+    }
+
+    @Test
+    public void test141() {
+        Demand d = new Demand();
+        d.increase(Long.MAX_VALUE);
+        assertFalse(d.isFulfilled());
+    }
+
+    @Test
+    public void test142() {
+        Demand d = new Demand();
+        d.increase(Long.MAX_VALUE);
+        d.increase(1);
+        assertFalse(d.isFulfilled());
+    }
+
+    @Test
+    public void test143() {
+        Demand d = new Demand();
+        d.increase(Long.MAX_VALUE);
+        d.increase(1);
+        assertFalse(d.isFulfilled());
+    }
+
+    @Test
+    public void test144() {
+        Demand d = new Demand();
+        d.increase(Long.MAX_VALUE);
+        d.increase(Long.MAX_VALUE);
+        d.decreaseAndGet(3);
+        d.decreaseAndGet(5);
+        assertFalse(d.isFulfilled());
+    }
+
+    @Test
+    public void test145() {
+        Demand d = new Demand();
+        d.increase(Long.MAX_VALUE);
+        d.decreaseAndGet(Long.MAX_VALUE);
+        assertTrue(d.isFulfilled());
+    }
+
+    @Test(invocationCount = 32)
+    public void test15() throws InterruptedException {
+        int N = Math.max(2, Runtime.getRuntime().availableProcessors() + 1);
+        int M = ((N + 1) * N) / 2; // 1 + 2 + 3 + ... N
+        Demand d = new Demand();
+        d.increase(M);
+        CyclicBarrier start = new CyclicBarrier(N);
+        CountDownLatch stop = new CountDownLatch(N);
+        AtomicReference<Throwable> error = new AtomicReference<>();
+        for (int i = 0; i < N; i++) {
+            int j = i + 1;
+            new Thread(() -> {
+                try {
+                    start.await();
+                } catch (Exception e) {
+                    error.compareAndSet(null, e);
+                }
+                try {
+                    assertEquals(d.decreaseAndGet(j), j);
+                } catch (Throwable t) {
+                    error.compareAndSet(null, t);
+                } finally {
+                    stop.countDown();
+                }
+            }).start();
+        }
+        stop.await();
+        assertTrue(d.isFulfilled());
+        assertEquals(error.get(), null);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/MinimalFutureTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http.common;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.testng.Assert.assertThrows;
+
+public class MinimalFutureTest {
+
+    @Test(dataProvider = "futures")
+    public void test(CompletableFuture<Object> mf) {
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            assertNoObtrusion(mf.thenApply(MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenApplyAsync(MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenApplyAsync(MinimalFutureTest::apply, executor));
+
+            assertNoObtrusion(mf.thenAccept(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptAsync(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptAsync(MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.thenRun(MinimalFutureTest::run));
+            assertNoObtrusion(mf.thenRunAsync(MinimalFutureTest::run));
+            assertNoObtrusion(mf.thenRunAsync(MinimalFutureTest::run, executor));
+
+            assertNoObtrusion(mf.thenCombine(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenCombineAsync(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenCombineAsync(otherFuture(), MinimalFutureTest::apply, executor));
+
+            assertNoObtrusion(mf.thenAcceptBoth(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptBothAsync(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptBothAsync(otherFuture(), MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.runAfterBoth(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterBothAsync(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterBothAsync(otherFuture(), MinimalFutureTest::run, executor));
+
+            // "either" methods may return something else if otherFuture() is
+            // not MinimalFuture
+
+            assertNoObtrusion(mf.applyToEither(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.applyToEitherAsync(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.applyToEitherAsync(otherFuture(), MinimalFutureTest::apply, executor));
+
+            assertNoObtrusion(mf.acceptEither(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.acceptEitherAsync(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.acceptEitherAsync(otherFuture(), MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.runAfterEither(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterEitherAsync(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterEitherAsync(otherFuture(), MinimalFutureTest::run, executor));
+
+            assertNoObtrusion(mf.thenCompose(MinimalFutureTest::completionStageOf));
+            assertNoObtrusion(mf.thenComposeAsync(MinimalFutureTest::completionStageOf));
+            assertNoObtrusion(mf.thenComposeAsync(MinimalFutureTest::completionStageOf, executor));
+
+            assertNoObtrusion(mf.handle(MinimalFutureTest::relay));
+            assertNoObtrusion(mf.handleAsync(MinimalFutureTest::relay));
+            assertNoObtrusion(mf.handleAsync(MinimalFutureTest::relay, executor));
+
+            assertNoObtrusion(mf.whenComplete(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.whenCompleteAsync(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.whenCompleteAsync(MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.toCompletableFuture());
+            assertNoObtrusion(mf.exceptionally(t -> null));
+
+            assertNoObtrusion(mf);
+            assertNoObtrusion(mf.copy());
+            assertNoObtrusion(mf.newIncompleteFuture());
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    private static CompletableFuture<Object> otherFuture() {
+        return MinimalFuture.completedFuture(new Object());
+    }
+
+    private static Object relay(Object r, Throwable e) {
+        if (e != null)
+            throw new CompletionException(e);
+        else
+            return r;
+    }
+
+    private static CompletableFuture<?> completionStageOf(Object r) {
+        return new CompletableFuture<>();
+    }
+
+    private static void accept(Object arg) {
+    }
+
+    private static void accept(Object arg1, Object arg2) {
+    }
+
+    private static void run() {
+    }
+
+    private static Object apply(Object arg) {
+        return new Object();
+    }
+
+    private static Object apply(Object arg1, Object arg2) {
+        return new Object();
+    }
+
+
+    @DataProvider(name = "futures")
+    public Object[][] futures() {
+
+        MinimalFuture<Object> mf = new MinimalFuture<>();
+        mf.completeExceptionally(new Throwable());
+
+        MinimalFuture<Object> mf1 = new MinimalFuture<>();
+        mf1.complete(new Object());
+
+        return new Object[][]{
+                new Object[]{new MinimalFuture<>()},
+                new Object[]{MinimalFuture.failedFuture(new Throwable())},
+                new Object[]{MinimalFuture.completedFuture(new Object())},
+                new Object[]{mf},
+                new Object[]{mf1},
+        };
+    }
+
+    private void assertNoObtrusion(CompletableFuture<?> cf) {
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeValue(null));
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeException(new RuntimeException()));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/frame/FramesDecoderTest.java	Wed Feb 07 21:45:37 2018 +0000
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http.frame;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.*;
+
+public class FramesDecoderTest {
+
+    abstract class TestFrameProcessor implements FramesDecoder.FrameProcessor {
+        protected volatile int count;
+        public int numberOfFramesDecoded() { return count; }
+    }
+
+    /**
+     * Verifies that a ByteBuffer containing more that one frame, destined
+     * to be returned to the user's subscriber, i.e. a data frame, does not
+     * inadvertently expose the following frame ( between its limit and
+     * capacity ).
+     */
+    @Test
+    public void decodeDataFrameFollowedByAnother() throws Exception {
+        // input frames for to the decoder
+        List<ByteBuffer> data1 = List.of(ByteBuffer.wrap("XXXX".getBytes(UTF_8)));
+        DataFrame dataFrame1 = new DataFrame(1, 0, data1);
+        List<ByteBuffer> data2 = List.of(ByteBuffer.wrap("YYYY".getBytes(UTF_8)));
+        DataFrame dataFrame2 = new DataFrame(1, 0, data2);
+
+        List<ByteBuffer> buffers = new ArrayList<>();
+        FramesEncoder encoder = new FramesEncoder();
+        buffers.addAll(encoder.encodeFrame(dataFrame1));
+        buffers.addAll(encoder.encodeFrame(dataFrame2));
+
+        ByteBuffer combined = ByteBuffer.allocate(1024);
+        buffers.stream().forEach(combined::put);
+        combined.flip();
+
+        TestFrameProcessor testFrameProcessor = new TestFrameProcessor() {
+            @Override
+            public void processFrame(Http2Frame frame) throws IOException {
+                assertTrue(frame instanceof DataFrame);
+                DataFrame dataFrame = (DataFrame) frame;
+                List<ByteBuffer> list = dataFrame.getData();
+                assertEquals(list.size(), 1);
+                ByteBuffer data = list.get(0);
+                byte[] bytes = new byte[data.remaining()];
+                data.get(bytes);
+                if (count == 0) {
+                    assertEquals(new String(bytes, UTF_8), "XXXX");
+                    out.println("First data received:" + data);
+                    assertEquals(data.position(), data.limit());  // since bytes read
+                    assertEquals(data.limit(), data.capacity());
+                } else {
+                    assertEquals(new String(bytes, UTF_8), "YYYY");
+                    out.println("Second data received:" + data);
+                }
+                count++;
+            }
+        };
+        FramesDecoder decoder = new FramesDecoder(testFrameProcessor);
+
+        out.println("Sending " + combined + " to decoder: ");
+        decoder.decode(combined);
+        Assert.assertEquals(testFrameProcessor.numberOfFramesDecoded(), 2);
+    }
+
+
+    /**
+     * Verifies that a ByteBuffer containing ONLY data one frame, destined
+     * to be returned to the user's subscriber, does not restrict the capacity.
+     * The complete buffer ( all its capacity ), since no longer used by the
+     * HTTP Client, should be returned to the user.
+     */
+    @Test
+    public void decodeDataFrameEnsureNotCapped() throws Exception {
+        // input frames for to the decoder
+        List<ByteBuffer> data1 = List.of(ByteBuffer.wrap("XXXX".getBytes(UTF_8)));
+        DataFrame dataFrame1 = new DataFrame(1, 0, data1);
+
+        List<ByteBuffer> buffers = new ArrayList<>();
+        FramesEncoder encoder = new FramesEncoder();
+        buffers.addAll(encoder.encodeFrame(dataFrame1));
+
+        ByteBuffer combined = ByteBuffer.allocate(1024);
+        buffers.stream().forEach(combined::put);
+        combined.flip();
+
+        TestFrameProcessor testFrameProcessor = new TestFrameProcessor() {
+            @Override
+            public void processFrame(Http2Frame frame) throws IOException {
+                assertTrue(frame instanceof DataFrame);
+                DataFrame dataFrame = (DataFrame) frame;
+                List<ByteBuffer> list = dataFrame.getData();
+                assertEquals(list.size(), 1);
+                ByteBuffer data = list.get(0);
+                byte[] bytes = new byte[data.remaining()];
+                data.get(bytes);
+                assertEquals(new String(bytes, UTF_8), "XXXX");
+                out.println("First data received:" + data);
+                assertEquals(data.position(), data.limit());  // since bytes read
+                //assertNotEquals(data.limit(), data.capacity());
+                assertEquals(data.capacity(), 1024 - 9 /*frame header*/);
+                count++;
+            }
+        };
+        FramesDecoder decoder = new FramesDecoder(testFrameProcessor);
+
+        out.println("Sending " + combined + " to decoder: ");
+        decoder.decode(combined);
+        Assert.assertEquals(testFrameProcessor.numberOfFramesDecoded(), 1);
+    }
+}