8197564: HTTP Client implementation
authorchegar
Tue, 17 Apr 2018 08:54:17 -0700
changeset 49765 ee6f7a61f3a5
parent 49707 f7fd051519ac
child 49766 e39a356eed2c
8197564: HTTP Client implementation Reviewed-by: chegar, dfuchs, michaelm, prappo Contributed-by: Chris Hegarty <chris.hegarty@oracle.com>, Daniel Fuchs <daniel.fuchs@oracle.com>, Michael McMahon <michael.x.mcmahon@oracle.com>, Pavel Rappo <pavel.rappo@oracle.com>
make/common/Modules.gmk
src/java.base/share/classes/java/net/CookieHandler.java
src/java.base/share/classes/java/net/doc-files/net-properties.html
src/java.base/share/classes/java/net/package-info.java
src/java.base/share/classes/module-info.java
src/java.base/share/lib/security/default.policy
src/java.net.http/share/classes/java/net/http/HttpClient.java
src/java.net.http/share/classes/java/net/http/HttpHeaders.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/HttpTimeoutException.java
src/java.net.http/share/classes/java/net/http/WebSocket.java
src/java.net.http/share/classes/java/net/http/WebSocketHandshakeException.java
src/java.net.http/share/classes/java/net/http/package-info.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/RawChannelTube.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/ResponseInfoImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.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/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/ImmutableExtendedSSLSession.java
src/java.net.http/share/classes/jdk/internal/net/http/common/ImmutableSSLSession.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/Logger.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/OperationTrackers.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/SimpleHeaderTable.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/MessageDecoder.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageEncoder.java
src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageQueue.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/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
src/java.net.http/share/classes/module-info.java
src/java.se/share/classes/module-info.java
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractAsyncSSLConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractSubscription.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLTunnelConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncTriggerEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AuthenticationFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/BufferingSubscriber.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ConnectionPool.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/CookieFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/FilterFactory.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderParser.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1AsyncReceiver.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Exchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1HeaderParser.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Request.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Response.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientBuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientFacade.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpHeaders.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponse.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponseImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpTimeoutException.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ImmutableHeaders.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiMapResult.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainHttpConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainProxyConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PrivilegedExecutor.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PullPublisher.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PushGroup.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RawChannelImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RedirectFilter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RequestPublishers.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Response.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseContent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseSubscribers.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SocketTube.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Stream.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/TimeoutEvent.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocketHandshakeException.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowController.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowUpdateSender.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/ByteBufferPool.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/ByteBufferReference.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/ConnectionExpiredException.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/DebugLogger.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Demand.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/FlowTube.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/HttpHeadersImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Log.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/MinimalFuture.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Pair.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SSLFlowDelegate.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SSLTube.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SequentialScheduler.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SubscriberWrapper.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SubscriptionBase.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/ContinuationFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/DataFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/ErrorFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/FramesDecoder.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/FramesEncoder.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/GoAwayFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/HeaderFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/HeadersFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/Http2Frame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/MalformedFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/OutgoingHeaders.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/PingFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/PriorityFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/PushPromiseFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/ResetFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/SettingsFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/WindowUpdateFrame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/BinaryRepresentationWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/BulkSizeUpdateWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/Decoder.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/DecodingCallback.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/Encoder.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/HPACK.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/HeaderTable.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/Huffman.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/ISO_8859_1.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IndexNameValueWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IndexedWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IntegerReader.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IntegerWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/LiteralNeverIndexedWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/LiteralWithIndexingWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/LiteralWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/SizeUpdateWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/StringReader.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/StringWriter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/package-info.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/BuilderImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/CheckFailedException.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/FailWebSocketException.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Frame.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/FrameConsumer.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/MessageStreamConsumer.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/OpeningHandshake.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/OutgoingMessage.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/RawChannel.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Receiver.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/StatusCodes.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Transmitter.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/TransportSupplier.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/UTF8AccumulatingDecoder.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketRequest.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/package-info.java
src/jdk.incubator.httpclient/share/classes/module-info.java
test/jdk/ProblemList.txt
test/jdk/java/net/httpclient/AbstractNoBody.java
test/jdk/java/net/httpclient/AsFileDownloadTest.java
test/jdk/java/net/httpclient/AsFileDownloadTest.policy
test/jdk/java/net/httpclient/BasicAuthTest.java
test/jdk/java/net/httpclient/BasicRedirectTest.java
test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java
test/jdk/java/net/httpclient/BufferingSubscriberCancelTest.java
test/jdk/java/net/httpclient/BufferingSubscriberErrorCompleteTest.java
test/jdk/java/net/httpclient/BufferingSubscriberTest.java
test/jdk/java/net/httpclient/CancelledResponse.java
test/jdk/java/net/httpclient/ConcurrentResponses.java
test/jdk/java/net/httpclient/CookieHeaderTest.java
test/jdk/java/net/httpclient/CustomRequestPublisher.java
test/jdk/java/net/httpclient/CustomResponseSubscriber.java
test/jdk/java/net/httpclient/DependentActionsTest.java
test/jdk/java/net/httpclient/DependentPromiseActionsTest.java
test/jdk/java/net/httpclient/DigestEchoClient.java
test/jdk/java/net/httpclient/DigestEchoClientSSL.java
test/jdk/java/net/httpclient/DigestEchoServer.java
test/jdk/java/net/httpclient/EchoHandler.java
test/jdk/java/net/httpclient/EncodedCharsInURI.java
test/jdk/java/net/httpclient/EscapedOctetsInURI.java
test/jdk/java/net/httpclient/ExpectContinue.java
test/jdk/java/net/httpclient/FlowAdapterPublisherTest.java
test/jdk/java/net/httpclient/FlowAdapterSubscriberTest.java
test/jdk/java/net/httpclient/FlowAdaptersCompileOnly.java
test/jdk/java/net/httpclient/HandshakeFailureTest.java
test/jdk/java/net/httpclient/HeadersTest.java
test/jdk/java/net/httpclient/HeadersTest1.java
test/jdk/java/net/httpclient/HeadersTest2.java
test/jdk/java/net/httpclient/HttpClientBuilderTest.java
test/jdk/java/net/httpclient/HttpEchoHandler.java
test/jdk/java/net/httpclient/HttpInputStreamTest.java
test/jdk/java/net/httpclient/HttpRequestBuilderTest.java
test/jdk/java/net/httpclient/HttpResponseInputStreamTest.java
test/jdk/java/net/httpclient/HttpServerAdapters.java
test/jdk/java/net/httpclient/HttpsTunnelTest.java
test/jdk/java/net/httpclient/ImmutableFlowItems.java
test/jdk/java/net/httpclient/ImmutableHeaders.java
test/jdk/java/net/httpclient/InterruptedBlockingSend.java
test/jdk/java/net/httpclient/InvalidInputStreamSubscriptionRequest.java
test/jdk/java/net/httpclient/InvalidSSLContextTest.java
test/jdk/java/net/httpclient/InvalidSubscriptionRequest.java
test/jdk/java/net/httpclient/LightWeightHttpServer.java
test/jdk/java/net/httpclient/LineAdaptersCompileOnly.java
test/jdk/java/net/httpclient/LineBodyHandlerTest.java
test/jdk/java/net/httpclient/LineStreamsAndSurrogatesTest.java
test/jdk/java/net/httpclient/LineSubscribersAndSurrogatesTest.java
test/jdk/java/net/httpclient/ManyRequests.java
test/jdk/java/net/httpclient/ManyRequests2.java
test/jdk/java/net/httpclient/ManyRequestsLegacy.java
test/jdk/java/net/httpclient/MappingResponseSubscriber.java
test/jdk/java/net/httpclient/MessageHeadersTest.java
test/jdk/java/net/httpclient/MethodsTest.java
test/jdk/java/net/httpclient/MockServer.java
test/jdk/java/net/httpclient/MultiAuthTest.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/ProxyAuthTest.java
test/jdk/java/net/httpclient/ProxyServer.java
test/jdk/java/net/httpclient/ProxyTest.java
test/jdk/java/net/httpclient/RedirectMethodChange.java
test/jdk/java/net/httpclient/RedirectWithCookie.java
test/jdk/java/net/httpclient/ReferenceTracker.java
test/jdk/java/net/httpclient/RequestBodyTest.java
test/jdk/java/net/httpclient/RequestBodyTest.policy
test/jdk/java/net/httpclient/RequestBuilderTest.java
test/jdk/java/net/httpclient/ResponsePublisher.java
test/jdk/java/net/httpclient/RetryWithCookie.java
test/jdk/java/net/httpclient/ServerCloseTest.java
test/jdk/java/net/httpclient/ShortRequestBody.java
test/jdk/java/net/httpclient/SmallTimeout.java
test/jdk/java/net/httpclient/SmokeTest.java
test/jdk/java/net/httpclient/SplitResponse.java
test/jdk/java/net/httpclient/SplitResponseSSL.java
test/jdk/java/net/httpclient/StreamingBody.java
test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java
test/jdk/java/net/httpclient/TEST.properties
test/jdk/java/net/httpclient/TestKit.java
test/jdk/java/net/httpclient/TestKitTest.java
test/jdk/java/net/httpclient/ThrowingPublishers.java
test/jdk/java/net/httpclient/ThrowingPushPromises.java
test/jdk/java/net/httpclient/ThrowingSubscribers.java
test/jdk/java/net/httpclient/TimeoutBasic.java
test/jdk/java/net/httpclient/TimeoutOrdering.java
test/jdk/java/net/httpclient/VersionTest.java
test/jdk/java/net/httpclient/ZeroRedirects.java
test/jdk/java/net/httpclient/dependent.policy
test/jdk/java/net/httpclient/docs/files/notsobigfile.txt
test/jdk/java/net/httpclient/examples/JavadocExamples.java
test/jdk/java/net/httpclient/examples/WebSocketExample.java
test/jdk/java/net/httpclient/http2/BadHeadersTest.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/Timeout.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/SimpleHeaderTableTest.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/BinaryPrimitivesTest.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/BuffersTestingKit.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/CircularBufferTest.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/DecoderTest.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/EncoderTest.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/HeaderTableTest.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/HuffmanTest.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/SpecHelper.java
test/jdk/java/net/httpclient/http2/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/TestHelper.java
test/jdk/java/net/httpclient/http2/keystore.p12
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/ExceptionallyCloseable.java
test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java
test/jdk/java/net/httpclient/http2/server/Http2Handler.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/NoBodyHandler.java
test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java
test/jdk/java/net/httpclient/http2/server/PushHandler.java
test/jdk/java/net/httpclient/http2/server/Queue.java
test/jdk/java/net/httpclient/http2/server/TestUtil.java
test/jdk/java/net/httpclient/offline/DelegatingHttpClient.java
test/jdk/java/net/httpclient/offline/FixedHttpHeaders.java
test/jdk/java/net/httpclient/offline/FixedHttpResponse.java
test/jdk/java/net/httpclient/offline/FixedResponseHttpClient.java
test/jdk/java/net/httpclient/offline/OfflineTesting.java
test/jdk/java/net/httpclient/security/0.policy
test/jdk/java/net/httpclient/security/1.policy
test/jdk/java/net/httpclient/security/10.policy
test/jdk/java/net/httpclient/security/11.policy
test/jdk/java/net/httpclient/security/12.policy
test/jdk/java/net/httpclient/security/14.policy
test/jdk/java/net/httpclient/security/15.policy
test/jdk/java/net/httpclient/security/2.policy
test/jdk/java/net/httpclient/security/3.policy
test/jdk/java/net/httpclient/security/4.policy
test/jdk/java/net/httpclient/security/5.policy
test/jdk/java/net/httpclient/security/6.policy
test/jdk/java/net/httpclient/security/7.policy
test/jdk/java/net/httpclient/security/8.policy
test/jdk/java/net/httpclient/security/9.policy
test/jdk/java/net/httpclient/security/Driver.java
test/jdk/java/net/httpclient/security/Security.java
test/jdk/java/net/httpclient/security/filePerms/FileProcessorPermissionTest.java
test/jdk/java/net/httpclient/security/filePerms/SecurityBeforeFile.java
test/jdk/java/net/httpclient/security/filePerms/allpermissions.policy
test/jdk/java/net/httpclient/security/filePerms/httpclient.policy
test/jdk/java/net/httpclient/security/filePerms/nopermissions.policy
test/jdk/java/net/httpclient/ssltest/CertificateTest.java
test/jdk/java/net/httpclient/ssltest/Server.java
test/jdk/java/net/httpclient/ssltest/bad.keystore
test/jdk/java/net/httpclient/ssltest/good.keystore
test/jdk/java/net/httpclient/ssltest/loopback.keystore
test/jdk/java/net/httpclient/websocket/Abort.java
test/jdk/java/net/httpclient/websocket/AutomaticPong.java
test/jdk/java/net/httpclient/websocket/BlowupOutputQueue.java
test/jdk/java/net/httpclient/websocket/BuildingWebSocketDriver.java
test/jdk/java/net/httpclient/websocket/ConnectionHandover.java
test/jdk/java/net/httpclient/websocket/ConnectionHandoverTest.java
test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java
test/jdk/java/net/httpclient/websocket/Frame.java
test/jdk/java/net/httpclient/websocket/HeaderWriterDriver.java
test/jdk/java/net/httpclient/websocket/MaskerDriver.java
test/jdk/java/net/httpclient/websocket/MessageQueueDriver.java
test/jdk/java/net/httpclient/websocket/MockListener.java
test/jdk/java/net/httpclient/websocket/PendingBinaryPingClose.java
test/jdk/java/net/httpclient/websocket/PendingBinaryPongClose.java
test/jdk/java/net/httpclient/websocket/PendingOperations.java
test/jdk/java/net/httpclient/websocket/PendingPingBinaryClose.java
test/jdk/java/net/httpclient/websocket/PendingPingTextClose.java
test/jdk/java/net/httpclient/websocket/PendingPongBinaryClose.java
test/jdk/java/net/httpclient/websocket/PendingPongTextClose.java
test/jdk/java/net/httpclient/websocket/PendingTextPingClose.java
test/jdk/java/net/httpclient/websocket/PendingTextPongClose.java
test/jdk/java/net/httpclient/websocket/ReaderDriver.java
test/jdk/java/net/httpclient/websocket/ReceivingTestDriver.java
test/jdk/java/net/httpclient/websocket/SendTest.java
test/jdk/java/net/httpclient/websocket/SendingTestDriver.java
test/jdk/java/net/httpclient/websocket/Support.java
test/jdk/java/net/httpclient/websocket/WSHandshakeException.java
test/jdk/java/net/httpclient/websocket/WSHandshakeExceptionTest.java
test/jdk/java/net/httpclient/websocket/WebSocketBuilderTest.java
test/jdk/java/net/httpclient/websocket/WebSocketExtendedTest.java
test/jdk/java/net/httpclient/websocket/WebSocketTest.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/MessageQueueTest.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/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/BuildingWebSocketTest.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/HeaderWriterTest.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MaskerTest.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockListener.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockReceiver.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockTransmitter.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockTransport.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/ReaderTest.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/ReceivingTest.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/SendingTest.java
test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/TestSupport.java
test/jdk/java/net/httpclient/websocket/security/WSURLPermissionTest.java
test/jdk/java/net/httpclient/websocket/security/httpclient.policy
test/jdk/java/net/httpclient/whitebox/AuthenticationFilterTestDriver.java
test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java
test/jdk/java/net/httpclient/whitebox/DefaultProxyDriver.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/RawChannelTestDriver.java
test/jdk/java/net/httpclient/whitebox/SSLEchoTubeTestDriver.java
test/jdk/java/net/httpclient/whitebox/SSLTubeTestDriver.java
test/jdk/java/net/httpclient/whitebox/SelectorTestDriver.java
test/jdk/java/net/httpclient/whitebox/WrapperTestDriver.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/AuthenticationFilterTest.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/DefaultProxy.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
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/AbstractRandomTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/AbstractSSLTubeTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/ConnectionPoolTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/FlowTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/Http1HeaderParserTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/RawChannelTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/SSLEchoTubeTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/SSLTubeTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/SelectorTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/WrapperTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/common/DemandTest.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/frame/FramesDecoderTest.java
test/jdk/lib/testlibrary/jdk/testlibrary/SimpleSSLContext.java
test/jdk/lib/testlibrary/jdk/testlibrary/testkeys
--- a/make/common/Modules.gmk	Tue Apr 17 15:39:20 2018 +0200
+++ b/make/common/Modules.gmk	Tue Apr 17 08:54:17 2018 -0700
@@ -89,6 +89,7 @@
     #
 
 PLATFORM_MODULES += \
+    java.net.http \
     java.scripting \
     java.security.jgss \
     java.smartcardio \
@@ -102,7 +103,6 @@
     jdk.crypto.ec \
     jdk.dynalink \
     jdk.httpserver \
-    jdk.incubator.httpclient \
     jdk.internal.vm.compiler.management \
     jdk.jsobject \
     jdk.localedata \
@@ -145,7 +145,6 @@
     jdk.editpad \
     jdk.hotspot.agent \
     jdk.httpserver \
-    jdk.incubator.httpclient \
     jdk.jartool \
     jdk.javadoc \
     jdk.jcmd \
--- a/src/java.base/share/classes/java/net/CookieHandler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/src/java.base/share/classes/java/net/CookieHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -36,9 +36,9 @@
  * handler. The HTTP state management mechanism specifies a way to
  * create a stateful session with HTTP requests and responses.
  *
- * <p>A system-wide CookieHandler that to used by the HTTP protocol
- * handler can be registered by doing a
- * CookieHandler.setDefault(CookieHandler). The currently registered
+ * <p> A system-wide CookieHandler to be used by the {@linkplain
+ * HttpURLConnection HTTP URL stream protocol handler} can be registered by
+ * doing a CookieHandler.setDefault(CookieHandler). The currently registered
  * CookieHandler can be retrieved by calling
  * CookieHandler.getDefault().
  *
--- a/src/java.base/share/classes/java/net/doc-files/net-properties.html	Tue Apr 17 15:39:20 2018 +0200
+++ b/src/java.base/share/classes/java/net/doc-files/net-properties.html	Tue Apr 17 08:54:17 2018 -0700
@@ -156,7 +156,7 @@
 	checked only once at startup.</P>
 </UL>
 <a id="MiscHTTP"></a>
-<H2>Misc HTTP properties</H2>
+<H2>Misc HTTP URL stream protocol handler properties</H2>
 <UL>
 	<LI><P><B>http.agent</B> (default: &ldquo;Java/&lt;version&gt;&rdquo;)<BR>
 	Defines the string sent in the User-Agent request header in http
--- a/src/java.base/share/classes/java/net/package-info.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/src/java.base/share/classes/java/net/package-info.java	Tue Apr 17 08:54:17 2018 -0700
@@ -121,8 +121,8 @@
  *            underlying protocol handlers like http or https.</li>
  *       <li>{@link java.net.HttpURLConnection} is a subclass of URLConnection
  *            and provides some additional functionalities specific to the
- *            HTTP protocol. This API has been superceded by the newer
-              HTTP client API described in the previous section.</li>
+ *            HTTP protocol. This API has been superseded by the newer
+ *            {@linkplain java.net.http HTTP Client API}.</li>
  * </ul>
  * <p>The recommended usage is to use {@link java.net.URI} to identify
  *    resources, then convert it into a {@link java.net.URL} when it is time to
--- a/src/java.base/share/classes/module-info.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/src/java.base/share/classes/module-info.java	Tue Apr 17 08:54:17 2018 -0700
@@ -171,7 +171,7 @@
         jdk.attach,
         jdk.charsets,
         jdk.compiler,
-        jdk.incubator.httpclient,
+        java.net.http,
         jdk.jdeps,
         jdk.jlink,
         jdk.jshell,
@@ -204,12 +204,11 @@
         jdk.internal.jvmstat;
     exports jdk.internal.vm.annotation to
         jdk.unsupported,
-        jdk.internal.vm.ci,
-        jdk.incubator.httpclient;
+        jdk.internal.vm.ci;
     exports jdk.internal.util.jar to
         jdk.jartool;
     exports sun.net to
-        jdk.incubator.httpclient,
+        java.net.http,
         jdk.naming.dns;
     exports sun.net.ext to
         jdk.net;
@@ -219,10 +218,10 @@
     exports sun.net.util to
         java.desktop,
         jdk.jconsole,
-        jdk.incubator.httpclient;
+        java.net.http;
     exports sun.net.www to
         java.desktop,
-        jdk.incubator.httpclient,
+        java.net.http,
         jdk.jartool;
     exports sun.net.www.protocol.http to
         java.security.jgss;
--- a/src/java.base/share/lib/security/default.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/src/java.base/share/lib/security/default.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -14,6 +14,23 @@
 };
 
 
+grant codeBase "jrt:/java.net.http" {
+    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
+    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
+    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
+    permission java.net.SocketPermission "*","connect,resolve";
+    permission java.net.URLPermission "http:*","*:*";
+    permission java.net.URLPermission "https:*","*:*";
+    permission java.net.URLPermission "ws:*","*:*";
+    permission java.net.URLPermission "wss:*","*:*";
+    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
+    // For request/response body processors, fromFile, asFile
+    permission java.io.FilePermission "<<ALL FILES>>","read,write,delete";
+    permission java.util.PropertyPermission "*","read";
+    permission java.net.NetPermission "getProxySelector";
+};
+
 grant codeBase "jrt:/java.scripting" {
     permission java.security.AllPermission;
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpClient.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,605 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.CookieHandler;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URLPermission;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import jdk.internal.net.http.HttpClientBuilderImpl;
+
+/**
+ * An HTTP Client.
+ *
+ * <p> An {@code HttpClient} can be used to send {@linkplain HttpRequest
+ * requests} and retrieve their {@linkplain HttpResponse responses}. An {@code
+ * HttpClient} is created through a {@link HttpClient#newBuilder() builder}. The
+ * builder can be used to configure per-client state, like: the preferred
+ * protocol version ( HTTP/1.1 or HTTP/2 ), whether to follow redirects, a
+ * proxy, an authenticator, etc. Once built, an {@code HttpClient} is immutable,
+ * and can be used to send multiple requests.
+ *
+ * <p> An {@code HttpClient} provides configuration information, and resource
+ * sharing, for all requests send through it.
+ *
+ * <p> A {@link BodyHandler BodyHandler} must be supplied for each {@link
+ * HttpRequest} sent. The {@code BodyHandler} determines how to handle the
+ * response body, if any. Once an {@link HttpResponse} is received, the
+ * headers, response code, and body (typically) are available. Whether the
+ * response body bytes have been read or not depends on the type, {@code T}, of
+ * the response body.
+ *
+ * <p> Requests can be sent either synchronously or asynchronously:
+ * <ul>
+ *     <li>{@link HttpClient#send(HttpRequest, BodyHandler)} blocks
+ *     until the request has been sent and the response has been received.</li>
+ *
+ *     <li>{@link HttpClient#sendAsync(HttpRequest, BodyHandler)} sends the
+ *     request and receives the response asynchronously. The {@code sendAsync}
+ *     method returns immediately with a {@link CompletableFuture
+ *     CompletableFuture}&lt;{@link HttpResponse}&gt;. The {@code
+ *     CompletableFuture} completes when the response becomes available. The
+ *     returned {@code CompletableFuture} can be combined in different ways to
+ *     declare dependencies among several asynchronous tasks.</li>
+ * </ul>
+ *
+ * <p><b>Synchronous Example</b>
+ * <pre>{@code    HttpClient client = HttpClient.newBuilder()
+ *        .version(Version.HTTP_1_1)
+ *        .followRedirects(Redirect.NORMAL)
+ *        .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
+ *        .authenticator(Authenticator.getDefault())
+ *        .build();
+ *   HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+ *   System.out.println(response.statusCode());
+ *   System.out.println(response.body());  }</pre>
+ *
+ * <p><b>Asynchronous Example</b>
+ * <pre>{@code    HttpRequest request = HttpRequest.newBuilder()
+ *        .uri(URI.create("https://foo.com/"))
+ *        .timeout(Duration.ofMinutes(1))
+ *        .header("Content-Type", "application/json")
+ *        .POST(BodyPublishers.ofFile(Paths.get("file.json")))
+ *        .build();
+ *   client.sendAsync(request, BodyHandlers.ofString())
+ *        .thenApply(HttpResponse::body)
+ *        .thenAccept(System.out::println);  }</pre>
+ *
+ * <p> <a id="securitychecks"></a><b>Security checks</b></a>
+ *
+ * <p> If a security manager is present then security checks are performed by
+ * the HTTP Client's sending methods. An appropriate {@link URLPermission} is
+ * required to access the destination server, and proxy server if one has
+ * been configured. The form of the {@code URLPermission} required to access a
+ * proxy has a {@code method} parameter of {@code "CONNECT"} (for all kinds of
+ * proxying) and a {@code URL} string of the form {@code "socket://host:port"}
+ * where host and port specify the proxy's address.
+ *
+ * @implNote If an explicit {@linkplain HttpClient.Builder#executor(Executor)
+ * executor} has not been set for an {@code HttpClient}, and a security manager
+ * has been installed, then the default executor will execute asynchronous and
+ * dependent tasks in a context that is granted no permissions. Custom
+ * {@linkplain HttpRequest.BodyPublisher request body publishers}, {@linkplain
+ * HttpResponse.BodyHandler response body handlers}, {@linkplain
+ * HttpResponse.BodySubscriber response body subscribers}, and {@linkplain
+ * WebSocket.Listener WebSocket Listeners}, if executing operations that require
+ * privileges, should do so within an appropriate {@linkplain
+ * AccessController#doPrivileged(PrivilegedAction) privileged context}.
+ *
+ * @since 11
+ */
+public abstract class HttpClient {
+
+    /**
+     * Creates an HttpClient.
+     */
+    protected HttpClient() {}
+
+    /**
+     * Returns a new {@code HttpClient} with default settings.
+     *
+     * <p> Equivalent to {@code newBuilder().build()}.
+     *
+     * <p> The default settings include: the "GET" request method, a preference
+     * of {@linkplain HttpClient.Version#HTTP_2 HTTP/2}, a redirection policy of
+     * {@linkplain Redirect#NEVER NEVER}, the {@linkplain
+     * ProxySelector#getDefault() default proxy selector}, and the {@linkplain
+     * SSLContext#getDefault() default SSL context}.
+     *
+     * @implNote The system-wide default values are retrieved at the time the
+     * {@code HttpClient} instance is constructed. Changing the system-wide
+     * values after an {@code HttpClient} instance has been built, for
+     * instance, by calling {@link ProxySelector#setDefault(ProxySelector)}
+     * or {@link SSLContext#setDefault(SSLContext)}, has no effect on already
+     * built instances.
+     *
+     * @return a new HttpClient
+     */
+    public static HttpClient newHttpClient() {
+        return newBuilder().build();
+    }
+
+    /**
+     * Creates a new {@code HttpClient} builder.
+     *
+     * @return an {@code HttpClient.Builder}
+     */
+    public static Builder newBuilder() {
+        return new HttpClientBuilderImpl();
+    }
+
+    /**
+     * A builder of {@linkplain HttpClient HTTP Clients}.
+     *
+     * <p> Builders are created by invoking {@link HttpClient#newBuilder()
+     * newBuilder}. Each of the setter methods modifies the state of the builder
+     * and returns the same instance. Builders are not thread-safe and should not be
+     * used concurrently from multiple threads without external synchronization.
+     *
+     * @since 11
+     */
+    public interface Builder {
+
+        /**
+         * A proxy selector that always return {@link Proxy#NO_PROXY} implying
+         * a direct connection.
+         *
+         * <p> This is a convenience object that can be passed to
+         * {@link #proxy(ProxySelector)} in order to build an instance of
+         * {@link HttpClient} that uses no proxy.
+         */
+        public static final ProxySelector NO_PROXY = ProxySelector.of(null);
+
+
+        /**
+         * Sets a cookie handler.
+         *
+         * @param cookieHandler the cookie handler
+         * @return this builder
+         */
+        public Builder cookieHandler(CookieHandler cookieHandler);
+
+        /**
+         * Sets an {@code SSLContext}.
+         *
+         * <p> If this method is not invoked prior to {@linkplain #build()
+         * building}, then newly built clients will use the {@linkplain
+         * SSLContext#getDefault() default context}, which is normally adequate
+         * for client applications that do not need to specify protocols, or
+         * require client authentication.
+         *
+         * @param sslContext the SSLContext
+         * @return this builder
+         */
+        public Builder sslContext(SSLContext sslContext);
+
+        /**
+         * Sets an {@code SSLParameters}.
+         *
+         * <p> If this method is not invoked prior to {@linkplain #build()
+         * building}, then newly built clients will use a default,
+         * implementation specific, set of parameters.
+         *
+         * <p> Some parameters which are used internally by the HTTP Client
+         * implementation (such as the application protocol list) should not be
+         * set by callers, as they may be ignored. The contents of the given
+         * object are copied.
+         *
+         * @param sslParameters the SSLParameters
+         * @return this builder
+         */
+        public Builder sslParameters(SSLParameters sslParameters);
+
+        /**
+         * Sets the executor to be used for asynchronous and dependent tasks.
+         *
+         * <p> If this method is not invoked prior to {@linkplain #build()
+         * building}, a default executor is created for each newly built {@code
+         * HttpClient}. The default executor uses a {@linkplain
+         * Executors#newCachedThreadPool(ThreadFactory) cached thread pool},
+         * with a custom thread factory.
+         *
+         * @implNote If a security manager has been installed, the thread
+         * factory creates threads that run with an access control context that
+         * has no permissions.
+         *
+         * @param executor the Executor
+         * @return this builder
+         */
+        public Builder executor(Executor executor);
+
+        /**
+         * Specifies whether requests will automatically follow redirects issued
+         * by the server.
+         *
+         * <p> If this method is not invoked prior to {@linkplain #build()
+         * building}, then newly built clients will use a default redirection
+         * policy of {@link Redirect#NEVER NEVER}.
+         *
+         * @param policy the redirection policy
+         * @return this builder
+         */
+        public Builder followRedirects(Redirect policy);
+
+        /**
+         * Requests a specific HTTP protocol version where possible.
+         *
+         * <p> If this method is not invoked prior to {@linkplain #build()
+         * building}, then newly built clients will prefer {@linkplain
+         * Version#HTTP_2 HTTP/2}.
+         *
+         * <p> If set to {@linkplain Version#HTTP_2 HTTP/2}, then each request
+         * will attempt to upgrade to HTTP/2. If the upgrade succeeds, then the
+         * response to this request will use HTTP/2 and all subsequent requests
+         * and responses to the same
+         * <a href="https://tools.ietf.org/html/rfc6454#section-4">origin server</a>
+         * will use HTTP/2. If the upgrade fails, then the response will be
+         * handled using HTTP/1.1
+         *
+         * @implNote Constraints may also affect the selection of protocol version.
+         * For example, if HTTP/2 is requested through a proxy, and if the implementation
+         * does not support this mode, then HTTP/1.1 may be used
+         *
+         * @param version the requested HTTP protocol version
+         * @return this builder
+         */
+        public Builder version(HttpClient.Version version);
+
+        /**
+         * Sets the default priority for any HTTP/2 requests sent from this
+         * client. The value provided must be between {@code 1} and {@code 256}
+         * (inclusive).
+         *
+         * @param priority the priority weighting
+         * @return this builder
+         * @throws IllegalArgumentException if the given priority is out of range
+         */
+        public Builder priority(int priority);
+
+        /**
+         * Sets a {@link java.net.ProxySelector}.
+         *
+         * @apiNote {@link ProxySelector#of(InetSocketAddress) ProxySelector::of}
+         * provides a {@code ProxySelector} which uses a single proxy for all
+         * requests. The system-wide proxy selector can be retrieved by
+         * {@link ProxySelector#getDefault()}.
+         *
+         * @implNote
+         * If this method is not invoked prior to {@linkplain #build() building},
+         * then newly built clients will use the {@linkplain
+         * ProxySelector#getDefault() default proxy selector}, which is usually
+         * adequate for client applications. The default proxy selector supports
+         * a set of system properties</a> related to
+         * <a href="{@docRoot}/java.base/java/net/doc-files/net-properties.html#Proxies">
+         * proxy settings</a>. This default behavior can be disabled by
+         * supplying an explicit proxy selector, such as {@link #NO_PROXY} or
+         * one returned by {@link ProxySelector#of(InetSocketAddress)
+         * ProxySelector::of}, before {@linkplain #build() building}.
+         *
+         * @param proxySelector the ProxySelector
+         * @return this builder
+         */
+        public Builder proxy(ProxySelector proxySelector);
+
+        /**
+         * Sets an authenticator to use for HTTP authentication.
+         *
+         * @param authenticator the Authenticator
+         * @return this builder
+         */
+        public Builder authenticator(Authenticator authenticator);
+
+        /**
+         * Returns a new {@link HttpClient} built from the current state of this
+         * builder.
+         *
+         * @return a new {@code HttpClient}
+         */
+        public HttpClient build();
+    }
+
+
+    /**
+     * Returns an {@code Optional} containing this client's {@link
+     * CookieHandler}. If no {@code CookieHandler} was set in this client's
+     * builder, then the {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this client's {@code CookieHandler}
+     */
+    public abstract Optional<CookieHandler> cookieHandler();
+
+    /**
+     * Returns the follow redirects policy for this client. The default value
+     * for client's built by builders that do not specify a redirect policy is
+     * {@link HttpClient.Redirect#NEVER NEVER}.
+     *
+     * @return this client's follow redirects setting
+     */
+    public abstract Redirect followRedirects();
+
+    /**
+     * Returns an {@code Optional} containing the {@code ProxySelector}
+     * supplied to this client. If no proxy selector was set in this client's
+     * builder, then the {@code Optional} is empty.
+     *
+     * <p> Even though this method may return an empty optional, the {@code
+     * HttpClient} may still have a non-exposed {@linkplain
+     * Builder#proxy(ProxySelector) default proxy selector} that is
+     * used for sending HTTP requests.
+     *
+     * @return an {@code Optional} containing the proxy selector supplied
+     *        to this client.
+     */
+    public abstract Optional<ProxySelector> proxy();
+
+    /**
+     * Returns this client's {@code SSLContext}.
+     *
+     * <p> If no {@code SSLContext} was set in this client's builder, then the
+     * {@linkplain SSLContext#getDefault() default context} is returned.
+     *
+     * @return this client's SSLContext
+     */
+    public abstract SSLContext sslContext();
+
+    /**
+     * Returns a copy of this client's {@link SSLParameters}.
+     *
+     * <p> If no {@code SSLParameters} were set in the client's builder, then an
+     * implementation specific default set of parameters, that the client will
+     * use, is returned.
+     *
+     * @return this client's {@code SSLParameters}
+     */
+    public abstract SSLParameters sslParameters();
+
+    /**
+     * Returns an {@code Optional} containing the {@link Authenticator} set on
+     * this client. If no {@code Authenticator} was set in the client's builder,
+     * then the {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this client's {@code Authenticator}
+     */
+    public abstract Optional<Authenticator> authenticator();
+
+    /**
+     * Returns the preferred HTTP protocol version for this client. The default
+     * value is {@link HttpClient.Version#HTTP_2}
+     *
+     * @implNote Constraints may also affect the selection of protocol version.
+     * For example, if HTTP/2 is requested through a proxy, and if the
+     * implementation does not support this mode, then HTTP/1.1 may be used
+     *
+     * @return the HTTP protocol version requested
+     */
+    public abstract HttpClient.Version version();
+
+    /**
+     * Returns an {@code Optional} containing this client's {@link
+     * Executor}. If no {@code Executor} was set in the client's builder,
+     * then the {@code Optional} is empty.
+     *
+     * <p> Even though this method may return an empty optional, the {@code
+     * HttpClient} may still have an non-exposed {@linkplain
+     * HttpClient.Builder#executor(Executor) default executor} that is used for
+     * executing asynchronous and dependent tasks.
+     *
+     * @return an {@code Optional} containing this client's {@code Executor}
+     */
+    public abstract Optional<Executor> executor();
+
+    /**
+     * The HTTP protocol version.
+     *
+     * @since 11
+     */
+    public enum Version {
+
+        /**
+         * HTTP version 1.1
+         */
+        HTTP_1_1,
+
+        /**
+         * HTTP version 2
+         */
+        HTTP_2
+    }
+
+    /**
+     * Defines the automatic redirection policy.
+     *
+     * <p> The automatic redirection policy is checked whenever a {@code 3XX}
+     * response code is received. If redirection does not happen automatically,
+     * then the response, containing the  {@code 3XX} response code, is returned,
+     * where it can be handled manually.
+     *
+     * <p> {@code Redirect} policy is set via the {@linkplain
+     * HttpClient.Builder#followRedirects(Redirect) Builder.followRedirects}
+     * method.
+     *
+     * @implNote When automatic redirection occurs, the request method of the
+     * redirected request may be modified depending on the specific {@code 30X}
+     * status code, as specified in <a href="https://tools.ietf.org/html/rfc7231">
+     * RFC 7231</a>. In addition, the {@code 301} and {@code 302} status codes
+     * cause a {@code POST} request to be converted to a {@code GET} in the
+     * redirected request.
+     *
+     * @since 11
+     */
+    public enum Redirect {
+
+        /**
+         * Never redirect.
+         */
+        NEVER,
+
+        /**
+         * Always redirect.
+         */
+        ALWAYS,
+
+        /**
+         * Always redirect, except from HTTPS URLs to HTTP URLs.
+         */
+        NORMAL
+    }
+
+    /**
+     * Sends the given request using this client, blocking if necessary to get
+     * the response. The returned {@link HttpResponse}{@code <T>} contains the
+     * response status, headers, and body ( as handled by given response body
+     * handler ).
+     *
+     * @param <T> the response body type
+     * @param request the request
+     * @param responseBodyHandler the response body handler
+     * @return the response
+     * @throws IOException if an I/O error occurs when sending or receiving
+     * @throws InterruptedException if the operation is interrupted
+     * @throws IllegalArgumentException if the {@code request} argument is not
+     *         a request that could have been validly built as specified by {@link
+     *         HttpRequest.Builder HttpRequest.Builder}.
+     * @throws SecurityException If a security manager has been installed
+     *          and it denies {@link java.net.URLPermission access} to the
+     *          URL in the given request, or proxy if one is configured.
+     *          See <a href="#securitychecks">security checks</a> for further
+     *          information.
+     */
+    public abstract <T> HttpResponse<T>
+    send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)
+        throws IOException, InterruptedException;
+
+    /**
+     * Sends the given request asynchronously using this client with the given
+     * response body handler.
+     *
+     * <p> Equivalent to: {@code sendAsync(request, responseBodyHandler, null)}.
+     *
+     * @param <T> the response body type
+     * @param request the request
+     * @param responseBodyHandler the response body handler
+     * @return a {@code CompletableFuture<HttpResponse<T>>}
+     * @throws IllegalArgumentException if the {@code request} argument is not
+     *         a request that could have been validly built as specified by {@link
+     *         HttpRequest.Builder HttpRequest.Builder}.
+     */
+    public abstract <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest request,
+              BodyHandler<T> responseBodyHandler);
+
+    /**
+     * Sends the given request asynchronously using this client with the given
+     * response body handler and push promise handler.
+     *
+     * <p> The returned completable future, if completed successfully, completes
+     * with an {@link HttpResponse}{@code <T>} that contains the response status,
+     * headers, and body ( as handled by given response body handler ).
+     *
+     * <p> {@linkplain PushPromiseHandler Push promises} received, if any, are
+     * handled by the given {@code pushPromiseHandler}. A {@code null} valued
+     * {@code pushPromiseHandler} rejects any push promises.
+     *
+     * <p> The returned completable future completes exceptionally with:
+     * <ul>
+     * <li>{@link IOException} - if an I/O error occurs when sending or receiving</li>
+     * <li>{@link SecurityException} - If a security manager has been installed
+     *          and it denies {@link java.net.URLPermission access} to the
+     *          URL in the given request, or proxy if one is configured.
+     *          See <a href="#securitychecks">security checks</a> for further
+     *          information.</li>
+     * </ul>
+     *
+     * @param <T> the response body type
+     * @param request the request
+     * @param responseBodyHandler the response body handler
+     * @param pushPromiseHandler push promise handler, may be null
+     * @return a {@code CompletableFuture<HttpResponse<T>>}
+     * @throws IllegalArgumentException if the {@code request} argument is not
+     *         a request that could have been validly built as specified by {@link
+     *         HttpRequest.Builder HttpRequest.Builder}.
+     */
+    public abstract <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest request,
+              BodyHandler<T> responseBodyHandler,
+              PushPromiseHandler<T> pushPromiseHandler);
+
+    /**
+     * Creates a new {@code WebSocket} builder (optional operation).
+     *
+     * <p> <b>Example</b>
+     * <pre>{@code    HttpClient client = HttpClient.newHttpClient();
+     *   CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+     *           .buildAsync(URI.create("ws://websocket.example.com"), listener); }</pre>
+     *
+     * <p> Finer control over the WebSocket Opening Handshake can be achieved
+     * by using a custom {@code HttpClient}.
+     *
+     * <p> <b>Example</b>
+     * <pre>{@code    InetSocketAddress addr = new InetSocketAddress("proxy.example.com", 80);
+     *   HttpClient client = HttpClient.newBuilder()
+     *           .proxy(ProxySelector.of(addr))
+     *           .build();
+     *   CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+     *           .buildAsync(URI.create("ws://websocket.example.com"), listener); }</pre>
+     *
+     * @implSpec The default implementation of this method throws
+     * {@code UnsupportedOperationException}. Clients obtained through
+     * {@link HttpClient#newHttpClient()} or {@link HttpClient#newBuilder()}
+     * return a {@code WebSocket} builder.
+     *
+     * @implNote Both builder and {@code WebSocket}s created with it operate in
+     * a non-blocking fashion. That is, their methods do not block before
+     * returning a {@code CompletableFuture}. Asynchronous tasks are executed in
+     * this {@code HttpClient}'s executor.
+     *
+     * <p> When a {@code CompletionStage} returned from
+     * {@link WebSocket.Listener#onClose Listener.onClose} completes,
+     * the {@code WebSocket} will send a Close message that has the same code
+     * the received message has and an empty reason.
+     *
+     * @return a {@code WebSocket.Builder}
+     * @throws UnsupportedOperationException
+     *         if this {@code HttpClient} does not provide WebSocket support
+     */
+    public WebSocket.Builder newWebSocketBuilder() {
+        throw new UnsupportedOperationException();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpHeaders.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,168 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalLong;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A read-only view of a set of HTTP headers.
+ *
+ * <p> An {@code HttpHeaders} is not created directly, but rather returned from
+ * an {@link HttpResponse HttpResponse}. Specific HTTP headers can be set for
+ * {@linkplain HttpRequest requests} through the one of the request builder's
+ * {@link HttpRequest.Builder#header(String, String) headers} methods.
+ *
+ * <p> The methods of this class ( that accept a String header name ), and the
+ * Map returned by the {@link #map() map} method, operate without regard to
+ * case when retrieving the header value.
+ *
+ * <p> {@code HttpHeaders} instances are immutable.
+ *
+ * @since 11
+ */
+public abstract class HttpHeaders {
+
+    /**
+     * Creates an HttpHeaders.
+     */
+    protected HttpHeaders() {}
+
+    /**
+     * Returns an {@link Optional} containing the first value of the given named
+     * (and possibly multi-valued) header. If the header is not present, then
+     * the returned {@code Optional} is empty.
+     *
+     * @implSpec
+     * The default implementation invokes
+     * {@code allValues(name).stream().findFirst()}
+     *
+     * @param name the header name
+     * @return an {@code Optional<String>} for the first named value
+     */
+    public Optional<String> firstValue(String name) {
+        return allValues(name).stream().findFirst();
+    }
+
+    /**
+     * Returns an {@link OptionalLong} containing the first value of the
+     * named header field. If the header is not present, then the Optional is
+     * empty. If the header is present but contains a value that does not parse
+     * as a {@code Long} value, then an exception is thrown.
+     *
+     * @implSpec
+     * The default implementation invokes
+     * {@code allValues(name).stream().mapToLong(Long::valueOf).findFirst()}
+     *
+     * @param name the header name
+     * @return  an {@code OptionalLong}
+     * @throws NumberFormatException if a value is found, but does not parse as
+     *                               a Long
+     */
+    public OptionalLong firstValueAsLong(String name) {
+        return allValues(name).stream().mapToLong(Long::valueOf).findFirst();
+    }
+
+    /**
+     * Returns an unmodifiable List of all of the values of the given named
+     * header. Always returns a List, which may be empty if the header is not
+     * present.
+     *
+     * @implSpec
+     * The default implementation invokes, among other things, the
+     * {@code map().get(name)} to retrieve the list of header values.
+     *
+     * @param name the header name
+     * @return a List of String values
+     */
+    public List<String> allValues(String name) {
+        requireNonNull(name);
+        List<String> values = map().get(name);
+        // Making unmodifiable list out of empty in order to make a list which
+        // throws UOE unconditionally
+        return values != null ? values : unmodifiableList(emptyList());
+    }
+
+    /**
+     * Returns an unmodifiable multi Map view of this HttpHeaders.
+     *
+     * @return the Map
+     */
+    public abstract Map<String, List<String>> map();
+
+    /**
+     * Tests this HTTP headers instance for equality with the given object.
+     *
+     * <p> If the given object is not an {@code HttpHeaders} then this
+     * method returns {@code false}. Two HTTP headers are equal if each
+     * of their corresponding {@linkplain #map() maps} are equal.
+     *
+     * <p> This method satisfies the general contract of the {@link
+     * Object#equals(Object) Object.equals} method.
+     *
+     * @param obj the object to which this object is to be compared
+     * @return {@code true} if, and only if, the given object is an {@code
+     *         HttpHeaders} that is equal to this HTTP headers
+     */
+    public final boolean equals(Object obj) {
+        if (!(obj instanceof HttpHeaders))
+            return false;
+        HttpHeaders that = (HttpHeaders)obj;
+        return this.map().equals(that.map());
+    }
+
+    /**
+     * Computes a hash code for this HTTP headers instance.
+     *
+     * <p> The hash code is based upon the components of the HTTP headers
+     * {@link #map() map}, and satisfies the general contract of the
+     * {@link Object#hashCode Object.hashCode} method.
+     *
+     * @return the hash-code value for this HTTP headers
+     */
+    public final int hashCode() {
+        return map().hashCode();
+    }
+
+    /**
+     * Returns this HTTP headers as a string.
+     *
+     * @return a string describing the HTTP headers
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(super.toString()).append(" { ");
+        sb.append(map());
+        sb.append(" }");
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,650 @@
+/*
+ * 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;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.Flow;
+import java.util.function.Supplier;
+import jdk.internal.net.http.HttpRequestBuilderImpl;
+import jdk.internal.net.http.RequestPublishers;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * An HTTP request.
+ *
+ * <p> An {@code HttpRequest} instance is built through an {@code HttpRequest}
+ * {@linkplain HttpRequest.Builder builder}. An {@code HttpRequest} builder
+ * is obtained from one of the {@link HttpRequest#newBuilder(URI) newBuilder}
+ * methods. A request's {@link URI}, headers, and body can be set. Request
+ * bodies are provided through a {@link BodyPublisher BodyPublisher} supplied
+ * to one of the {@link Builder#POST(BodyPublisher) POST},
+ * {@link Builder#PUT(BodyPublisher) PUT} or
+ * {@link Builder#method(String,BodyPublisher) method} methods.
+ * Once all required parameters have been set in the builder, {@link
+ * Builder#build() build} will return the {@code HttpRequest}. Builders can be
+ * copied and modified many times in order to build multiple related requests
+ * that differ in some parameters.
+ *
+ * <p> The following is an example of a GET request that prints the response
+ * body as a String:
+ *
+ * <pre>{@code    HttpClient client = HttpClient.newHttpClient();
+ *   HttpRequest request = HttpRequest.newBuilder()
+ *         .uri(URI.create("http://foo.com/"))
+ *         .build();
+ *   client.sendAsync(request, BodyHandlers.ofString())
+ *         .thenApply(HttpResponse::body)
+ *         .thenAccept(System.out::println)
+ *         .join(); }</pre>
+ *
+ * <p>The class {@link BodyPublishers BodyPublishers} provides implementations
+ * of many common publishers. Alternatively, a custom {@code BodyPublisher}
+ * implementation can be used.
+ *
+ * @since 11
+ */
+public abstract class HttpRequest {
+
+    /**
+     * Creates an HttpRequest.
+     */
+    protected HttpRequest() {}
+
+    /**
+     * A builder of {@linkplain HttpRequest HTTP requests}.
+     *
+     * <p> Instances of {@code HttpRequest.Builder} are created by calling {@link
+     * HttpRequest#newBuilder(URI)} or {@link HttpRequest#newBuilder()}.
+     *
+     * <p> Each of the setter methods modifies the state of the builder
+     * and returns the same instance. The methods are not synchronized and
+     * should not be called from multiple threads without external
+     * synchronization. The {@link #build() build} method returns a new
+     * {@code HttpRequest} each time it is invoked. Once built an {@code
+     * HttpRequest} is immutable, and can be sent multiple times.
+     *
+     * <p> Note, that not all request headers may be set by user code. Some are
+     * restricted for security reasons and others such as the headers relating
+     * to authentication, redirection and cookie management may be managed by
+     * specific APIs rather than through directly user set headers.
+     *
+     * @since 11
+     */
+    public interface Builder {
+
+        /**
+         * Sets this {@code HttpRequest}'s request {@code URI}.
+         *
+         * @param uri the request URI
+         * @return this builder
+         * @throws IllegalArgumentException if the {@code URI} scheme is not
+         *         supported
+         */
+        public Builder uri(URI uri);
+
+        /**
+         * Requests the server to acknowledge the request before sending the
+         * body. This is disabled by default. If enabled, the server is
+         * requested to send an error response or a {@code 100 Continue}
+         * response before the client sends the request body. This means the
+         * request publisher for the request will not be invoked until this
+         * interim response is received.
+         *
+         * @param enable {@code true} if Expect continue to be sent
+         * @return this builder
+         */
+        public Builder expectContinue(boolean enable);
+
+        /**
+         * Sets the preferred {@link HttpClient.Version} for this request.
+         *
+         * <p> The corresponding {@link HttpResponse} should be checked for the
+         * version that was actually used. If the version is not set in a
+         * request, then the version requested will be that of the sending
+         * {@link HttpClient}.
+         *
+         * @param version the HTTP protocol version requested
+         * @return this builder
+         */
+        public Builder version(HttpClient.Version version);
+
+        /**
+         * Adds the given name value pair to the set of headers for this request.
+         * The given value is added to the list of values for that name.
+         *
+         * @implNote An implementation may choose to restrict some header names
+         *           or values, as the HTTP Client may determine their value itself.
+         *           For example, "Content-Length", which will be determined by
+         *           the request Publisher. In such a case, an implementation of
+         *           {@code HttpRequest.Builder} may choose to throw an
+         *           {@code IllegalArgumentException} if such a header is passed
+         *           to the builder.
+         *
+         * @param name the header name
+         * @param value the header value
+         * @return this builder
+         * @throws IllegalArgumentException if the header name or value is not
+         *         valid, see <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
+         *         RFC 7230 section-3.2</a>, or the header name or value is restricted
+         *         by the implementation.
+         */
+        public Builder header(String name, String value);
+
+        /**
+         * Adds the given name value pairs to the set of headers for this
+         * request. The supplied {@code String} instances must alternate as
+         * header names and header values.
+         * To add several values to the same name then the same name must
+         * be supplied with each new value.
+         *
+         * @param headers the list of name value pairs
+         * @return this builder
+         * @throws IllegalArgumentException if there are an odd number of
+         *         parameters, or if a header name or value is not valid, see
+         *         <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
+         *         RFC 7230 section-3.2</a>, or a header name or value is
+         *         {@linkplain #header(String, String) restricted} by the
+         *         implementation.
+         */
+        public Builder headers(String... headers);
+
+        /**
+         * Sets a timeout for this request. If the response is not received
+         * within the specified timeout then an {@link HttpTimeoutException} is
+         * thrown from {@link HttpClient#send(java.net.http.HttpRequest,
+         * java.net.http.HttpResponse.BodyHandler) HttpClient::send} or
+         * {@link HttpClient#sendAsync(java.net.http.HttpRequest,
+         * java.net.http.HttpResponse.BodyHandler) HttpClient::sendAsync}
+         * completes exceptionally with an {@code HttpTimeoutException}. The effect
+         * of not setting a timeout is the same as setting an infinite Duration, ie.
+         * block forever.
+         *
+         * @param duration the timeout duration
+         * @return this builder
+         * @throws IllegalArgumentException if the duration is non-positive
+         */
+        public abstract Builder timeout(Duration duration);
+
+        /**
+         * Sets the given name value pair to the set of headers for this
+         * request. This overwrites any previously set values for name.
+         *
+         * @param name the header name
+         * @param value the header value
+         * @return this builder
+         * @throws IllegalArgumentException if the header name or value is not valid,
+         *         see <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
+         *         RFC 7230 section-3.2</a>, or the header name or value is
+         *         {@linkplain #header(String, String) restricted} by the
+         *         implementation.
+         */
+        public Builder setHeader(String name, String value);
+
+        /**
+         * Sets the request method of this builder to GET.
+         * This is the default.
+         *
+         * @return this builder
+         */
+        public Builder GET();
+
+        /**
+         * Sets the request method of this builder to POST and sets its
+         * request body publisher to the given value.
+         *
+         * @param bodyPublisher the body publisher
+         *
+         * @return this builder
+         */
+        public Builder POST(BodyPublisher bodyPublisher);
+
+        /**
+         * Sets the request method of this builder to PUT and sets its
+         * request body publisher to the given value.
+         *
+         * @param bodyPublisher the body publisher
+         *
+         * @return this builder
+         */
+        public Builder PUT(BodyPublisher bodyPublisher);
+
+        /**
+         * Sets the request method of this builder to DELETE.
+         *
+         * @return this builder
+         */
+        public Builder DELETE();
+
+        /**
+         * Sets the request method and request body of this builder to the
+         * given values.
+         *
+         * @apiNote The {@link BodyPublishers#noBody() noBody} request
+         * body publisher can be used where no request body is required or
+         * appropriate. Whether a method is restricted, or not, is
+         * implementation specific. For example, some implementations may choose
+         * to restrict the {@code CONNECT} method.
+         *
+         * @param method the method to use
+         * @param bodyPublisher the body publisher
+         * @return this builder
+         * @throws IllegalArgumentException if the method name is not
+         *         valid, see <a href="https://tools.ietf.org/html/rfc7230#section-3.1.1">
+         *         RFC 7230 section-3.1.1</a>, or the method is restricted by the
+         *         implementation.
+         */
+        public Builder method(String method, BodyPublisher bodyPublisher);
+
+        /**
+         * Builds and returns an {@link HttpRequest}.
+         *
+         * @return a new {@code HttpRequest}
+         * @throws IllegalStateException if a URI has not been set
+         */
+        public HttpRequest build();
+
+        /**
+         * Returns an exact duplicate copy of this {@code Builder} based on
+         * current state. The new builder can then be modified independently of
+         * this builder.
+         *
+         * @return an exact copy of this builder
+         */
+        public Builder copy();
+    }
+
+    /**
+     * Creates an {@code HttpRequest} builder with the given URI.
+     *
+     * @param uri the request URI
+     * @return a new request builder
+     * @throws IllegalArgumentException if the URI scheme is not supported.
+     */
+    public static HttpRequest.Builder newBuilder(URI uri) {
+        return new HttpRequestBuilderImpl(uri);
+    }
+
+    /**
+     * Creates an {@code HttpRequest} builder.
+     *
+     * @return a new request builder
+     */
+    public static HttpRequest.Builder newBuilder() {
+        return new HttpRequestBuilderImpl();
+    }
+
+    /**
+     * Returns an {@code Optional} containing the {@link BodyPublisher} set on
+     * this request. If no {@code BodyPublisher} was set in the requests's
+     * builder, then the {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this request's {@code BodyPublisher}
+     */
+    public abstract Optional<BodyPublisher> bodyPublisher();
+
+    /**
+     * Returns the request method for this request. If not set explicitly,
+     * the default method for any request is "GET".
+     *
+     * @return this request's method
+     */
+    public abstract String method();
+
+    /**
+     * Returns an {@code Optional} containing this request's timeout duration.
+     * If the timeout duration was not set in the request's builder, then the
+     * {@code Optional} is empty.
+     *
+     * @return an {@code Optional} containing this request's timeout duration
+     */
+    public abstract Optional<Duration> timeout();
+
+    /**
+     * Returns this request's {@linkplain HttpRequest.Builder#expectContinue(boolean)
+     * expect continue} setting.
+     *
+     * @return this request's expect continue setting
+     */
+    public abstract boolean expectContinue();
+
+    /**
+     * Returns this request's {@code URI}.
+     *
+     * @return this request's URI
+     */
+    public abstract URI uri();
+
+    /**
+     * Returns an {@code Optional} containing the HTTP protocol version that
+     * will be requested for this {@code HttpRequest}. If the version was not
+     * set in the request's builder, then the {@code Optional} is empty.
+     * In that case, the version requested will be that of the sending
+     * {@link HttpClient}. The corresponding {@link HttpResponse} should be
+     * queried to determine the version that was actually used.
+     *
+     * @return HTTP protocol version
+     */
+    public abstract Optional<HttpClient.Version> version();
+
+    /**
+     * The (user-accessible) request headers that this request was (or will be)
+     * sent with.
+     *
+     * @return this request's HttpHeaders
+     */
+    public abstract HttpHeaders headers();
+
+    /**
+     * Tests this HTTP request instance for equality with the given object.
+     *
+     * <p> If the given object is not an {@code HttpRequest} then this
+     * method returns {@code false}. Two HTTP requests are equal if their URI,
+     * method, and headers fields are all equal.
+     *
+     * <p> This method satisfies the general contract of the {@link
+     * Object#equals(Object) Object.equals} method.
+     *
+     * @param obj the object to which this object is to be compared
+     * @return {@code true} if, and only if, the given object is an {@code
+     *         HttpRequest} that is equal to this HTTP request
+     */
+    @Override
+    public final boolean equals(Object obj) {
+       if (! (obj instanceof HttpRequest))
+           return false;
+       HttpRequest that = (HttpRequest)obj;
+       if (!that.method().equals(this.method()))
+           return false;
+       if (!that.uri().equals(this.uri()))
+           return false;
+       if (!that.headers().equals(this.headers()))
+           return false;
+       return true;
+    }
+
+    /**
+     * Computes a hash code for this HTTP request instance.
+     *
+     * <p> The hash code is based upon the HTTP request's URI, method, and
+     * header components, and satisfies the general contract of the
+     * {@link Object#hashCode Object.hashCode} method.
+     *
+     * @return the hash-code value for this HTTP request
+     */
+    public final int hashCode() {
+        return method().hashCode()
+                + uri().hashCode()
+                + headers().hashCode();
+    }
+
+    /**
+     * A {@code BodyPublisher} converts high-level Java objects into a flow of
+     * byte buffers suitable for sending as a request body.  The class
+     * {@link BodyPublishers BodyPublishers} provides implementations of many
+     * common publishers.
+     *
+     * <p> The {@code BodyPublisher} interface extends {@link Flow.Publisher
+     * Flow.Publisher&lt;ByteBuffer&gt;}, which means that a {@code BodyPublisher}
+     * acts as a publisher of {@linkplain ByteBuffer byte buffers}.
+     *
+     * <p> When sending a request that contains a body, the HTTP Client
+     * subscribes to the request's {@code BodyPublisher} in order to receive the
+     * flow of outgoing request body data. The normal semantics of {@link
+     * Flow.Subscriber} and {@link Flow.Publisher} are implemented by the HTTP
+     * Client and are expected from {@code BodyPublisher} implementations. Each
+     * outgoing request results in one HTTP Client {@code Subscriber}
+     * subscribing to the {@code BodyPublisher} in order to provide the sequence
+     * of byte buffers containing the request body. Instances of {@code
+     * ByteBuffer} published by the publisher must be allocated by the
+     * publisher, and must not be accessed after being published to the HTTP
+     * Client. These subscriptions complete normally when the request body is
+     * fully sent, and can be canceled or terminated early through error. If a
+     * request needs to be resent for any reason, then a new subscription is
+     * created which is expected to generate the same data as before.
+     *
+     * <p> A {@code BodyPublisher} that reports a {@linkplain #contentLength()
+     * content length} of {@code 0} may not be subscribed to by the HTTP Client,
+     * as it has effectively no data to publish.
+     *
+     * @see BodyPublishers
+     * @since 11
+     */
+    public interface BodyPublisher extends Flow.Publisher<ByteBuffer> {
+
+        /**
+         * Returns the content length for this request body. May be zero
+         * if no request body being sent, greater than zero for a fixed
+         * length content, or less than zero for an unknown content length.
+         *
+         * <p> This method may be invoked before the publisher is subscribed to.
+         * This method may be invoked more than once by the HTTP client
+         * implementation, and MUST return the same constant value each time.
+         *
+         * @return the content length for this request body, if known
+         */
+        long contentLength();
+    }
+
+    /**
+     * Implementations of {@link BodyPublisher BodyPublisher} that implement
+     * various useful publishers, such as publishing the request body from a
+     * String, or from a file.
+     *
+     * <p> The following are examples of using the predefined body publishers to
+     * convert common high-level Java objects into a flow of data suitable for
+     * sending as a request body:
+     *
+     *  <pre>{@code    // Request body from a String
+     *   HttpRequest request = HttpRequest.newBuilder()
+     *        .uri(URI.create("https://foo.com/"))
+     *        .header("Content-Type", "text/plain; charset=UTF-8")
+     *        .POST(BodyPublishers.ofString("some body text"))
+     *        .build();
+     *
+     *   // Request body from a File
+     *   HttpRequest request = HttpRequest.newBuilder()
+     *        .uri(URI.create("https://foo.com/"))
+     *        .header("Content-Type", "application/json")
+     *        .POST(BodyPublishers.ofFile(Paths.get("file.json")))
+     *        .build();
+     *
+     *   // Request body from a byte array
+     *   HttpRequest request = HttpRequest.newBuilder()
+     *        .uri(URI.create("https://foo.com/"))
+     *        .POST(BodyPublishers.ofByteArray(new byte[] { ... }))
+     *        .build(); }</pre>
+     *
+     * @since 11
+     */
+    public static class BodyPublishers {
+
+        private BodyPublishers() { }
+
+        /**
+         * Returns a request body publisher whose body is retrieved from the
+         * given {@code Flow.Publisher}. The returned request body publisher
+         * has an unknown content length.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodyPublisher} and {@code Flow.Publisher}, where the amount of
+         * request body that the publisher will publish is unknown.
+         *
+         * @param publisher the publisher responsible for publishing the body
+         * @return a BodyPublisher
+         */
+        public static BodyPublisher
+        fromPublisher(Flow.Publisher<? extends ByteBuffer> publisher) {
+            return new RequestPublishers.PublisherAdapter(publisher, -1L);
+        }
+
+        /**
+         * Returns a request body publisher whose body is retrieved from the
+         * given {@code Flow.Publisher}. The returned request body publisher
+         * has the given content length.
+         *
+         * <p> The given {@code contentLength} is a positive number, that
+         * represents the exact amount of bytes the {@code publisher} must
+         * publish.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodyPublisher} and {@code Flow.Publisher}, where the amount of
+         * request body that the publisher will publish is known.
+         *
+         * @param publisher the publisher responsible for publishing the body
+         * @param contentLength a positive number representing the exact
+         *                      amount of bytes the publisher will publish
+         * @throws IllegalArgumentException if the content length is
+         *                                  non-positive
+         * @return a BodyPublisher
+         */
+        public static BodyPublisher
+        fromPublisher(Flow.Publisher<? extends ByteBuffer> publisher,
+                      long contentLength) {
+            if (contentLength < 1)
+                throw new IllegalArgumentException("non-positive contentLength: "
+                        + contentLength);
+            return new RequestPublishers.PublisherAdapter(publisher, contentLength);
+        }
+
+        /**
+         * Returns a request body publisher whose body is the given {@code
+         * String}, converted using the {@link StandardCharsets#UTF_8 UTF_8}
+         * character set.
+         *
+         * @param body the String containing the body
+         * @return a BodyPublisher
+         */
+        public static BodyPublisher ofString(String body) {
+            return ofString(body, UTF_8);
+        }
+
+        /**
+         * Returns a request body publisher whose body is the given {@code
+         * String}, converted using the given character set.
+         *
+         * @param s the String containing the body
+         * @param charset the character set to convert the string to bytes
+         * @return a BodyPublisher
+         */
+        public static BodyPublisher ofString(String s, Charset charset) {
+            return new RequestPublishers.StringPublisher(s, charset);
+        }
+
+        /**
+         * A request body publisher that reads its data from an {@link
+         * InputStream}. A {@link Supplier} of {@code InputStream} is used in
+         * case the request needs to be repeated, as the content is not buffered.
+         * The {@code Supplier} may return {@code null} on subsequent attempts,
+         * in which case the request fails.
+         *
+         * @param streamSupplier a Supplier of open InputStreams
+         * @return a BodyPublisher
+         */
+        // TODO (spec): specify that the stream will be closed
+        public static BodyPublisher ofInputStream(Supplier<? extends InputStream> streamSupplier) {
+            return new RequestPublishers.InputStreamPublisher(streamSupplier);
+        }
+
+        /**
+         * Returns a request body publisher whose body is the given byte array.
+         *
+         * @param buf the byte array containing the body
+         * @return a BodyPublisher
+         */
+        public static BodyPublisher ofByteArray(byte[] buf) {
+            return new RequestPublishers.ByteArrayPublisher(buf);
+        }
+
+        /**
+         * Returns a request body publisher whose body is the content of the
+         * given byte array of {@code length} bytes starting from the specified
+         * {@code offset}.
+         *
+         * @param buf the byte array containing the body
+         * @param offset the offset of the first byte
+         * @param length the number of bytes to use
+         * @return a BodyPublisher
+         * @throws IndexOutOfBoundsException if the sub-range is defined to be
+         *                                   out-of-bounds
+         */
+        public static BodyPublisher ofByteArray(byte[] buf, int offset, int length) {
+            Objects.checkFromIndexSize(offset, length, buf.length);
+            return new RequestPublishers.ByteArrayPublisher(buf, offset, length);
+        }
+
+        /**
+         * A request body publisher that takes data from the contents of a File.
+         *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when the {@code BodyPublisher} is created. Care must be taken
+         * that the {@code BodyPublisher} is not shared with untrusted code.
+         *
+         * @param path the path to the file containing the body
+         * @return a BodyPublisher
+         * @throws java.io.FileNotFoundException if the path is not found
+         * @throws SecurityException if a security manager has been installed
+         *          and it denies {@link SecurityManager#checkRead(String)
+         *          read access} to the given file
+         */
+        public static BodyPublisher ofFile(Path path) throws FileNotFoundException {
+            Objects.requireNonNull(path);
+            return RequestPublishers.FilePublisher.create(path);
+        }
+
+        /**
+         * A request body publisher that takes data from an {@code Iterable}
+         * of byte arrays. An {@link Iterable} is provided which supplies
+         * {@link Iterator} instances. Each attempt to send the request results
+         * in one invocation of the {@code Iterable}.
+         *
+         * @param iter an Iterable of byte arrays
+         * @return a BodyPublisher
+         */
+        public static BodyPublisher ofByteArrays(Iterable<byte[]> iter) {
+            return new RequestPublishers.IterablePublisher(iter);
+        }
+
+        /**
+         * A request body publisher which sends no request body.
+         *
+         * @return a BodyPublisher which completes immediately and sends
+         *         no request body.
+         */
+        public static BodyPublisher noBody() {
+            return new RequestPublishers.EmptyPublisher();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpResponse.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,1315 @@
+/*
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.Flow.Subscription;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLSession;
+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 jdk.internal.net.http.ResponseSubscribers.PathSubscriber;
+import static java.nio.file.StandardOpenOption.*;
+import static jdk.internal.net.http.common.Utils.charsetFrom;
+
+/**
+ * An HTTP response.
+ *
+ * <p> An {@code HttpResponse} is not created directly, but rather returned as
+ * a result of sending an {@link HttpRequest}. An {@code HttpResponse} is
+ * made available when the response status code and headers have been received,
+ * and typically after the response body has also been completely received.
+ * Whether or not the {@code HttpResponse} is made available before the response
+ * body has been completely received depends on the {@link BodyHandler
+ * BodyHandler} provided when sending the {@code HttpRequest}.
+ *
+ * <p> This class provides methods for accessing the response status code,
+ * headers, the response body, and the {@code HttpRequest} corresponding
+ * to this response.
+ *
+ * <p> The following is an example of retrieving a response as a String:
+ *
+ * <pre>{@code    HttpResponse<String> response = client
+ *     .send(request, BodyHandlers.ofString()); }</pre>
+ *
+ * <p> The class {@link BodyHandlers BodyHandlers} provides implementations
+ * of many common response handlers. Alternatively, a custom {@code BodyHandler}
+ * implementation can be used.
+ *
+ * @param <T> the response body type
+ * @since 11
+ */
+public interface HttpResponse<T> {
+
+
+    /**
+     * Returns the status code for this response.
+     *
+     * @return the response code
+     */
+    public int statusCode();
+
+    /**
+     * Returns the {@link HttpRequest} corresponding to this response.
+     *
+     * <p> The returned {@code HttpRequest} may not be the initiating request
+     * provided when {@linkplain HttpClient#send(HttpRequest, BodyHandler)
+     * sending}. For example, if the initiating request was redirected, then the
+     * request returned by this method will have the redirected URI, which will
+     * be different from the initiating request URI.
+     *
+     * @see #previousResponse()
+     *
+     * @return the request
+     */
+    public HttpRequest request();
+
+    /**
+     * Returns an {@code Optional} containing the previous intermediate response
+     * if one was received. An intermediate response is one that is received
+     * as a result of redirection or authentication. If no previous response
+     * was received then an empty {@code Optional} is returned.
+     *
+     * @return an Optional containing the HttpResponse, if any.
+     */
+    public Optional<HttpResponse<T>> previousResponse();
+
+    /**
+     * Returns the received response headers.
+     *
+     * @return the response headers
+     */
+    public HttpHeaders headers();
+
+    /**
+     * Returns the body. Depending on the type of {@code T}, the returned body
+     * may represent the body after it was read (such as {@code byte[]}, or
+     * {@code String}, or {@code Path}) or it may represent an object with
+     * which the body is read, such as an {@link java.io.InputStream}.
+     *
+     * <p> If this {@code HttpResponse} was returned from an invocation of
+     * {@link #previousResponse()} then this method returns {@code null}
+     *
+     * @return the body
+     */
+    public T body();
+
+    /**
+     * Returns an {@link Optional} containing the {@link SSLSession} in effect
+     * for this response. Returns an empty {@code Optional} if this is not a
+     * <i>HTTPS</i> response.
+     *
+     * @return an {@code Optional} containing the {@code SSLSession} associated
+     *         with the response
+     */
+    public Optional<SSLSession> sslSession();
+
+    /**
+     * Returns the {@code URI} that the response was received from. This may be
+     * different from the request {@code URI} if redirection occurred.
+     *
+     * @return the URI of the response
+     */
+     public URI uri();
+
+    /**
+     * Returns the HTTP protocol version that was used for this response.
+     *
+     * @return HTTP protocol version
+     */
+    public HttpClient.Version version();
+
+
+    /**
+     * Initial response information supplied to a {@link BodyHandler BodyHandler}
+     * when a response is initially received and before the body is processed.
+     */
+    public interface ResponseInfo {
+        /**
+         * Provides the response status code.
+         * @return the response status code
+         */
+        public int statusCode();
+
+        /**
+         * Provides the response headers.
+         * @return the response headers
+         */
+        public HttpHeaders headers();
+
+        /**
+         * Provides the response protocol version.
+         * @return the response protocol version
+         */
+        public HttpClient.Version version();
+    }
+
+    /**
+     * A handler for response bodies.  The class {@link BodyHandlers BodyHandlers}
+     * provides implementations of many common body handlers.
+     *
+     * <p> The {@code BodyHandler} interface allows inspection of the response
+     * code and headers, before the actual response body is received, and is
+     * responsible for creating the response {@link BodySubscriber
+     * BodySubscriber}. The {@code BodySubscriber} consumes the actual response
+     * body bytes and, typically, converts them into a higher-level Java type.
+     *
+     * <p> A {@code BodyHandler} is a function that takes a {@link ResponseInfo
+     * ResponseInfo} object; and which returns a {@code BodySubscriber}. The
+     * {@code BodyHandler} is invoked when the response status code and headers
+     * are available, but before the response  body bytes are received.
+     *
+     * <p> The following example uses one of the {@linkplain BodyHandlers
+     * predefined body handlers} that always process the response body in the
+     * same way ( streams the response body to a file ).
+     *
+     * <pre>{@code   HttpRequest request = HttpRequest.newBuilder()
+     *        .uri(URI.create("http://www.foo.com/"))
+     *        .build();
+     *  client.sendAsync(request, BodyHandlers.ofFile(Paths.get("/tmp/f")))
+     *        .thenApply(HttpResponse::body)
+     *        .thenAccept(System.out::println); }</pre>
+     *
+     * Note, that even though the pre-defined handlers do not examine the
+     * response code, the response code and headers are always retrievable from
+     * the {@link HttpResponse}, when it is returned.
+     *
+     * <p> In the second example, the function returns a different subscriber
+     * depending on the status code.
+     * <pre>{@code   HttpRequest request = HttpRequest.newBuilder()
+     *        .uri(URI.create("http://www.foo.com/"))
+     *        .build();
+     *  BodyHandler<Path> bodyHandler = (rspInfo) -> rspInfo.statusCode() == 200
+     *                      ? BodySubscribers.ofFile(Paths.get("/tmp/f"))
+     *                      : BodySubscribers.replacing(Paths.get("/NULL"));
+     *  client.sendAsync(request, bodyHandler)
+     *        .thenApply(HttpResponse::body)
+     *        .thenAccept(System.out::println); }</pre>
+     *
+     * @param <T> the response body type
+     * @see BodyHandlers
+     * @since 11
+     */
+    @FunctionalInterface
+    public interface BodyHandler<T> {
+
+        /**
+         * Returns a {@link BodySubscriber BodySubscriber} considering the
+         * given response status code and headers. This method is invoked before
+         * the actual response body bytes are read and its implementation must
+         * return a {@link BodySubscriber BodySubscriber} to consume the response
+         * body bytes.
+         *
+         * <p> The response body can be discarded using one of {@link
+         * BodyHandlers#discarding() discarding} or {@link
+         * BodyHandlers#replacing(Object) replacing}.
+         *
+         * @param responseInfo the response info
+         * @return a body subscriber
+         */
+        public BodySubscriber<T> apply(ResponseInfo responseInfo);
+    }
+
+    /**
+     * Implementations of {@link BodyHandler BodyHandler} that implement various
+     * useful handlers, such as handling the response body as a String, or
+     * streaming the response body to a file.
+     *
+     * <p> These implementations do not examine the status code, meaning the
+     * body is always accepted. They typically return an equivalently named
+     * {@code BodySubscriber}. Alternatively, a custom handler can be used to
+     * examine the status code and headers, and return a different body
+     * subscriber, of the same type, as appropriate.
+     *
+     * <p>The following are examples of using the predefined body handlers to
+     * convert a flow of response body data into common high-level Java objects:
+     *
+     * <pre>{@code    // Receives the response body as a String
+     *   HttpResponse<String> response = client
+     *     .send(request, BodyHandlers.ofString());
+     *
+     *   // Receives the response body as a file
+     *   HttpResponse<Path> response = client
+     *     .send(request, BodyHandlers.ofFile(Paths.get("example.html")));
+     *
+     *   // Receives the response body as an InputStream
+     *   HttpResponse<InputStream> response = client
+     *     .send(request, BodyHandlers.ofInputStream());
+     *
+     *   // Discards the response body
+     *   HttpResponse<Void> response = client
+     *     .send(request, BodyHandlers.discarding());  }</pre>
+     *
+     * @since 11
+     */
+    public static class BodyHandlers {
+
+        private BodyHandlers() { }
+
+        /**
+         * Returns a response body handler that returns a {@link BodySubscriber
+         * BodySubscriber}{@code <Void>} obtained from {@link
+         * BodySubscribers#fromSubscriber(Subscriber)}, with the given
+         * {@code subscriber}.
+         *
+         * <p> The response body is not available through this, or the {@code
+         * HttpResponse} API, but instead all response body is forwarded to the
+         * given {@code subscriber}, which should make it available, if
+         * appropriate, through some other mechanism, e.g. an entry in a
+         * database, etc.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodySubscriber} and {@code Flow.Subscriber}.
+         *
+         * <p> For example:
+         * <pre> {@code  TextSubscriber subscriber = new TextSubscriber();
+         *  HttpResponse<Void> response = client.sendAsync(request,
+         *      BodyHandlers.fromSubscriber(subscriber)).join();
+         *  System.out.println(response.statusCode()); }</pre>
+         *
+         * @param subscriber the subscriber
+         * @return a response body handler
+         */
+        public static BodyHandler<Void>
+        fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber) {
+            Objects.requireNonNull(subscriber);
+            return (responseInfo) -> BodySubscribers.fromSubscriber(subscriber,
+                                                                       s -> null);
+        }
+
+        /**
+         * Returns a response body handler that returns a {@link BodySubscriber
+         * BodySubscriber}{@code <T>} obtained from {@link
+         * BodySubscribers#fromSubscriber(Subscriber, Function)}, with the
+         * given {@code subscriber} and {@code finisher} function.
+         *
+         * <p> The given {@code finisher} function is applied after the given
+         * subscriber's {@code onComplete} has been invoked. The {@code finisher}
+         * function is invoked with the given subscriber, and returns a value
+         * that is set as the response's body.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodySubscriber} and {@code Flow.Subscriber}.
+         *
+         * <p> For example:
+         * <pre> {@code  TextSubscriber subscriber = ...;  // accumulates bytes and transforms them into a String
+         *  HttpResponse<String> response = client.sendAsync(request,
+         *      BodyHandlers.fromSubscriber(subscriber, TextSubscriber::getTextResult)).join();
+         *  String text = response.body(); }</pre>
+         *
+         * @param <S> the type of the Subscriber
+         * @param <T> the type of the response body
+         * @param subscriber the subscriber
+         * @param finisher a function to be applied after the subscriber has completed
+         * @return a response body handler
+         */
+        public static <S extends Subscriber<? super List<ByteBuffer>>,T> BodyHandler<T>
+        fromSubscriber(S subscriber, Function<? super S,? extends T> finisher) {
+            Objects.requireNonNull(subscriber);
+            Objects.requireNonNull(finisher);
+            return (responseInfo) -> BodySubscribers.fromSubscriber(subscriber,
+                                                                      finisher);
+        }
+
+        /**
+         * Returns a response body handler that returns a {@link BodySubscriber
+         * BodySubscriber}{@code <Void>} obtained from {@link
+         * BodySubscribers#fromLineSubscriber(Subscriber, Function, Charset, String)
+         * BodySubscribers.fromLineSubscriber(subscriber, s -> null, charset, null)},
+         * with the given {@code subscriber}.
+         * The {@link Charset charset} used to decode the response body bytes is
+         * obtained from the HTTP response headers as specified by {@link #ofString()},
+         * and lines are delimited in the manner of {@link BufferedReader#readLine()}.
+         *
+         * <p> The response body is not available through this, or the {@code
+         * HttpResponse} API, but instead all response body is forwarded to the
+         * given {@code subscriber}, which should make it available, if
+         * appropriate, through some other mechanism, e.g. an entry in a
+         * database, etc.
+         *
+         * @apiNote This method can be used as an adapter between a {@code
+         * BodySubscriber} and a text based {@code Flow.Subscriber} that parses
+         * text line by line.
+         *
+         * <p> For example:
+         * <pre> {@code  // A PrintSubscriber that implements Flow.Subscriber<String>
+         *  // and print lines received by onNext() on System.out
+         *  PrintSubscriber subscriber = new PrintSubscriber(System.out);
+         *  client.sendAsync(request, BodyHandlers.fromLineSubscriber(subscriber))
+         *      .thenApply(HttpResponse::statusCode)
+         *      .thenAccept((status) -> {
+         *          if (status != 200) {
+         *              System.err.printf("ERROR: %d status received%n", status);
+         *          }
+         *      }); }</pre>
+         *
+         * @param subscriber the subscriber
+         * @return a response body handler
+         */
+        public static BodyHandler<Void>
+        fromLineSubscriber(Subscriber<? super String> subscriber) {
+            Objects.requireNonNull(subscriber);
+            return (responseInfo) ->
+                        BodySubscribers.fromLineSubscriber(subscriber,
+                                                           s -> null,
+                                                           charsetFrom(responseInfo.headers()),
+                                                           null);
+        }
+
+        /**
+         * Returns a response body handler that returns a {@link BodySubscriber
+         * BodySubscriber}{@code <T>} obtained from {@link
+         * BodySubscribers#fromLineSubscriber(Subscriber, Function, Charset, String)
+         * BodySubscribers.fromLineSubscriber(subscriber, finisher, charset, lineSeparator)},
+         * with the given {@code subscriber}, {@code finisher} function, and line separator.
+         * The {@link Charset charset} used to decode the response body bytes is
+         * obtained from the HTTP response headers as specified by {@link #ofString()}.
+         *
+         * <p> The given {@code finisher} function is applied after the given
+         * subscriber's {@code onComplete} has been invoked. The {@code finisher}
+         * function is invoked with the given subscriber, and returns a value
+         * that is set as the response's body.
+         *
+         * @apiNote This method can be used as an adapter between a {@code
+         * BodySubscriber} and a text based {@code Flow.Subscriber} that parses
+         * text line by line.
+         *
+         * <p> For example:
+         * <pre> {@code  // A LineParserSubscriber that implements Flow.Subscriber<String>
+         *  // and accumulates lines that match a particular pattern
+         *  Pattern pattern = ...;
+         *  LineParserSubscriber subscriber = new LineParserSubscriber(pattern);
+         *  HttpResponse<List<String>> response = client.send(request,
+         *      BodyHandlers.fromLineSubscriber(subscriber, s -> s.getMatchingLines(), "\n"));
+         *  if (response.statusCode() != 200) {
+         *      System.err.printf("ERROR: %d status received%n", response.statusCode());
+         *  } }</pre>
+         *
+         *
+         * @param <S> the type of the Subscriber
+         * @param <T> the type of the response body
+         * @param subscriber the subscriber
+         * @param finisher a function to be applied after the subscriber has completed
+         * @param lineSeparator an optional line separator: can be {@code null},
+         *                      in which case lines will be delimited in the manner of
+         *                      {@link BufferedReader#readLine()}.
+         * @return a response body handler
+         * @throws IllegalArgumentException if the supplied {@code lineSeparator}
+         *         is the empty string
+         */
+        public static <S extends Subscriber<? super String>,T> BodyHandler<T>
+        fromLineSubscriber(S subscriber,
+                           Function<? super S,? extends T> finisher,
+                           String lineSeparator) {
+            Objects.requireNonNull(subscriber);
+            Objects.requireNonNull(finisher);
+            // implicit null check
+            if (lineSeparator != null && lineSeparator.isEmpty())
+                throw new IllegalArgumentException("empty line separator");
+            return (responseInfo) ->
+                        BodySubscribers.fromLineSubscriber(subscriber,
+                                                           finisher,
+                                                           charsetFrom(responseInfo.headers()),
+                                                           lineSeparator);
+        }
+
+        /**
+         * Returns a response body handler that discards the response body.
+         *
+         * @return a response body handler
+         */
+        public static BodyHandler<Void> discarding() {
+            return (responseInfo) -> BodySubscribers.discarding();
+        }
+
+        /**
+         * Returns a response body handler that returns the given replacement
+         * value, after discarding the response body.
+         *
+         * @param <U> the response body type
+         * @param value the value of U to return as the body, may be {@code null}
+         * @return a response body handler
+         */
+        public static <U> BodyHandler<U> replacing(U value) {
+            return (responseInfo) -> BodySubscribers.replacing(value);
+        }
+
+        /**
+         * Returns a {@code BodyHandler<String>} that returns a
+         * {@link BodySubscriber BodySubscriber}{@code <String>} obtained from
+         * {@link BodySubscribers#ofString(Charset) BodySubscribers.ofString(Charset)}.
+         * The body is decoded using the given character set.
+         *
+         * @param charset the character set to convert the body with
+         * @return a response body handler
+         */
+        public static BodyHandler<String> ofString(Charset charset) {
+            Objects.requireNonNull(charset);
+            return (responseInfo) -> BodySubscribers.ofString(charset);
+        }
+
+        /**
+         * Returns a {@code BodyHandler<Path>} that returns a
+         * {@link BodySubscriber BodySubscriber}{@code <Path>} obtained from
+         * {@link BodySubscribers#ofFile(Path, OpenOption...)
+         * BodySubscribers.ofFile(Path,OpenOption...)}.
+         *
+         * <p> When the {@code HttpResponse} object is returned, the body has
+         * been completely written to the file, and {@link #body()} returns a
+         * reference to its {@link Path}.
+         *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when the {@code BodyHandler} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
+         * @param file the file to store the body in
+         * @param openOptions any options to use when opening/creating the file
+         * @return a response body handler
+         * @throws IllegalArgumentException if an invalid set of open options
+         *          are specified
+         * @throws SecurityException If a security manager has been installed
+         *          and it denies {@link SecurityManager#checkWrite(String)
+         *          write access} to the file.
+         */
+        public static BodyHandler<Path> ofFile(Path file, OpenOption... openOptions) {
+            Objects.requireNonNull(file);
+            List<OpenOption> opts = List.of(openOptions);
+            if (opts.contains(DELETE_ON_CLOSE) || opts.contains(READ)) {
+                // these options make no sense, since the FileChannel is not exposed
+                throw new IllegalArgumentException("invalid openOptions: " + opts);
+            }
+            return PathBodyHandler.create(file, opts);
+        }
+
+        /**
+         * Returns a {@code BodyHandler<Path>} that returns a
+         * {@link BodySubscriber BodySubscriber}{@code <Path>}.
+         *
+         * <p> Equivalent to: {@code ofFile(file, CREATE, WRITE)}
+         *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when the {@code BodyHandler} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
+         * @param file the file to store the body in
+         * @return a response body handler
+         * @throws SecurityException If a security manager has been installed
+         *          and it denies {@link SecurityManager#checkWrite(String)
+         *          write access} to the file.
+         */
+        public static BodyHandler<Path> ofFile(Path file) {
+            return BodyHandlers.ofFile(file, CREATE, WRITE);
+        }
+
+        /**
+         * Returns a {@code BodyHandler<Path>} that returns a
+         * {@link BodySubscriber BodySubscriber}&lt;{@link Path}&gt;
+         * where the download directory is specified, but the filename is
+         * obtained from the {@code Content-Disposition} response header. The
+         * {@code Content-Disposition} header must specify the <i>attachment</i>
+         * type and must also contain a <i>filename</i> parameter. If the
+         * filename specifies multiple path components only the final component
+         * is used as the filename (with the given directory name).
+         *
+         * <p> When the {@code HttpResponse} object is returned, the body has
+         * been completely written to the file and {@link #body()} returns a
+         * {@code Path} object for the file. The returned {@code Path} is the
+         * combination of the supplied directory name and the file name supplied
+         * by the server. If the destination directory does not exist or cannot
+         * be written to, then the response will fail with an {@link IOException}.
+         *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when the {@code BodyHandler} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
+         * @param directory the directory to store the file in
+         * @param openOptions open options used when opening the file
+         * @return a response body handler
+         * @throws IllegalArgumentException if the given path does not exist,
+         *          is not a directory, is not writable, or if an invalid set
+         *          of open options are specified
+         * @throws SecurityException If a security manager has been installed
+         *          and it denies
+         *          {@linkplain SecurityManager#checkRead(String) read access}
+         *          to the directory, or it denies
+         *          {@linkplain SecurityManager#checkWrite(String) write access}
+         *          to the directory, or it denies
+         *          {@linkplain SecurityManager#checkWrite(String) write access}
+         *          to the files within the directory.
+         */
+        public static BodyHandler<Path> ofFileDownload(Path directory,
+                                                       OpenOption... openOptions) {
+            Objects.requireNonNull(directory);
+            List<OpenOption> opts = List.of(openOptions);
+            if (opts.contains(DELETE_ON_CLOSE)) {
+                throw new IllegalArgumentException("invalid option: " + DELETE_ON_CLOSE);
+            }
+            return FileDownloadBodyHandler.create(directory, opts);
+        }
+
+        /**
+         * Returns a {@code BodyHandler<InputStream>} that returns a
+         * {@link BodySubscriber BodySubscriber}{@code <InputStream>} obtained from
+         * {@link BodySubscribers#ofInputStream() BodySubscribers.ofInputStream}.
+         *
+         * <p> When the {@code HttpResponse} object is returned, the response
+         * headers will have been completely read, but the body may not have
+         * been fully received yet. The {@link #body()} method returns an
+         * {@link InputStream} from which the body can be read as it is received.
+         *
+         * @apiNote See {@link BodySubscribers#ofInputStream()} for more
+         * information.
+         *
+         * @return a response body handler
+         */
+        public static BodyHandler<InputStream> ofInputStream() {
+            return (responseInfo) -> BodySubscribers.ofInputStream();
+        }
+
+        /**
+         * Returns a {@code BodyHandler<Stream<String>>} that returns a
+         * {@link BodySubscriber BodySubscriber}{@code <Stream<String>>} obtained
+         * from {@link BodySubscribers#ofLines(Charset) BodySubscribers.ofLines(charset)}.
+         * The {@link Charset charset} used to decode the response body bytes is
+         * obtained from the HTTP response headers as specified by {@link #ofString()},
+         * and lines are delimited in the manner of {@link BufferedReader#readLine()}.
+         *
+         * <p> When the {@code HttpResponse} object is returned, the body may
+         * not have been completely received.
+         *
+         * @return a response body handler
+         */
+        public static BodyHandler<Stream<String>> ofLines() {
+            return (responseInfo) ->
+                    BodySubscribers.ofLines(charsetFrom(responseInfo.headers()));
+        }
+
+        /**
+         * Returns a {@code BodyHandler<Void>} that returns a
+         * {@link BodySubscriber BodySubscriber}{@code <Void>} obtained from
+         * {@link BodySubscribers#ofByteArrayConsumer(Consumer)
+         * BodySubscribers.ofByteArrayConsumer(Consumer)}.
+         *
+         * <p> When the {@code HttpResponse} object is returned, the body has
+         * been completely written to the consumer.
+         *
+         * @apiNote
+         * The subscriber returned by this handler is not flow controlled.
+         * Therefore, the supplied consumer must be able to process whatever
+         * amount of data is delivered in a timely fashion.
+         *
+         * @param consumer a Consumer to accept the response body
+         * @return a response body handler
+         */
+        public static BodyHandler<Void>
+        ofByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
+            Objects.requireNonNull(consumer);
+            return (responseInfo) -> BodySubscribers.ofByteArrayConsumer(consumer);
+        }
+
+        /**
+         * Returns a {@code BodyHandler<byte[]>} that returns a
+         * {@link BodySubscriber BodySubscriber}&lt;{@code byte[]}&gt; obtained
+         * from {@link BodySubscribers#ofByteArray() BodySubscribers.ofByteArray()}.
+         *
+         * <p> When the {@code HttpResponse} object is returned, the body has
+         * been completely written to the byte array.
+         *
+         * @return a response body handler
+         */
+        public static BodyHandler<byte[]> ofByteArray() {
+            return (responseInfo) -> BodySubscribers.ofByteArray();
+        }
+
+        /**
+         * Returns a {@code BodyHandler<String>} that returns a
+         * {@link BodySubscriber BodySubscriber}{@code <String>} obtained from
+         * {@link BodySubscribers#ofString(Charset) BodySubscribers.ofString(Charset)}.
+         * The body is decoded using the character set specified in
+         * the {@code Content-Type} response header. If there is no such
+         * header, or the character set is not supported, then
+         * {@link StandardCharsets#UTF_8 UTF_8} is used.
+         *
+         * <p> When the {@code HttpResponse} object is returned, the body has
+         * been completely written to the string.
+         *
+         * @return a response body handler
+         */
+        public static BodyHandler<String> ofString() {
+            return (responseInfo) -> BodySubscribers.ofString(charsetFrom(responseInfo.headers()));
+        }
+
+        /**
+         * Returns a {@code BodyHandler<Publisher<List<ByteBuffer>>>} that creates a
+         * {@link BodySubscriber BodySubscriber}{@code <Publisher<List<ByteBuffer>>>}
+         * obtained from {@link BodySubscribers#ofPublisher()
+         * BodySubscribers.ofPublisher()}.
+         *
+         * <p> When the {@code HttpResponse} object is returned, the response
+         * headers will have been completely read, but the body may not have
+         * been fully received yet. The {@link #body()} method returns a
+         * {@link Publisher Publisher<List<ByteBuffer>>} from which the body
+         * response bytes can be obtained as they are received. The publisher
+         * can and must be subscribed to only once.
+         *
+         * @apiNote See {@link BodySubscribers#ofPublisher()} for more
+         * information.
+         *
+         * @return a response body handler
+         */
+        public static BodyHandler<Publisher<List<ByteBuffer>>> ofPublisher() {
+            return (responseInfo) -> BodySubscribers.ofPublisher();
+        }
+
+        /**
+         * Returns a {@code BodyHandler} which, when invoked, returns a {@linkplain
+         * BodySubscribers#buffering(BodySubscriber,int) buffering BodySubscriber}
+         * that buffers data before delivering it to the downstream subscriber.
+         * These {@code BodySubscriber} instances are created by calling
+         * {@link BodySubscribers#buffering(BodySubscriber,int)
+         * BodySubscribers.buffering} with a subscriber obtained from the given
+         * downstream handler and the {@code bufferSize} parameter.
+         *
+         * @param <T> the response body type
+         * @param downstreamHandler the downstream handler
+         * @param bufferSize the buffer size parameter passed to {@link
+         *        BodySubscribers#buffering(BodySubscriber,int) BodySubscribers.buffering}
+         * @return a body handler
+         * @throws IllegalArgumentException if {@code bufferSize <= 0}
+         */
+         public static <T> BodyHandler<T> buffering(BodyHandler<T> downstreamHandler,
+                                                    int bufferSize) {
+             Objects.requireNonNull(downstreamHandler);
+             if (bufferSize <= 0)
+                 throw new IllegalArgumentException("must be greater than 0");
+             return (responseInfo) -> BodySubscribers
+                     .buffering(downstreamHandler.apply(responseInfo),
+                                bufferSize);
+         }
+    }
+
+    /**
+     * A handler for push promises.
+     *
+     * <p> A <i>push promise</i> is a synthetic request sent by an HTTP/2 server
+     * when retrieving an initiating client-sent request. The server has
+     * determined, possibly through inspection of the initiating request, that
+     * the client will likely need the promised resource, and hence pushes a
+     * synthetic push request, in the form of a push promise, to the client. The
+     * client can choose to accept or reject the push promise request.
+     *
+     * <p> A push promise request may be received up to the point where the
+     * response body of the initiating client-sent request has been fully
+     * received. The delivery of a push promise response, however, is not
+     * coordinated with the delivery of the response to the initiating
+     * client-sent request.
+     *
+     * @param <T> the push promise response body type
+     * @since 11
+     */
+    public interface PushPromiseHandler<T> {
+
+        /**
+         * Notification of an incoming push promise.
+         *
+         * <p> This method is invoked once for each push promise received, up
+         * to the point where the response body of the initiating client-sent
+         * request has been fully received.
+         *
+         * <p> A push promise is accepted by invoking the given {@code acceptor}
+         * function. The {@code acceptor} function must be passed a non-null
+         * {@code BodyHandler}, that is to be used to handle the promise's
+         * response body. The acceptor function will return a {@code
+         * CompletableFuture} that completes with the promise's response.
+         *
+         * <p> If the {@code acceptor} function is not successfully invoked,
+         * then the push promise is rejected. The {@code acceptor} function will
+         * throw an {@code IllegalStateException} if invoked more than once.
+         *
+         * @param initiatingRequest the initiating client-send request
+         * @param pushPromiseRequest the synthetic push request
+         * @param acceptor the acceptor function that must be successfully
+         *                 invoked to accept the push promise
+         */
+        public void applyPushPromise(
+            HttpRequest initiatingRequest,
+            HttpRequest pushPromiseRequest,
+            Function<HttpResponse.BodyHandler<T>,CompletableFuture<HttpResponse<T>>> acceptor
+        );
+
+
+        /**
+         * Returns a push promise handler that accumulates push promises, and
+         * their responses, into the given map.
+         *
+         * <p> Entries are added to the given map for each push promise accepted.
+         * The entry's key is the push request, and the entry's value is a
+         * {@code CompletableFuture} that completes with the response
+         * corresponding to the key's push request. A push request is rejected /
+         * cancelled if there is already an entry in the map whose key is
+         * {@link HttpRequest#equals equal} to it. A push request is
+         * rejected / cancelled if it  does not have the same origin as its
+         * initiating request.
+         *
+         * <p> Entries are added to the given map as soon as practically
+         * possible when a push promise is received and accepted. That way code,
+         * using such a map like a cache, can determine if a push promise has
+         * been issued by the server and avoid making, possibly, unnecessary
+         * requests.
+         *
+         * <p> The delivery of a push promise response is not coordinated with
+         * the delivery of the response to the initiating client-sent request.
+         * However, when the response body for the initiating client-sent
+         * request has been fully received, the map is guaranteed to be fully
+         * populated, that is, no more entries will be added. The individual
+         * {@code CompletableFutures} contained in the map may or may not
+         * already be completed at this point.
+         *
+         * @param <T> the push promise response body type
+         * @param pushPromiseHandler t he body handler to use for push promises
+         * @param pushPromisesMap a map to accumulate push promises into
+         * @return a push promise handler
+         */
+        public static <T> PushPromiseHandler<T>
+        of(Function<HttpRequest,BodyHandler<T>> pushPromiseHandler,
+           ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap) {
+            return new PushPromisesHandlerWithMap<>(pushPromiseHandler, pushPromisesMap);
+        }
+    }
+
+    /**
+     * A {@code BodySubscriber} consumes response body bytes and converts them
+     * into a higher-level Java type.  The class {@link BodySubscribers
+     * BodySubscriber} provides implementations of many common body subscribers.
+     *
+     * <p> The object acts as a {@link Flow.Subscriber}&lt;{@link List}&lt;{@link
+     * ByteBuffer}&gt;&gt; to the HTTP client implementation, which publishes
+     * unmodifiable lists of read-only ByteBuffers containing the response body.
+     * The Flow of data, as well as the order of ByteBuffers in the Flow lists,
+     * is a strictly ordered representation of the response body. Both the Lists
+     * and the ByteBuffers, once passed to the subscriber, are no longer used by
+     * the HTTP client. The subscriber converts the incoming buffers of data to
+     * some higher-level Java type {@code T}.
+     *
+     * <p> The {@link #getBody()} method returns a
+     * {@link CompletionStage}&lt;{@code T}&gt; that provides the response body
+     * object. The {@code CompletionStage} must be obtainable at any time. When
+     * it completes depends on the nature of type {@code T}. In many cases,
+     * when {@code T} represents the entire body after being consumed then
+     * the {@code CompletionStage} completes after the body has been consumed.
+     * If  {@code T} is a streaming type, such as {@link java.io.InputStream
+     * InputStream}, then it completes before the body has been read, because
+     * the calling code uses the {@code InputStream} to consume the data.
+     *
+     * @apiNote To ensure that all resources associated with the corresponding
+     * HTTP exchange are properly released, an implementation of {@code
+     * BodySubscriber} should ensure to {@link Flow.Subscription#request
+     * request} more data until one of {@link #onComplete() onComplete} or
+     * {@link #onError(Throwable) onError} are signalled, or {@link
+     * Flow.Subscription#request cancel} its {@linkplain
+     * #onSubscribe(Flow.Subscription) subscription} if unable or unwilling to
+     * do so. Calling {@code cancel} before exhausting the response body data
+     * may cause the underlying HTTP connection to be closed and prevent it
+     * from being reused for subsequent operations.
+     *
+     * @param <T> the response body type
+     * @see BodySubscribers
+     * @since 11
+     */
+    public interface BodySubscriber<T>
+            extends Flow.Subscriber<List<ByteBuffer>> {
+
+        /**
+         * Returns a {@code CompletionStage} which when completed will return
+         * the response body object. This method can be called at any time
+         * relative to the other {@link Flow.Subscriber} methods and is invoked
+         * using the client's {@link HttpClient#executor() executor}.
+         *
+         * @return a CompletionStage for the response body
+         */
+        public CompletionStage<T> getBody();
+    }
+
+    /**
+     * Implementations of {@link BodySubscriber BodySubscriber} that implement
+     * various useful subscribers, such as converting the response body bytes
+     * into a String, or streaming the bytes to a file.
+     *
+     * <p>The following are examples of using the predefined body subscribers
+     * to convert a flow of response body data into common high-level Java
+     * objects:
+     *
+     * <pre>{@code    // Streams the response body to a File
+     *   HttpResponse<byte[]> response = client
+     *     .send(request, (statusCode, responseHeaders) -> BodySubscribers.ofByteArray());
+     *
+     *   // Accumulates the response body and returns it as a byte[]
+     *   HttpResponse<byte[]> response = client
+     *     .send(request, (statusCode, responseHeaders) -> BodySubscribers.ofByteArray());
+     *
+     *   // Discards the response body
+     *   HttpResponse<Void> response = client
+     *     .send(request, (statusCode, responseHeaders) -> BodySubscribers.discarding());
+     *
+     *   // Accumulates the response body as a String then maps it to its bytes
+     *   HttpResponse<byte[]> response = client
+     *     .send(request, (sc, hdrs) ->
+     *        BodySubscribers.mapping(BodySubscribers.ofString(), String::getBytes));
+     * }</pre>
+     *
+     * @since 11
+     */
+    public static class BodySubscribers {
+
+        private BodySubscribers() { }
+
+        /**
+         * Returns a body subscriber that forwards all response body to the
+         * given {@code Flow.Subscriber}. The {@linkplain BodySubscriber#getBody()
+         * completion stage} of the returned body subscriber completes after one
+         * of the given subscribers {@code onComplete} or {@code onError} has
+         * been invoked.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodySubscriber} and {@code Flow.Subscriber}.
+         *
+         * @param subscriber the subscriber
+         * @return a body subscriber
+         */
+        public static BodySubscriber<Void>
+        fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber) {
+            return new ResponseSubscribers.SubscriberAdapter<>(subscriber, s -> null);
+        }
+
+        /**
+         * Returns a body subscriber that forwards all response body to the
+         * given {@code Flow.Subscriber}. The {@linkplain BodySubscriber#getBody()
+         * completion stage} of the returned body subscriber completes after one
+         * of the given subscribers {@code onComplete} or {@code onError} has
+         * been invoked.
+         *
+         * <p> The given {@code finisher} function is applied after the given
+         * subscriber's {@code onComplete} has been invoked. The {@code finisher}
+         * function is invoked with the given subscriber, and returns a value
+         * that is set as the response's body.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodySubscriber} and {@code Flow.Subscriber}.
+         *
+         * @param <S> the type of the Subscriber
+         * @param <T> the type of the response body
+         * @param subscriber the subscriber
+         * @param finisher a function to be applied after the subscriber has
+         *                 completed
+         * @return a body subscriber
+         */
+        public static <S extends Subscriber<? super List<ByteBuffer>>,T> BodySubscriber<T>
+        fromSubscriber(S subscriber,
+                       Function<? super S,? extends T> finisher) {
+            return new ResponseSubscribers.SubscriberAdapter<>(subscriber, finisher);
+        }
+
+        /**
+         * Returns a body subscriber that forwards all response body to the
+         * given {@code Flow.Subscriber}, line by line.
+         * The {@linkplain BodySubscriber#getBody() completion
+         * stage} of the returned body subscriber completes after one of the
+         * given subscribers {@code onComplete} or {@code onError} has been
+         * invoked.
+         * Bytes are decoded using the {@link StandardCharsets#UTF_8
+         * UTF-8} charset, and lines are delimited in the manner of
+         * {@link BufferedReader#readLine()}.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodySubscriber} and {@code Flow.Subscriber}.
+         *
+         * @implNote This is equivalent to calling <pre>{@code
+         *      fromLineSubscriber(subscriber, s -> null, StandardCharsets.UTF_8, null)
+         * }</pre>
+         *
+         * @param subscriber the subscriber
+         * @return a body subscriber
+         */
+        public static BodySubscriber<Void>
+        fromLineSubscriber(Subscriber<? super String> subscriber) {
+            return fromLineSubscriber(subscriber,  s -> null,
+                    StandardCharsets.UTF_8, null);
+        }
+
+        /**
+         * Returns a body subscriber that forwards all response body to the
+         * given {@code Flow.Subscriber}, line by line. The {@linkplain
+         * BodySubscriber#getBody() completion stage} of the returned body
+         * subscriber completes after one of the given subscribers
+         * {@code onComplete} or {@code onError} has been invoked.
+         *
+         * <p> The given {@code finisher} function is applied after the given
+         * subscriber's {@code onComplete} has been invoked. The {@code finisher}
+         * function is invoked with the given subscriber, and returns a value
+         * that is set as the response's body.
+         *
+         * @apiNote This method can be used as an adapter between {@code
+         * BodySubscriber} and {@code Flow.Subscriber}.
+         *
+         * @param <S> the type of the Subscriber
+         * @param <T> the type of the response body
+         * @param subscriber the subscriber
+         * @param finisher a function to be applied after the subscriber has
+         *                 completed
+         * @param charset a {@link Charset} to decode the bytes
+         * @param lineSeparator an optional line separator: can be {@code null},
+         *                      in which case lines will be delimited in the manner of
+         *                      {@link BufferedReader#readLine()}.
+         * @return a body subscriber
+         * @throws IllegalArgumentException if the supplied {@code lineSeparator}
+         *         is the empty string
+         */
+        public static <S extends Subscriber<? super String>,T> BodySubscriber<T>
+        fromLineSubscriber(S subscriber,
+                           Function<? super S,? extends T> finisher,
+                           Charset charset,
+                           String lineSeparator) {
+            return LineSubscriberAdapter.create(subscriber,
+                    finisher, charset, lineSeparator);
+        }
+
+        /**
+         * Returns a body subscriber which stores the response body as a {@code
+         * String} converted using the given {@code Charset}.
+         *
+         * <p> The {@link HttpResponse} using this subscriber is available after
+         * the entire response has been read.
+         *
+         * @param charset the character set to convert the String with
+         * @return a body subscriber
+         */
+        public static BodySubscriber<String> ofString(Charset charset) {
+            Objects.requireNonNull(charset);
+            return new ResponseSubscribers.ByteArraySubscriber<>(
+                    bytes -> new String(bytes, charset)
+            );
+        }
+
+        /**
+         * Returns a {@code BodySubscriber} which stores the response body as a
+         * byte array.
+         *
+         * <p> The {@link HttpResponse} using this subscriber is available after
+         * the entire response has been read.
+         *
+         * @return a body subscriber
+         */
+        public static BodySubscriber<byte[]> ofByteArray() {
+            return new ResponseSubscribers.ByteArraySubscriber<>(
+                    Function.identity() // no conversion
+            );
+        }
+
+        /**
+         * Returns a {@code BodySubscriber} which stores the response body in a
+         * file opened with the given options and name. The file will be opened
+         * with the given options using {@link FileChannel#open(Path,OpenOption...)
+         * FileChannel.open} just before the body is read. Any exception thrown
+         * will be returned or thrown from {@link HttpClient#send(HttpRequest,
+         * BodyHandler) HttpClient::send} or {@link HttpClient#sendAsync(HttpRequest,
+         * BodyHandler) HttpClient::sendAsync} as appropriate.
+         *
+         * <p> The {@link HttpResponse} using this subscriber is available after
+         * the entire response has been read.
+         *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when the {@code BodySubscriber} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
+         * @param file the file to store the body in
+         * @param openOptions the list of options to open the file with
+         * @return a body subscriber
+         * @throws IllegalArgumentException if an invalid set of open options
+         *          are specified
+         * @throws SecurityException if a security manager has been installed
+         *          and it denies {@link SecurityManager#checkWrite(String)
+         *          write access} to the file
+         */
+        public static BodySubscriber<Path> ofFile(Path file, OpenOption... openOptions) {
+            Objects.requireNonNull(file);
+            List<OpenOption> opts = List.of(openOptions);
+            if (opts.contains(DELETE_ON_CLOSE) || opts.contains(READ)) {
+                // these options make no sense, since the FileChannel is not exposed
+                throw new IllegalArgumentException("invalid openOptions: " + opts);
+            }
+            return PathSubscriber.create(file, opts);
+        }
+
+        /**
+         * Returns a {@code BodySubscriber} which stores the response body in a
+         * file opened with the given name.
+         *
+         * <p> Equivalent to: {@code ofFile(file, CREATE, WRITE)}
+         *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when the {@code BodySubscriber} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
+         * @param file the file to store the body in
+         * @return a body subscriber
+         * @throws SecurityException if a security manager has been installed
+         *          and it denies {@link SecurityManager#checkWrite(String)
+         *          write access} to the file
+         */
+        public static BodySubscriber<Path> ofFile(Path file) {
+            return ofFile(file, CREATE, WRITE);
+        }
+
+        /**
+         * Returns a {@code BodySubscriber} which provides the incoming body
+         * data to the provided Consumer of {@code Optional<byte[]>}. Each
+         * call to {@link Consumer#accept(java.lang.Object) Consumer.accept()}
+         * will contain a non empty {@code Optional}, except for the final
+         * invocation after all body data has been read, when the {@code
+         * Optional} will be empty.
+         *
+         * <p> The {@link HttpResponse} using this subscriber is available after
+         * the entire response has been read.
+         *
+         * @apiNote
+         * This subscriber is not flow controlled.
+         * Therefore, the supplied consumer must be able to process whatever
+         * amount of data is delivered in a timely fashion.
+         *
+         * @param consumer a Consumer of byte arrays
+         * @return a BodySubscriber
+         */
+        public static BodySubscriber<Void>
+        ofByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
+            return new ResponseSubscribers.ConsumerSubscriber(consumer);
+        }
+
+        /**
+         * Returns a {@code BodySubscriber} which streams the response body as
+         * an {@link InputStream}.
+         *
+         * <p> The {@link HttpResponse} using this subscriber is available
+         * immediately after the response headers have been read, without
+         * requiring to wait for the entire body to be processed. The response
+         * body can then be read directly from the {@link InputStream}.
+         *
+         * @apiNote To ensure that all resources associated with the
+         * corresponding exchange are properly released the caller must
+         * ensure to either read all bytes until EOF is reached, or call
+         * {@link InputStream#close} if it is unable or unwilling to do so.
+         * Calling {@code close} before exhausting the stream may cause
+         * the underlying HTTP connection to be closed and prevent it
+         * from being reused for subsequent operations.
+         *
+         * @return a body subscriber that streams the response body as an
+         *         {@link InputStream}.
+         */
+        public static BodySubscriber<InputStream> ofInputStream() {
+            return new ResponseSubscribers.HttpResponseInputStream();
+        }
+
+        /**
+         * Returns a {@code BodySubscriber} which streams the response body as
+         * a {@link Stream Stream<String>}, where each string in the stream
+         * corresponds to a line as defined by {@link BufferedReader#lines()}.
+         *
+         * <p> The {@link HttpResponse} using this subscriber is available
+         * immediately after the response headers have been read, without
+         * requiring to wait for the entire body to be processed. The response
+         * body can then be read directly from the {@link Stream}.
+         *
+         * @apiNote To ensure that all resources associated with the
+         * corresponding exchange are properly released the caller must
+         * ensure to either read all lines until the stream is exhausted,
+         * or call {@link Stream#close} if it is unable or unwilling to do so.
+         * Calling {@code close} before exhausting the stream may cause
+         * the underlying HTTP connection to be closed and prevent it
+         * from being reused for subsequent operations.
+         *
+         * @param charset the character set to use when converting bytes to characters
+         * @return a body subscriber that streams the response body as a
+         *         {@link Stream Stream<String>}.
+         *
+         * @see BufferedReader#lines()
+         */
+        public static BodySubscriber<Stream<String>> ofLines(Charset charset) {
+            return ResponseSubscribers.createLineStream(charset);
+        }
+
+        /**
+         * Returns a response subscriber which publishes the response body
+         * through a {@link Publisher Publisher<List<ByteBuffer>>}.
+         *
+         * <p> The {@link HttpResponse} using this subscriber is available
+         * immediately after the response headers have been read, without
+         * requiring to wait for the entire body to be processed. The response
+         * body bytes can then be obtained by subscribing to the publisher
+         * returned by the {@code HttpResponse} {@link HttpResponse#body() body}
+         * method.
+         *
+         * <p>The publisher returned by the {@link HttpResponse#body() body}
+         * method can be subscribed to only once. The first subscriber will
+         * receive the body response bytes if successfully subscribed, or will
+         * cause the subscription to be cancelled otherwise.
+         * If more subscriptions are attempted, the subsequent subscribers will
+         * be immediately subscribed with an empty subscription and their
+         * {@link Subscriber#onError(Throwable) onError} method
+         * will be invoked with an {@code IllegalStateException}.
+         *
+         * @apiNote To ensure that all resources associated with the
+         * corresponding exchange are properly released the caller must
+         * ensure that the provided publisher is subscribed once, and either
+         * {@linkplain Subscription#request(long) requests} all bytes
+         * until {@link Subscriber#onComplete() onComplete} or
+         * {@link Subscriber#onError(Throwable) onError} are invoked, or
+         * cancel the provided {@linkplain Subscriber#onSubscribe(Subscription)
+         * subscription} if it is unable or unwilling to do so.
+         * Note that depending on the actual HTTP protocol {@linkplain
+         * HttpClient.Version version} used for the exchange, cancelling the
+         * subscription instead of exhausting the flow may cause the underlying
+         * HTTP connection to be closed and prevent it from being reused for
+         * subsequent operations.
+         *
+         * @return A {@code BodySubscriber} which publishes the response body
+         *         through a {@code Publisher<List<ByteBuffer>>}.
+         */
+        public static BodySubscriber<Publisher<List<ByteBuffer>>> ofPublisher() {
+            return ResponseSubscribers.createPublisher();
+        }
+
+        /**
+         * Returns a response subscriber which discards the response body. The
+         * supplied value is the value that will be returned from
+         * {@link HttpResponse#body()}.
+         *
+         * @param <U> the type of the response body
+         * @param value the value to return from HttpResponse.body(), may be {@code null}
+         * @return a {@code BodySubscriber}
+         */
+        public static <U> BodySubscriber<U> replacing(U value) {
+            return new ResponseSubscribers.NullSubscriber<>(Optional.ofNullable(value));
+        }
+
+        /**
+         * Returns a response subscriber which discards the response body.
+         *
+         * @return a response body subscriber
+         */
+        public static BodySubscriber<Void> discarding() {
+            return new ResponseSubscribers.NullSubscriber<>(Optional.ofNullable(null));
+        }
+
+        /**
+         * Returns a {@code BodySubscriber} which buffers data before delivering
+         * it to the given downstream subscriber. The subscriber guarantees to
+         * deliver {@code buffersize} bytes of data to each invocation of the
+         * downstream's {@link BodySubscriber#onNext(Object) onNext} method,
+         * except for the final invocation, just before
+         * {@link BodySubscriber#onComplete() onComplete} is invoked. The final
+         * invocation of {@code onNext} may contain fewer than {@code bufferSize}
+         * bytes.
+         *
+         * <p> The returned subscriber delegates its {@link BodySubscriber#getBody()
+         * getBody()} method to the downstream subscriber.
+         *
+         * @param <T> the type of the response body
+         * @param downstream the downstream subscriber
+         * @param bufferSize the buffer size
+         * @return a buffering body subscriber
+         * @throws IllegalArgumentException if {@code bufferSize <= 0}
+         */
+         public static <T> BodySubscriber<T> buffering(BodySubscriber<T> downstream,
+                                                       int bufferSize) {
+             if (bufferSize <= 0)
+                 throw new IllegalArgumentException("must be greater than 0");
+             return new BufferingSubscriber<>(downstream, bufferSize);
+         }
+
+        /**
+         * Returns a {@code BodySubscriber} whose response body value is that of
+         * the result of applying the given function to the body object of the
+         * given {@code upstream} {@code BodySubscriber}.
+         *
+         * <p> The mapping function is executed using the client's {@linkplain
+         * HttpClient#executor() executor}, and can therefore be used to map any
+         * response body type, including blocking {@link InputStream}, as shown
+         * in the following example which uses a well-known JSON parser to
+         * convert an {@code InputStream} into any annotated Java type.
+         *
+         * <p>For example:
+         * <pre> {@code  public static <W> BodySubscriber<W> asJSON(Class<W> targetType) {
+         *     BodySubscriber<InputStream> upstream = BodySubscribers.ofInputStream();
+         *
+         *     BodySubscriber<W> downstream = BodySubscribers.mapping(
+         *           upstream,
+         *           (InputStream is) -> {
+         *               try (InputStream stream = is) {
+         *                   ObjectMapper objectMapper = new ObjectMapper();
+         *                   return objectMapper.readValue(stream, targetType);
+         *               } catch (IOException e) {
+         *                   throw new UncheckedIOException(e);
+         *               }
+         *           });
+         *    return downstream;
+         * } }</pre>
+         *
+         * @param <T> the upstream body type
+         * @param <U> the type of the body subscriber returned
+         * @param upstream the body subscriber to be mapped
+         * @param mapper the mapping function
+         * @return a mapping body subscriber
+         */
+        public static <T,U> BodySubscriber<U> mapping(BodySubscriber<T> upstream,
+                                                      Function<? super T, ? extends U> mapper)
+        {
+            return new ResponseSubscribers.MappingSubscriber<>(upstream, mapper);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpTimeoutException.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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 java.net.http;
+
+import java.io.IOException;
+
+/**
+ * Thrown when a response is not received within a specified time period.
+ *
+ * @since 11
+ */
+public class HttpTimeoutException extends IOException {
+
+    private static final long serialVersionUID = 981344271622632951L;
+
+    /**
+     * Constructs an {@code HttpTimeoutException} with the given detail message.
+     *
+     * @param message
+     *        The detail message; can be {@code null}
+     */
+    public HttpTimeoutException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/WebSocket.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,771 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * A WebSocket Client.
+ *
+ * <p> {@code WebSocket} instances can be created via {@link WebSocket.Builder}.
+ *
+ * <p> WebSocket has an input and an output sides. These sides are independent
+ * from each other. A side can either be open or closed. Once closed, the side
+ * remains closed. WebSocket messages are sent through a {@code WebSocket} and
+ * received through a {@code WebSocket.Listener} associated with it. Messages
+ * can be sent until the WebSocket's output is closed, and received until the
+ * WebSocket's input is closed.
+ *
+ * <p> A send method is any of the {@code sendText}, {@code sendBinary},
+ * {@code sendPing}, {@code sendPong} and {@code sendClose} methods of
+ * {@code WebSocket}. A send method initiates a send operation and returns a
+ * {@code CompletableFuture} which completes once the operation has completed.
+ * If the {@code CompletableFuture} completes normally the operation is
+ * considered succeeded. If the {@code CompletableFuture} completes
+ * exceptionally, the operation is considered failed. An operation that has been
+ * initiated but not yet completed is considered pending.
+ *
+ * <p> A receive method is any of the {@code onText}, {@code onBinary},
+ * {@code onPing}, {@code onPong} and {@code onClose} methods of
+ * {@code Listener}. A receive method initiates a receive operation and returns
+ * a {@code CompletionStage} which completes once the operation has completed.
+ *
+ * <p> A WebSocket maintains an <a id="counter">internal counter</a>.
+ * This counter's value is a number of times the WebSocket has yet to invoke a
+ * receive method. While this counter is zero the WebSocket does not invoke
+ * receive methods. The counter is incremented by {@code n} when {@code
+ * request(n)} is called. The counter is decremented by one when the WebSocket
+ * invokes a receive method. {@code onOpen} and {@code onError} are not receive
+ * methods. WebSocket invokes {@code onOpen} prior to any other methods on the
+ * listener. WebSocket invokes {@code onOpen} at most once. WebSocket may invoke
+ * {@code onError} at any given time. If the WebSocket invokes {@code onError}
+ * or {@code onClose}, then no further listener's methods will be invoked, no
+ * matter the value of the counter. For a newly built WebSocket the counter is
+ * zero. A WebSocket invokes methods on the listener in a thread-safe manner.
+ *
+ * <p> Unless otherwise stated, {@code null} arguments will cause methods
+ * of {@code WebSocket} to throw {@code NullPointerException}, similarly,
+ * {@code WebSocket} will not pass {@code null} arguments to methods of
+ * {@code Listener}. The state of a WebSocket is not changed by the invocations
+ * that throw or return a {@code CompletableFuture} that completes with one of
+ * the {@code NullPointerException}, {@code IllegalArgumentException},
+ * {@code IllegalStateException} exceptions.
+ *
+ * <p> {@code WebSocket} handles received Ping and Close messages automatically
+ * (as per the WebSocket Protocol) by replying with Pong and Close messages. If
+ * the listener receives Ping or Close messages, no mandatory actions from the
+ * listener are required.
+ *
+ * @apiNote The relationship between a WebSocket and the associated Listener is
+ * analogous to that of a Subscription and the associated Subscriber of type
+ * {@link java.util.concurrent.Flow}.
+ *
+ * @since 11
+ */
+public interface WebSocket {
+
+    /**
+     * The WebSocket Close message status code (<code>{@value}</code>),
+     * indicating normal closure, meaning that the purpose for which the
+     * connection was established has been fulfilled.
+     *
+     * @see #sendClose(int, String)
+     * @see Listener#onClose(WebSocket, int, String)
+     */
+    int NORMAL_CLOSURE = 1000;
+
+    /**
+     * A builder of {@linkplain WebSocket WebSocket Clients}.
+     *
+     * <p> A builder can be created by invoking the
+     * {@link HttpClient#newWebSocketBuilder HttpClient.newWebSocketBuilder}
+     * method. The intermediate (setter-like) methods change the state of the
+     * builder and return the same builder they have been invoked on. If an
+     * intermediate method is not invoked, an appropriate default value (or
+     * behavior) will be assumed. A {@code Builder} is not safe for use by
+     * multiple threads without external synchronization.
+     *
+     * @since 11
+     */
+    interface Builder {
+
+        /**
+         * Adds the given name-value pair to the list of additional HTTP headers
+         * sent during the opening handshake.
+         *
+         * <p> Headers defined in the
+         * <a href="https://tools.ietf.org/html/rfc6455#section-11.3">WebSocket
+         * Protocol</a> are illegal. If this method is not invoked, no
+         * additional HTTP headers will be sent.
+         *
+         * @param name
+         *         the header name
+         * @param value
+         *         the header value
+         *
+         * @return this builder
+         */
+        Builder header(String name, String value);
+
+        /**
+         * Sets a timeout for establishing a WebSocket connection.
+         *
+         * <p> If the connection is not established within the specified
+         * duration then building of the {@code WebSocket} will fail with
+         * {@link HttpTimeoutException}. If this method is not invoked then the
+         * infinite timeout is assumed.
+         *
+         * @param timeout
+         *         the timeout, non-{@linkplain Duration#isNegative() negative},
+         *         non-{@linkplain Duration#ZERO ZERO}
+         *
+         * @return this builder
+         */
+        Builder connectTimeout(Duration timeout);
+
+        /**
+         * Sets a request for the given subprotocols.
+         *
+         * <p> After the {@code WebSocket} has been built, the actual
+         * subprotocol can be queried via
+         * {@link WebSocket#getSubprotocol WebSocket.getSubprotocol()}.
+         *
+         * <p> Subprotocols are specified in the order of preference. The most
+         * preferred subprotocol is specified first. If there are any additional
+         * subprotocols they are enumerated from the most preferred to the least
+         * preferred.
+         *
+         * <p> Subprotocols not conforming to the syntax of subprotocol
+         * identifiers are illegal. If this method is not invoked then no
+         * subprotocols will be requested.
+         *
+         * @param mostPreferred
+         *         the most preferred subprotocol
+         * @param lesserPreferred
+         *         the lesser preferred subprotocols
+         *
+         * @return this builder
+         */
+        Builder subprotocols(String mostPreferred, String... lesserPreferred);
+
+        /**
+         * Builds a {@link WebSocket} connected to the given {@code URI} and
+         * associated with the given {@code Listener}.
+         *
+         * <p> Returns a {@code CompletableFuture} which will either complete
+         * normally with the resulting {@code WebSocket} or complete
+         * exceptionally with one of the following errors:
+         * <ul>
+         * <li> {@link IOException} -
+         *          if an I/O error occurs
+         * <li> {@link WebSocketHandshakeException} -
+         *          if the opening handshake fails
+         * <li> {@link HttpTimeoutException} -
+         *          if the opening handshake does not complete within
+         *          the timeout
+         * <li> {@link InterruptedException} -
+         *          if the operation is interrupted
+         * <li> {@link SecurityException} -
+         *          if a security manager has been installed and it denies
+         *          {@link java.net.URLPermission access} to {@code uri}.
+         *          <a href="HttpRequest.html#securitychecks">Security checks</a>
+         *          contains more information relating to the security context
+         *          in which the the listener is invoked.
+         * <li> {@link IllegalArgumentException} -
+         *          if any of the arguments of this builder's methods are
+         *          illegal
+         * </ul>
+         *
+         * @param uri
+         *         the WebSocket URI
+         * @param listener
+         *         the listener
+         *
+         * @return a {@code CompletableFuture} with the {@code WebSocket}
+         */
+        CompletableFuture<WebSocket> buildAsync(URI uri, Listener listener);
+    }
+
+    /**
+     * The receiving interface of {@code WebSocket}.
+     *
+     * <p> A {@code WebSocket} invokes methods of the associated listener
+     * passing itself as an argument. When data has been received, the
+     * {@code WebSocket} invokes a receive method. Methods {@code onText},
+     * {@code onBinary}, {@code onPing} and {@code onPong} must return a
+     * {@code CompletionStage} that completes once the message has been received
+     * by the listener.
+     *
+     * <p> An {@code IOException} raised in {@code WebSocket} will result in an
+     * invocation of {@code onError} with that exception (if the input is not
+     * closed). Unless otherwise stated if the listener's method throws an
+     * exception or a {@code CompletionStage} returned from a method completes
+     * exceptionally, the WebSocket will invoke {@code onError} with this
+     * exception.
+     *
+     * <p> If a listener's method returns {@code null} rather than a
+     * {@code CompletionStage}, {@code WebSocket} will behave as if the listener
+     * returned a {@code CompletionStage} that is already completed normally.
+     *
+     * @apiNote Careful attention may be required if a listener is associated
+     * with more than a single {@code WebSocket}. In this case invocations
+     * related to different instances of {@code WebSocket} may not be ordered
+     * and may even happen concurrently.
+     *
+     * <p> {@code CompletionStage}s returned from the receive methods have
+     * nothing to do with the
+     * <a href="WebSocket.html#counter">counter of invocations</a>.
+     * Namely, a {@code CompletionStage} does not have to be completed in order
+     * to receive more invocations of the listener's methods.
+     * Here is an example of a listener that requests invocations, one at a
+     * time, until a complete message has been accumulated, then processes
+     * the result, and completes the {@code CompletionStage}:
+     * <pre>{@code     WebSocket.Listener listener = new WebSocket.Listener() {
+     *
+     *        List<CharSequence> parts = new ArrayList<>();
+     *        CompletableFuture<?> accumulatedMessage = new CompletableFuture<>();
+     *
+     *        public CompletionStage<?> onText(WebSocket webSocket,
+     *                                         CharSequence message,
+     *                                         boolean last) {
+     *            parts.add(message);
+     *            webSocket.request(1);
+     *            if (last) {
+     *                processWholeText(parts);
+     *                parts = new ArrayList<>();
+     *                accumulatedMessage.complete(null);
+     *                CompletionStage<?> cf = accumulatedMessage;
+     *                accumulatedMessage = new CompletableFuture<>();
+     *                return cf;
+     *            }
+     *            return accumulatedMessage;
+     *        }
+     *    ...
+     *    } } </pre>
+     *
+     * @since 11
+     */
+    interface Listener {
+
+        /**
+         * A {@code WebSocket} has been connected.
+         *
+         * <p> This is the initial invocation and it is made once. It is
+         * typically used to make a request for more invocations.
+         *
+         * @implSpec The default implementation is equivalent to:
+         * <pre>{@code     webSocket.request(1); }</pre>
+         *
+         * @param webSocket
+         *         the WebSocket that has been connected
+         */
+        default void onOpen(WebSocket webSocket) { webSocket.request(1); }
+
+        /**
+         * A textual data has been received.
+         *
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as an indication it may reclaim the
+         * {@code CharSequence}. Do not access the {@code CharSequence} after
+         * this {@code CompletionStage} has completed.
+         *
+         * @implSpec The default implementation is equivalent to:
+         * <pre>{@code     webSocket.request(1);
+         *    return null; }</pre>
+         *
+         * @implNote The {@code data} is always a legal UTF-16 sequence.
+         *
+         * @param webSocket
+         *         the WebSocket on which the data has been received
+         * @param data
+         *         the data
+         * @param last
+         *         whether this invocation completes the message
+         *
+         * @return a {@code CompletionStage} which completes when the
+         * {@code CharSequence} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
+         */
+        default CompletionStage<?> onText(WebSocket webSocket,
+                                          CharSequence data,
+                                          boolean last) {
+            webSocket.request(1);
+            return null;
+        }
+
+        /**
+         * A binary data has been received.
+         *
+         * <p> This data is located in bytes from the buffer's position to its
+         * limit.
+         *
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as an indication it may reclaim the
+         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
+         * this {@code CompletionStage} has completed.
+         *
+         * @implSpec The default implementation is equivalent to:
+         * <pre>{@code     webSocket.request(1);
+         *    return null; }</pre>
+         *
+         * @param webSocket
+         *         the WebSocket on which the data has been received
+         * @param data
+         *         the data
+         * @param last
+         *         whether this invocation completes the message
+         *
+         * @return a {@code CompletionStage} which completes when the
+         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
+         */
+        default CompletionStage<?> onBinary(WebSocket webSocket,
+                                            ByteBuffer data,
+                                            boolean last) {
+            webSocket.request(1);
+            return null;
+        }
+
+        /**
+         * A Ping message has been received.
+         *
+         * <p> As guaranteed by the WebSocket Protocol, the message consists of
+         * not more than {@code 125} bytes. These bytes are located from the
+         * buffer's position to its limit.
+         *
+         * <p> Given that the WebSocket implementation will automatically send a
+         * reciprocal pong when a ping is received, it is rarely required to
+         * send a pong message explicitly when a ping is received.
+         *
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as a signal it may reclaim the
+         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
+         * this {@code CompletionStage} has completed.
+         *
+         * @implSpec The default implementation is equivalent to:
+         * <pre>{@code     webSocket.request(1);
+         *    return null; }</pre>
+         *
+         * @param webSocket
+         *         the WebSocket on which the message has been received
+         * @param message
+         *         the message
+         *
+         * @return a {@code CompletionStage} which completes when the
+         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
+         */
+        default CompletionStage<?> onPing(WebSocket webSocket,
+                                          ByteBuffer message) {
+            webSocket.request(1);
+            return null;
+        }
+
+        /**
+         * A Pong message has been received.
+         *
+         * <p> As guaranteed by the WebSocket Protocol, the message consists of
+         * not more than {@code 125} bytes. These bytes are located from the
+         * buffer's position to its limit.
+         *
+         * <p> Return a {@code CompletionStage} which will be used by the
+         * {@code WebSocket} as a signal it may reclaim the
+         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
+         * this {@code CompletionStage} has completed.
+         *
+         * @implSpec The default implementation is equivalent to:
+         * <pre>{@code     webSocket.request(1);
+         *    return null; }</pre>
+         *
+         * @param webSocket
+         *         the WebSocket on which the message has been received
+         * @param message
+         *         the message
+         *
+         * @return a {@code CompletionStage} which completes when the
+         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
+         * reclaimed immediately
+         */
+        default CompletionStage<?> onPong(WebSocket webSocket,
+                                          ByteBuffer message) {
+            webSocket.request(1);
+            return null;
+        }
+
+        /**
+         * Receives a Close message indicating the WebSocket's input has been
+         * closed.
+         *
+         * <p> This is the last invocation from the specified {@code WebSocket}.
+         * By the time this invocation begins the WebSocket's input will have
+         * been closed.
+         *
+         * <p> A Close message consists of a status code and a reason for
+         * closing. The status code is an integer from the range
+         * {@code 1000 <= code <= 65535}. The {@code reason} is a string which
+         * has a UTF-8 representation not longer than {@code 123} bytes.
+         *
+         * <p> If the WebSocket's output is not already closed, the
+         * {@code CompletionStage} returned by this method will be used as an
+         * indication that the WebSocket's output may be closed. The WebSocket
+         * will close its output at the earliest of completion of the returned
+         * {@code CompletionStage} or invoking either of the {@code sendClose}
+         * or {@code abort} methods.
+         *
+         * @apiNote Returning a {@code CompletionStage} that never completes,
+         * effectively disables the reciprocating closure of the output.
+         *
+         * <p> To specify a custom closure code or reason code the
+         * {@code sendClose} method may be invoked from inside the
+         * {@code onClose} invocation:
+         * <pre>{@code     public CompletionStage<?> onClose(WebSocket webSocket,
+         *                                      int statusCode,
+         *                                      String reason) {
+         *        webSocket.sendClose(CUSTOM_STATUS_CODE, CUSTOM_REASON);
+         *        return new CompletableFuture<Void>();
+         *    } } </pre>
+         *
+         * @implSpec The default implementation of this method returns
+         * {@code null}, indicating that the output should be closed
+         * immediately.
+         *
+         * @param webSocket
+         *         the WebSocket on which the message has been received
+         * @param statusCode
+         *         the status code
+         * @param reason
+         *         the reason
+         *
+         * @return a {@code CompletionStage} which completes when the
+         * {@code WebSocket} may be closed; or {@code null} if it may be
+         * closed immediately
+         */
+        default CompletionStage<?> onClose(WebSocket webSocket,
+                                           int statusCode,
+                                           String reason) {
+            return null;
+        }
+
+        /**
+         * An error has occurred.
+         *
+         * <p> This is the last invocation from the specified WebSocket. By the
+         * time this invocation begins both the WebSocket's input and output
+         * will have been closed. A WebSocket may invoke this method on the
+         * associated listener at any time after it has invoked {@code onOpen},
+         * regardless of whether or not any invocations have been requested from
+         * the WebSocket.
+         *
+         * <p> If an exception is thrown from this method, resulting behavior is
+         * undefined.
+         *
+         * @param webSocket
+         *         the WebSocket on which the error has occurred
+         * @param error
+         *         the error
+         */
+        default void onError(WebSocket webSocket, Throwable error) { }
+    }
+
+    /**
+     * Sends textual data with characters from the given character sequence.
+     *
+     * <p> The character sequence must not be modified until the
+     * {@code CompletableFuture} returned from this method has completed.
+     *
+     * <p> A {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
+     * <ul>
+     * <li> {@link IllegalStateException} -
+     *          if there is a pending text or binary send operation
+     *          or if the previous binary data does not complete the message
+     * <li> {@link IOException} -
+     *          if an I/O error occurs, or if the output is closed
+     * </ul>
+     *
+     * @implNote If {@code data} is a malformed UTF-16 sequence, the operation
+     * will fail with {@code IOException}.
+     *
+     * @param data
+     *         the data
+     * @param last
+     *         {@code true} if this invocation completes the message,
+     *         {@code false} otherwise
+     *
+     * @return a {@code CompletableFuture} that completes, with this WebSocket,
+     * when the data has been sent
+     */
+    CompletableFuture<WebSocket> sendText(CharSequence data, boolean last);
+
+    /**
+     * Sends binary data with bytes from the given buffer.
+     *
+     * <p> The data is located in bytes from the buffer's position to its limit.
+     * Upon normal completion of a {@code CompletableFuture} returned from this
+     * method the buffer will have no remaining bytes. The buffer must not be
+     * accessed until after that.
+     *
+     * <p> The {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
+     * <ul>
+     * <li> {@link IllegalStateException} -
+     *          if there is a pending text or binary send operation
+     *          or if the previous textual data does not complete the message
+     * <li> {@link IOException} -
+     *          if an I/O error occurs, or if the output is closed
+     * </ul>
+     *
+     * @param data
+     *         the data
+     * @param last
+     *         {@code true} if this invocation completes the message,
+     *         {@code false} otherwise
+     *
+     * @return a {@code CompletableFuture} that completes, with this WebSocket,
+     * when the data has been sent
+     */
+    CompletableFuture<WebSocket> sendBinary(ByteBuffer data, boolean last);
+
+    /**
+     * Sends a Ping message with bytes from the given buffer.
+     *
+     * <p> The message consists of not more than {@code 125} bytes from the
+     * buffer's position to its limit. Upon normal completion of a
+     * {@code CompletableFuture} returned from this method the buffer will
+     * have no remaining bytes. The buffer must not be accessed until after that.
+     *
+     * <p> The {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
+     * <ul>
+     * <li> {@link IllegalStateException} -
+     *          if there is a pending ping or pong send operation
+     * <li> {@link IllegalArgumentException} -
+     *          if the message is too long
+     * <li> {@link IOException} -
+     *          if an I/O error occurs, or if the output is closed
+     * </ul>
+     *
+     * @param message
+     *         the message
+     *
+     * @return a {@code CompletableFuture} that completes, with this WebSocket,
+     * when the Ping message has been sent
+     */
+    CompletableFuture<WebSocket> sendPing(ByteBuffer message);
+
+    /**
+     * Sends a Pong message with bytes from the given buffer.
+     *
+     * <p> The message consists of not more than {@code 125} bytes from the
+     * buffer's position to its limit. Upon normal completion of a
+     * {@code CompletableFuture} returned from this method the buffer will have
+     * no remaining bytes. The buffer must not be accessed until after that.
+     *
+     * <p> Given that the WebSocket implementation will automatically send a
+     * reciprocal pong when a ping is received, it is rarely required to send a
+     * pong message explicitly.
+     *
+     * <p> The {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
+     * <ul>
+     * <li> {@link IllegalStateException} -
+     *          if there is a pending ping or pong send operation
+     * <li> {@link IllegalArgumentException} -
+     *          if the message is too long
+     * <li> {@link IOException} -
+     *          if an I/O error occurs, or if the output is closed
+     * </ul>
+     *
+     * @param message
+     *         the message
+     *
+     * @return a {@code CompletableFuture} that completes, with this WebSocket,
+     * when the Pong message has been sent
+     */
+    CompletableFuture<WebSocket> sendPong(ByteBuffer message);
+
+    /**
+     * Initiates an orderly closure of this WebSocket's output by
+     * sending a Close message with the given status code and the reason.
+     *
+     * <p> The {@code statusCode} is an integer from the range
+     * {@code 1000 <= code <= 4999}. Status codes {@code 1002}, {@code 1003},
+     * {@code 1006}, {@code 1007}, {@code 1009}, {@code 1010}, {@code 1012},
+     * {@code 1013} and {@code 1015} are illegal. Behaviour in respect to other
+     * status codes is implementation-specific. A legal {@code reason} is a
+     * string that has a UTF-8 representation not longer than {@code 123} bytes.
+     *
+     * <p> A {@code CompletableFuture} returned from this method can
+     * complete exceptionally with:
+     * <ul>
+     * <li> {@link IllegalArgumentException} -
+     *           if {@code statusCode} is illegal, or
+     *           if {@code reason} is illegal
+     * <li> {@link IOException} -
+     *           if an I/O error occurs, or if the output is closed
+     * </ul>
+     *
+     * <p> Unless the {@code CompletableFuture} returned from this method
+     * completes with {@code IllegalArgumentException}, or the method throws
+     * {@code NullPointerException}, the output will be closed.
+     *
+     * <p> If not already closed, the input remains open until a Close message
+     * {@linkplain Listener#onClose(WebSocket, int, String) received}, or
+     * {@code abort} is invoked, or an
+     * {@linkplain Listener#onError(WebSocket, Throwable) error} occurs.
+     *
+     * @apiNote Use the provided integer constant {@link #NORMAL_CLOSURE} as a
+     * status code and an empty string as a reason in a typical case:
+     * <pre>{@code     CompletableFuture<WebSocket> webSocket = ...
+     *    webSocket.thenCompose(ws -> ws.sendText("Hello, ", false))
+     *             .thenCompose(ws -> ws.sendText("world!", true))
+     *             .thenCompose(ws -> ws.sendClose(WebSocket.NORMAL_CLOSURE, ""))
+     *             .join(); }</pre>
+     *
+     * The {@code sendClose} method does not close this WebSocket's input. It
+     * merely closes this WebSocket's output by sending a Close message. To
+     * enforce closing the input, invoke the {@code abort} method. Here is an
+     * example of an application that sends a Close message, and then starts a
+     * timer. Once no data has been received within the specified timeout, the
+     * timer goes off and the alarm aborts {@code WebSocket}:
+     * <pre>{@code     MyAlarm alarm = new MyAlarm(webSocket::abort);
+     *    WebSocket.Listener listener = new WebSocket.Listener() {
+     *
+     *        public CompletionStage<?> onText(WebSocket webSocket,
+     *                                         CharSequence data,
+     *                                         boolean last) {
+     *            alarm.snooze();
+     *            ...
+     *        }
+     *        ...
+     *    };
+     *    ...
+     *    Runnable startTimer = () -> {
+     *        MyTimer idleTimer = new MyTimer();
+     *        idleTimer.add(alarm, 30, TimeUnit.SECONDS);
+     *    };
+     *    webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok").thenRun(startTimer);
+     * } </pre>
+     *
+     * @param statusCode
+     *         the status code
+     * @param reason
+     *         the reason
+     *
+     * @return a {@code CompletableFuture} that completes, with this WebSocket,
+     * when the Close message has been sent
+     */
+    CompletableFuture<WebSocket> sendClose(int statusCode, String reason);
+
+    /**
+     * Increments the counter of invocations of receive methods.
+     *
+     * <p> This WebSocket will invoke {@code onText}, {@code onBinary},
+     * {@code onPing}, {@code onPong} or {@code onClose} methods on the
+     * associated listener (i.e. receive methods) up to {@code n} more times.
+     *
+     * @apiNote The parameter of this method is the number of invocations being
+     * requested from this WebSocket to the associated listener, not the number
+     * of messages. Sometimes a message may be delivered to the listener in a
+     * single invocation, but not always. For example, Ping, Pong and Close
+     * messages are delivered in a single invocation of {@code onPing},
+     * {@code onPong} and {@code onClose} methods respectively. However, whether
+     * or not Text and Binary messages are delivered in a single invocation of
+     * {@code onText} and {@code onBinary} methods depends on the boolean
+     * argument ({@code last}) of these methods. If {@code last} is
+     * {@code false}, then there is more to a message than has been delivered to
+     * the invocation.
+     *
+     * <p> Here is an example of a listener that requests invocations, one at a
+     * time, until a complete message has been accumulated, and then processes
+     * the result:
+     * <pre>{@code     WebSocket.Listener listener = new WebSocket.Listener() {
+     *
+     *        StringBuilder text = new StringBuilder();
+     *
+     *        public CompletionStage<?> onText(WebSocket webSocket,
+     *                                         CharSequence message,
+     *                                         boolean last) {
+     *            text.append(message);
+     *            if (last) {
+     *                processCompleteTextMessage(text);
+     *                text = new StringBuilder();
+     *            }
+     *            webSocket.request(1);
+     *            return null;
+     *        }
+     *    ...
+     *    } } </pre>
+     *
+     * @param n
+     *         the number of invocations
+     *
+     * @throws IllegalArgumentException
+     *         if {@code n <= 0}
+     */
+    void request(long n);
+
+    /**
+     * Returns the subprotocol used by this WebSocket.
+     *
+     * @return the subprotocol, or an empty string if there's no subprotocol
+     */
+    String getSubprotocol();
+
+    /**
+     * Tells whether this WebSocket's output is closed.
+     *
+     * <p> If this method returns {@code true}, subsequent invocations will also
+     * return {@code true}.
+     *
+     * @return {@code true} if closed, {@code false} otherwise
+     */
+    boolean isOutputClosed();
+
+    /**
+     * Tells whether this WebSocket's input is closed.
+     *
+     * <p> If this method returns {@code true}, subsequent invocations will also
+     * return {@code true}.
+     *
+     * @return {@code true} if closed, {@code false} otherwise
+     */
+    boolean isInputClosed();
+
+    /**
+     * Closes this WebSocket's input and output abruptly.
+     *
+     * <p> When this method returns both the input and the output will have been
+     * closed. Any pending send operations will fail with {@code IOException}.
+     * Subsequent invocations of {@code abort} will have no effect.
+     */
+    void abort();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/WebSocketHandshakeException.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+/**
+ * An exception used to signal the opening handshake failed.
+ *
+ * @since 11
+ */
+public final class WebSocketHandshakeException extends IOException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final transient HttpResponse<?> response;
+
+    /**
+     * Constructs a {@code WebSocketHandshakeException} with the given
+     * {@code HttpResponse}.
+     *
+     * @param response
+     *        the {@code HttpResponse} that resulted in the handshake failure
+     */
+    public WebSocketHandshakeException(HttpResponse<?> response) {
+        this.response = response;
+    }
+
+    /**
+     * Returns the server's counterpart of the opening handshake.
+     *
+     * <p> The value may be unavailable ({@code null}) if this exception has
+     * been serialized and then deserialized.
+     *
+     * @return server response
+     */
+    public HttpResponse<?> getResponse() {
+        return response;
+    }
+
+    @Override
+    public WebSocketHandshakeException initCause(Throwable cause) {
+        return (WebSocketHandshakeException) super.initCause(cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/java/net/http/package-info.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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.
+ */
+
+/**
+ * <h2>HTTP Client and WebSocket APIs</h2>
+ *
+ * <p> Provides high-level client interfaces to HTTP (versions 1.1 and 2) and
+ * low-level client interfaces to WebSocket. The main types defined are:
+ *
+ * <ul>
+ *    <li>{@link java.net.http.HttpClient}</li>
+ *    <li>{@link java.net.http.HttpRequest}</li>
+ *    <li>{@link java.net.http.HttpResponse}</li>
+ *    <li>{@link java.net.http.WebSocket}</li>
+ * </ul>
+ *
+ * <p> The protocol-specific requirements are defined in the
+ * <a href="https://tools.ietf.org/html/rfc7540">Hypertext Transfer Protocol
+ * Version 2 (HTTP/2)</a>, the <a href="https://tools.ietf.org/html/rfc2616">
+ * Hypertext Transfer Protocol (HTTP/1.1)</a>, and
+ * <a href="https://tools.ietf.org/html/rfc6455">The WebSocket Protocol</a>.
+ *
+ * <p> Asynchronous tasks and dependent actions of returned {@link
+ * java.util.concurrent.CompletableFuture} instances are executed on the threads
+ * supplied by the client's {@link java.util.concurrent.Executor}, where
+ * practical.
+ *
+ * <p> {@code CompletableFuture}s returned by this API will throw {@link
+ * java.lang.UnsupportedOperationException} for their {@link
+ * java.util.concurrent.CompletableFuture#obtrudeValue(Object) obtrudeValue}
+ * and {@link java.util.concurrent.CompletableFuture#obtrudeException(Throwable)
+ * obtrudeException} methods. Invoking the {@link
+ * java.util.concurrent.CompletableFuture#cancel cancel} method on a {@code
+ * CompletableFuture} returned by this API will not interrupt the underlying
+ * operation, but may be useful to complete, exceptionally, dependent stages
+ * that have not already completed.
+ *
+ * <p> Unless otherwise stated, {@code null} parameter values will cause methods
+ * of all classes in this package to throw {@code NullPointerException}.
+ *
+ * @since 11
+ */
+package java.net.http;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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;
+
+import java.net.InetSocketAddress;
+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.SSLParameters;
+
+import jdk.internal.net.http.common.SSLTube;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.Utils;
+import static jdk.internal.net.http.common.Utils.ServerName;
+
+/**
+ * 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;
+
+    // Setting this property disables HTTPS hostname verification. Use with care.
+    private static final boolean disableHostnameVerification
+            = Utils.isHostnameVerificationDisabled();
+
+    AbstractAsyncSSLConnection(InetSocketAddress addr,
+                               HttpClientImpl client,
+                               ServerName serverName, int port,
+                               String[] alpn) {
+        super(addr, client);
+        this.serverName = serverName.getName();
+        SSLContext context = client.theSSLContext();
+        sslParameters = createSSLParameters(client, serverName, alpn);
+        Log.logParams(sslParameters);
+        engine = createEngine(context, serverName.getName(), port, 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,
+                                                     ServerName serverName,
+                                                     String[] alpn) {
+        SSLParameters sslp = client.sslParameters();
+        SSLParameters sslParameters = Utils.copySSLParameters(sslp);
+        if (!disableHostnameVerification)
+            sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
+        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.isLiteral()) {
+            String name = serverName.getName();
+            if (name != null && name.length() > 0) {
+                sslParameters.setServerNames(List.of(new SNIHostName(name)));
+            }
+        }
+        return sslParameters;
+    }
+
+    private static SSLEngine createEngine(SSLContext context, String serverName, int port,
+                                          SSLParameters sslParameters) {
+        SSLEngine engine = context.createSSLEngine(serverName, port);
+        engine.setUseClientMode(true);
+        engine.setSSLParameters(sslParameters);
+        return engine;
+    }
+
+    @Override
+    final boolean isSecure() {
+        return true;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AbstractSubscription.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,43 @@
+/*
+ * 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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,100 @@
+/*
+ * 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;
+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), addr.getPort(), 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
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -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;
+
+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), addr.getPort(), alpn);
+        this.plainConnection = new PlainTunnelingConnection(addr, proxy, client, proxyHeaders);
+        this.writePublisher = new PlainHttpPublisher();
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        if (debug.on()) debug.log("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 -> {
+                    if (debug.on()) debug.log("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
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,440 @@
+/*
+ * 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);
+        String host;
+        int port;
+        String protocol;
+        InetSocketAddress proxyAddress;
+        if (proxy && (proxyAddress = req.proxy()) != null) {
+            // request sent to server through proxy
+            proxyAddress = req.proxy();
+            host = proxyAddress.getHostString();
+            port = proxyAddress.getPort();
+            protocol = "http"; // we don't support https connection to proxy
+        } else {
+            // direct connection to server or proxy
+            host = uri.getHost();
+            port = uri.getPort();
+            protocol = uri.getScheme();
+        }
+
+        // needs to be instance method in Authenticator
+        return auth.requestPasswordAuthenticationInstance(host,
+                                                          null,
+                                                          port,
+                                                          protocol,
+                                                          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);
+        } 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;
+                URI proxyURI = getProxyURI(req);
+                if (proxyURI != null) {
+                    cache.store(au.scheme, proxyURI, true, 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;
+            }
+            req = HttpRequestImpl.newInstanceForAuthentication(req);
+            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;
+            }
+            req = HttpRequestImpl.newInstanceForAuthentication(req);
+            addBasicCredentials(req, proxy, au.credentials);
+            au.retries++;
+            return req;
+        }
+    }
+
+    // Use a WeakHashMap to make it possible for the HttpClient to
+    // be garbage 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 final class Cache {
+        final LinkedList<CacheEntry> entries = new LinkedList<>();
+
+        Cache() {}
+
+        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 URI normalize(URI uri, boolean isPrimaryKey) {
+        String path = uri.getPath();
+        if (path == null || path.isEmpty()) {
+            // make sure the URI has a path, ignore query and fragment
+            try {
+                return new URI(uri.getScheme(), uri.getAuthority(), "/", null, null);
+            } catch (URISyntaxException e) {
+                throw new InternalError(e);
+            }
+        } else if (isPrimaryKey || !"/".equals(path)) {
+            // remove extraneous components and normalize path
+            return uri.resolve(".");
+        } else {
+            // path == "/" and the URI is not used to store
+            // the primary key in the cache: nothing to do.
+            return uri;
+        }
+    }
+
+    static final 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 = normalize(uri, true).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 = String.valueOf(normalize(uri, false));
+            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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,487 @@
+/*
+ * 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.Logger;
+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
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.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();
+        if (debug.on()) debug.log("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) {
+        if (debug.on())
+            debug.log("%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	Tue Apr 17 08:54:17 2018 -0700
@@ -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.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;
+import jdk.internal.net.http.common.Utils;
+
+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 (Map.Entry<String,List<String>> entry : cookies.entrySet()) {
+                final String hdrname = entry.getKey();
+                if (!hdrname.equalsIgnoreCase("Cookie")
+                        && !hdrname.equalsIgnoreCase("Cookie2"))
+                    continue;
+                List<String> values = entry.getValue();
+                if (values == null || values.isEmpty()) continue;
+                for (String val : values) {
+                    if (Utils.isValidValue(val)) {
+                        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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,575 @@
+/*
+ * 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.Logger;
+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> {
+
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.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
+            if (debug.on()) debug.log("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.
+            if (debug.on()) debug.log("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.on()) {
+            debug.log("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;
+            HttpConnection c = ex == null ? null : ex.connection();
+            Response syntheticResponse = new Response(request, this,
+                    proxyResponse.headers, c, 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 tunneling through HTTP/1.1 proxy
+        // or by sendHeaderAsync (case of HTTP/1.1 SSL tunneling 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(HttpResponse.ResponseInfo hdrs) {
+        return HttpResponse.BodySubscribers.replacing(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?
+            if (debug.on()) debug.log("Upgrading async %s", e.connection());
+            return e.readBodyAsync(this::ignoreBody, false, parentExecutor)
+                .thenCompose((T v) -> {// v is null
+                    debug.log("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);
+                            }
+                            if (debug.on())
+                                debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,217 @@
+/*
+ * 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.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+import java.net.http.HttpResponse;
+import jdk.internal.net.http.common.Logger;
+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> {
+
+    private static final Logger debug =
+            Utils.getDebugLogger("ExchangeImpl"::toString, Utils.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) {
+            if (debug.on())
+                debug.log("get: HTTP/1.1: new Http1Exchange");
+            return createHttp1Exchange(exchange, connection);
+        } else {
+            Http2ClientImpl c2 = exchange.client().client2(); // #### improve
+            HttpRequestImpl request = exchange.request();
+            CompletableFuture<Http2Connection> c2f = c2.getConnectionFor(request);
+            if (debug.on())
+                debug.log("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)
+    {
+        if (debug.on())
+            debug.log("handling HTTP/2 connection creation result");
+        boolean secure = exchange.request().secure();
+        if (t != null) {
+            if (debug.on())
+                debug.log("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();
+                if (debug.on())
+                    debug.log("downgrading to HTTP/1.1 with: %s", as);
+                CompletableFuture<? extends ExchangeImpl<U>> ex =
+                        createHttp1Exchange(exchange, as);
+                return ex;
+            } else {
+                if (debug.on())
+                    debug.log("HTTP/2 connection creation failed "
+                                     + "with unexpected exception: %s", (Object)t);
+                return MinimalFuture.failedFuture(t);
+            }
+        }
+        if (secure && c== null) {
+            if (debug.on())
+                debug.log("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
+            if (debug.on())
+                debug.log("new Http1Exchange, try to upgrade");
+            return createHttp1Exchange(exchange, connection)
+                    .thenApply((e) -> {
+                        exchange.h2Upgrade();
+                        return e;
+                    });
+        } else {
+            if (debug.on()) debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -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;
+
+import java.util.LinkedList;
+import java.util.List;
+
+class FilterFactory {
+
+    // Strictly-ordered list of filters.
+    final LinkedList<Class<? extends HeaderFilter>> filterClasses = new LinkedList<>();
+
+    public void addFilter(Class<? extends HeaderFilter> type) {
+        filterClasses.add(type);
+    }
+
+    LinkedList<HeaderFilter> getFilterChain() {
+        LinkedList<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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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(Locale.US);
+                }
+            } 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(Locale.US);
+                }
+            }
+            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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,704 @@
+/*
+ * 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.function.Consumer;
+import jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.FlowTube.TubeSubscriber;
+import jdk.internal.net.http.common.Logger;
+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 {
+
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.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();
+
+        /**
+         * Called to make sure resources are released when the
+         * when the Http1AsyncReceiver is stopped.
+         * @param error The Http1AsyncReceiver pending error ref,
+         *              if any.
+         */
+        public void close(Throwable error);
+
+    }
+
+    /**
+     * 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 Consumer<Throwable> onError;
+        private final SequentialScheduler scheduler;
+        private volatile boolean cancelled;
+        Http1AsyncDelegateSubscription(SequentialScheduler scheduler,
+                                       Runnable onCancel,
+                                       Consumer<Throwable> onError) {
+            this.scheduler = scheduler;
+            this.onCancel = onCancel;
+            this.onError = onError;
+        }
+        @Override
+        public void request(long n) {
+            if (cancelled) return;
+            try {
+                final Demand demand = demand();
+                if (demand.increase(n)) {
+                    scheduler.runOrSchedule();
+                }
+            } catch (IllegalArgumentException x) {
+                cancelled = true;
+                onError.accept(x);
+            }
+        }
+        @Override
+        public void cancel() {
+            cancelled = true;
+            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;
+    private volatile boolean stopRequested;
+
+    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 && !stopRequested) {
+                Http1AsyncDelegate delegate = this.delegate;
+                if (debug.on())
+                    debug.log("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;
+                if (debug.on())
+                    debug.log("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();
+                    if (debug.on()) debug.log(() -> {
+                        // 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() && !stopRequested);
+                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
+            if (debug.on()) debug.log("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 && (stopRequested || queue.isEmpty())) {
+            // forward error only after emptying the queue.
+            final Object captured = delegate;
+            if (debug.on())
+                debug.log(() -> "flushing " + x + "\n\t delegate: " + captured
+                          + "\t\t queue.isEmpty: " + queue.isEmpty());
+            scheduler.stop();
+            delegate.onReadError(x);
+            if (stopRequested) {
+                // This is the special case where the subscriber
+                // has requested an illegal number of items.
+                // In this case, the error doesn't come from
+                // upstream, but from downstream, and we need to
+                // close the upstream connection.
+                Http1Exchange<?> exchg = owner;
+                stop();
+                if (exchg != null) exchg.connection().close();
+            }
+        }
+    }
+
+    /**
+     * 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);
+        if (debug.on())
+            debug.log("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();
+        if (debug.on())
+            debug.log("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);
+            Consumer<Throwable> onSubscriptionError = (x) -> {
+                setRetryOnError(false);
+                stopRequested = true;
+                onReadError(x);
+            };
+            Runnable cancel = () -> {
+                if (debug.on())
+                    debug.log("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, onSubscriptionError);
+            pending.onSubscribe(subscription);
+            this.delegate = delegate = pending;
+            final Object captured = delegate;
+            if (debug.on())
+                debug.log("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() {
+        if (debug.on()) debug.log("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);
+        }
+        if (debug.on())
+            debug.log("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) {
+                if (debug.on()) debug.log("Unsubscribed %s", delegate);
+                this.delegate = null;
+            }
+        }
+    }
+
+    // Callback: Consumer of ByteBuffer
+    private void asyncReceive(ByteBuffer buf) {
+        if (debug.on())
+            debug.log("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;
+        if (debug.on()) debug.log("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);
+            if (debug.on())
+                debug.log("recorded " + t + "\n\t delegate: " + delegate
+                          + "\t\t queue.isEmpty: " + queue.isEmpty(), ex);
+        }
+        if (queue.isEmpty() || pendingDelegateRef.get() != null || stopRequested) {
+            // 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() {
+        if (debug.on()) debug.log("stopping");
+        scheduler.stop();
+        // make sure ref count is handled properly by
+        // closing the delegate.
+        Http1AsyncDelegate previous = delegate;
+        if (previous != null) previous.close(error);
+        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) {
+                    if (debug.on())
+                        debug.log("Http1TubeSubscriber: requesting one more from upstream");
+                    s.request(1);
+                    return;
+                }
+            }
+            if (debug.on())
+                debug.log("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() {
+            if (debug.on()) debug.log("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()]);
+
+// the assertion looks suspicious, more investigation needed
+//
+//        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;
+        if (debug.on())
+            debug.log("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) {
+            if (debug.on())
+                debug.log("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;
+        if (debug.on())
+            debug.log("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 = "Http1AsyncReceiver("+ flowTag + ")";
+            } 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,658 @@
+/*
+ * 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.Logger;
+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> {
+
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.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> {
+        final MinimalFuture<Flow.Subscription> whenSubscribed = new MinimalFuture<>();
+        private volatile Flow.Subscription subscription;
+        volatile boolean complete;
+        private final Logger debug;
+        Http1BodySubscriber(Logger debug) {
+            assert debug != null;
+            this.debug = debug;
+        }
+
+        /** Final sentinel in the stream of request body. */
+        static final List<ByteBuffer> COMPLETED = List.of(ByteBuffer.allocate(0));
+
+        final void request(long n) {
+            if (debug.on())
+                debug.log("Http1BodySubscriber requesting %d, from %s",
+                          n, subscription);
+            subscription.request(n);
+        }
+
+        final boolean isSubscribed() {
+            return subscription != null;
+        }
+
+        final void setSubscription(Flow.Subscription subscription) {
+            this.subscription = subscription;
+            whenSubscribed.complete(subscription);
+        }
+
+        final void cancelSubscription() {
+            try {
+                subscription.cancel();
+            } catch(Throwable t) {
+                String msg = "Ignoring exception raised when canceling BodyPublisher subscription";
+                if (debug.on()) debug.log("%s: %s", msg, t);
+                Log.logError("{0}: {1}", msg, (Object)t);
+            }
+        }
+
+        static Http1BodySubscriber completeSubscriber(Logger debug) {
+            return new Http1BodySubscriber(debug) {
+                @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;
+        }
+
+        @Override
+        public AbstractSubscription subscription() {
+            return s;
+        }
+
+        @Override
+        public void close(Throwable error) {}
+    }
+
+    @Override
+    HttpConnection connection() {
+        return connection;
+    }
+
+    private void connectFlows(HttpConnection connection) {
+        FlowTube tube =  connection.getConnectionFlow();
+        if (debug.on()) debug.log("%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.
+        if (debug.on()) debug.log("Sending headers only");
+        if (response == null) {
+            response = new Http1Response<>(connection, this, asyncReceiver);
+        }
+
+        if (debug.on()) debug.log("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()) {
+            if (debug.on()) debug.log("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);
+
+                        if (debug.on()) debug.log("requestAction.headers");
+                        List<ByteBuffer> data = requestAction.headers();
+                        synchronized (lock) {
+                            state = State.HEADERS;
+                        }
+                        if (debug.on()) debug.log("setting outgoing with headers");
+                        assert outgoing.isEmpty() : "Unexpected outgoing:" + outgoing;
+                        appendToOutgoing(data);
+                        cf.complete(null);
+                        return cf;
+                    } catch (Throwable t) {
+                        if (debug.on()) debug.log("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(debug);
+                appendToOutgoing(Http1BodySubscriber.COMPLETED);
+            } else {
+                // start
+                bodySubscriber.whenSubscribed
+                        .thenAccept((s) -> requestMoreBody());
+            }
+        } catch (Throwable t) {
+            cancelImpl(t);
+            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);
+            if (debug.on())
+                debug.log(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(new ResponseInfoImpl(response.responseCode(),
+                                                                  response.responseHeaders(),
+                                                                  HTTP_1_1));
+        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;
+        Throwable error;
+        synchronized (lock) {
+            if ((error = failed) == null) {
+                failed = error = 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;
+            Throwable x = error;
+            while (!toComplete.isEmpty()) {
+                CompletableFuture<?> cf = toComplete.poll();
+                exec.execute(() -> {
+                    if (cf.completeExceptionally(x)) {
+                        if (debug.on())
+                            debug.log("completed cf with %s", (Object) x);
+                    }
+                });
+            }
+        }
+    }
+
+    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) {
+        if (debug.on()) debug.log("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();
+    }
+
+    private void requestMoreBody() {
+        try {
+            if (debug.on()) debug.log("requesting more body from the subscriber");
+            bodySubscriber.request(1);
+        } catch (Throwable t) {
+            if (debug.on()) debug.log("Subscription::request failed", t);
+            cancelImpl(t);
+            bodySentCF.completeExceptionally(t);
+        }
+    }
+
+    // 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(() -> {
+                    headersSentCF.completeExceptionally(dp.throwable);
+                    bodySentCF.completeExceptionally(dp.throwable);
+                    connection.close();
+                });
+                return dp;
+            }
+
+            switch (state) {
+                case HEADERS:
+                    state = State.BODY;
+                    // completeAsync, since dependent tasks should run in another thread
+                    if (debug.on()) debug.log("initiating completion of headersSentCF");
+                    headersSentCF.completeAsync(() -> this, exec);
+                    break;
+                case BODY:
+                    if (dp.data == Http1BodySubscriber.COMPLETED) {
+                        state = State.COMPLETING;
+                        if (debug.on()) debug.log("initiating completion of bodySentCF");
+                        bodySentCF.completeAsync(() -> this, exec);
+                    } else {
+                        exec.execute(this::requestMoreBody);
+                    }
+                    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 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;
+            if (debug.on()) debug.log("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;
+                if (debug.on()) debug.log("WriteTask");
+                if (subscriber == null) {
+                    if (debug.on()) debug.log("no subscriber yet");
+                    return;
+                }
+                if (debug.on()) debug.log(() -> "hasOutgoing = " + hasOutgoing());
+                while (hasOutgoing() && demand.tryDecrement()) {
+                    DataPair dp = getOutgoing();
+
+                    if (dp.throwable != null) {
+                        if (debug.on()) debug.log("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;
+                            }
+                            if (debug.on())
+                                debug.log("completed, stopping %s", writeScheduler);
+                            writeScheduler.stop();
+                            // Do nothing more. Just do not publish anything further.
+                            // The next Subscriber will eventually take over.
+
+                        } else {
+                            if (debug.on())
+                                debug.log("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);
+                if (debug.on())
+                    debug.log("subscription request(%d), demand=%s", n, demand);
+                writeScheduler.runOrSchedule(client.theExecutor());
+            }
+
+            @Override
+            public void cancel() {
+                if (debug.on()) debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,469 @@
+/*
+ * 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.Logger;
+import jdk.internal.net.http.common.Utils;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+/**
+ *  An HTTP/1.1 request.
+ */
+class Http1Request {
+
+    private static final String COOKIE_HEADER = "Cookie";
+    private static final BiPredicate<String,List<String>> NOCOOKIES =
+            (k,v) -> !COOKIE_HEADER.equalsIgnoreCase(k);
+
+    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");
+            if (s.endsWith("\n\n")) s = s.substring(0, s.length() - 2);
+            Log.logHeaders("REQUEST HEADERS:\n{0}\n", s);
+        }
+    }
+
+
+    private void collectHeaders0(StringBuilder sb) {
+        BiPredicate<String,List<String>> filter =
+                connection.headerFilter(request);
+
+        // Filter out 'Cookie:' headers, we will collect them at the end.
+        BiPredicate<String,List<String>> nocookies =
+                NOCOOKIES.and(filter);
+
+        // 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, nocookies);
+
+        // If we're sending this request through a tunnel,
+        // don't send any user-supplied proxy-* headers
+        // to the target server.
+        collectHeaders1(sb, userHeaders, nocookies);
+
+        // Gather all 'Cookie:' headers and concatenate their
+        // values in a single line.
+        collectCookies(sb, COOKIE_HEADER,
+                systemHeaders, userHeaders, filter);
+
+        // terminate headers
+        sb.append('\r').append('\n');
+    }
+
+    // Concatenate any 'Cookie:' header in a single line, as mandated
+    // by RFC 6265, section 5.4:
+    //
+    // <<When the user agent generates an HTTP request, the user agent MUST
+    //   NOT attach more than one Cookie header field.>>
+    //
+    // This constraint is relaxed for the HTTP/2 protocol, which
+    // explicitly allows sending multiple Cookie header fields.
+    // RFC 7540 section 8.1.2.5:
+    //
+    // <<To allow for better compression efficiency, the Cookie header
+    //   field MAY be split into separate header fields, each with one or
+    //   more cookie-pairs.>>
+    //
+    // This method will therefore concatenate multiple Cookie header field
+    // values into a single field, in a similar way than was implemented in
+    // the legacy HttpURLConnection.
+    //
+    // Note that at this point this method performs no further validation
+    // on the actual field-values, except to check that they do not contain
+    // any illegal character for header field values.
+    //
+    private void collectCookies(StringBuilder sb,
+                                String key,
+                                HttpHeaders system,
+                                HttpHeaders user,
+                                BiPredicate<String, List<String>> filter) {
+        List<String> systemList = system.allValues(key);
+        if (systemList != null && !filter.test(key, systemList)) systemList = null;
+        List<String> userList = user.allValues(key);
+        if (userList != null && !filter.test(key, userList)) userList = null;
+        boolean found = false;
+        if (systemList != null) {
+            for (String cookie : systemList) {
+                if (!found) {
+                    found = true;
+                    sb.append(key).append(':').append(' ');
+                } else {
+                    sb.append(';').append(' ');
+                }
+                sb.append(cookie);
+            }
+        }
+        if (userList != null) {
+            for (String cookie : userList) {
+                if (!found) {
+                    found = true;
+                    sb.append(key).append(':').append(' ');
+                } else {
+                    sb.append(';').append(' ');
+                }
+                sb.append(cookie);
+            }
+        }
+        if (found) sb.append('\r').append('\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(' ')
+                        .append(value)
+                        .append('\r').append('\n');
+            }
+        }
+    }
+
+    private String getPathAndQuery(URI uri) {
+        String path = uri.getRawPath();
+        String query = uri.getRawQuery();
+        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;
+    }
+
+    final class StreamSubscriber extends Http1BodySubscriber {
+
+        StreamSubscriber() { super(debug); }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (isSubscribed()) {
+                Throwable t = new IllegalStateException("already subscribed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                setSubscription(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;
+
+            cancelSubscription();
+            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?
+
+            }
+        }
+    }
+
+    final class FixedContentSubscriber extends Http1BodySubscriber {
+
+        private volatile long contentWritten;
+        FixedContentSubscriber() { super(debug); }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (isSubscribed()) {
+                Throwable t = new IllegalStateException("already subscribed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                setSubscription(subscription);
+            }
+        }
+
+        @Override
+        public void onNext(ByteBuffer item) {
+            if (debug.on()) debug.log("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) {
+                    cancelSubscription();
+                    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) {
+            if (debug.on()) debug.log("onError");
+            if (complete)  // TODO: error?
+                return;
+
+            cancelSubscription();
+            http1Exchange.appendToOutgoing(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            if (debug.on()) debug.log("onComplete");
+            if (complete) {
+                Throwable t = new IllegalStateException("subscription already completed");
+                http1Exchange.appendToOutgoing(t);
+            } else {
+                complete = true;
+                long written = contentWritten;
+                if (contentLength > written) {
+                    cancelSubscription();
+                    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);
+    }
+
+    final Logger debug = Utils.getDebugLogger(this::toString, Utils.DEBUG);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,763 @@
+/*
+ * 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.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+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.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.Logger;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.Utils;
+
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+import static java.net.http.HttpResponse.BodySubscribers.discarding;
+
+/**
+ * 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;
+
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
+    final static AtomicLong responseCount = new AtomicLong();
+    final long id = responseCount.incrementAndGet();
+
+    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);
+    }
+
+    String dbgTag;
+    private String dbgString() {
+        String dbg = dbgTag;
+        if (dbg == null) {
+            String cdbg = connection.dbgTag;
+            if (cdbg != null) {
+                dbgTag = dbg = "Http1Response(id=" + id + ", " + cdbg + ")";
+            } else {
+                dbg = "Http1Response(id=" + id + ")";
+            }
+        }
+        return dbg;
+    }
+
+    // The ClientRefCountTracker is used to track the state
+    // of a pending operation. Altough there usually is a single
+    // point where the operation starts, it may terminate at
+    // different places.
+    private final class ClientRefCountTracker {
+        final HttpClientImpl client = connection.client();
+        // state & 0x01 != 0 => acquire called
+        // state & 0x02 != 0 => tryRelease called
+        byte state;
+
+        public synchronized void acquire() {
+            if (state == 0) {
+                // increment the reference count on the HttpClientImpl
+                // to prevent the SelectorManager thread from exiting
+                // until our operation is complete.
+                if (debug.on())
+                    debug.log("Operation started: incrementing ref count for %s", client);
+                client.reference();
+                state = 0x01;
+            } else {
+                if (debug.on())
+                    debug.log("Operation ref count for %s is already %s",
+                              client, ((state & 0x2) == 0x2) ? "released." : "incremented!" );
+                assert (state & 0x01) == 0 : "reference count already incremented";
+            }
+        }
+
+        public synchronized void tryRelease() {
+            if (state == 0x01) {
+                // 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.
+                if (debug.on())
+                    debug.log("Operation finished: decrementing ref count for %s", client);
+                client.unreference();
+            } else if (state == 0) {
+                if (debug.on())
+                    debug.log("Operation finished: releasing ref count for %s", client);
+            } else if ((state & 0x02) == 0x02) {
+                if (debug.on())
+                    debug.log("ref count for %s already released", client);
+            }
+            state |= 0x02;
+        }
+    }
+
+    public CompletableFuture<Response> readHeadersAsync(Executor executor) {
+        if (debug.on())
+            debug.log("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;
+                if (debug.on())
+                    debug.log("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,
+                                        connection,
+                                        responseCode,
+                                        HTTP_1_1);
+
+                if (Log.headers()) {
+                    StringBuilder sb = new StringBuilder("RESPONSE HEADERS:\n");
+                    Log.dumpHeaders(sb, "    ", headers);
+                    Log.logHeaders(sb.toString());
+                }
+
+                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(discarding(), true, executor);
+        }
+    }
+
+    static final Flow.Subscription NOP = new Flow.Subscription() {
+        @Override
+        public void request(long n) { }
+        public void cancel() { }
+    };
+
+    /**
+     * The Http1AsyncReceiver ensures that all calls to
+     * the subscriber, including onSubscribe, occur sequentially.
+     * There could however be some race conditions that could happen
+     * in case of unexpected errors thrown at unexpected places, which
+     * may cause onError to be called multiple times.
+     * The Http1BodySubscriber will ensure that the user subscriber
+     * is actually completed only once - and only after it is
+     * subscribed.
+     * @param <U> The type of response.
+     */
+    final static class Http1BodySubscriber<U> implements HttpResponse.BodySubscriber<U> {
+        final HttpResponse.BodySubscriber<U> userSubscriber;
+        final AtomicBoolean completed = new AtomicBoolean();
+        volatile Throwable withError;
+        volatile boolean subscribed;
+        Http1BodySubscriber(HttpResponse.BodySubscriber<U> userSubscriber) {
+            this.userSubscriber = userSubscriber;
+        }
+
+        // propagate the error to the user subscriber, even if not
+        // subscribed yet.
+        private void propagateError(Throwable t) {
+            assert t != null;
+            try {
+                // if unsubscribed at this point, it will not
+                // get subscribed later - so do it now and
+                // propagate the error
+                if (subscribed == false) {
+                    subscribed = true;
+                    userSubscriber.onSubscribe(NOP);
+                }
+            } finally  {
+                // if onError throws then there is nothing to do
+                // here: let the caller deal with it by logging
+                // and closing the connection.
+                userSubscriber.onError(t);
+            }
+        }
+
+        // complete the subscriber, either normally or exceptionally
+        // ensure that the subscriber is completed only once.
+        private void complete(Throwable t) {
+            if (completed.compareAndSet(false, true)) {
+                t  = withError = Utils.getCompletionCause(t);
+                if (t == null) {
+                    assert subscribed;
+                    try {
+                        userSubscriber.onComplete();
+                    } catch (Throwable x) {
+                        propagateError(t = withError = Utils.getCompletionCause(x));
+                        // rethrow and let the caller deal with it.
+                        // (i.e: log and close the connection)
+                        // arguably we could decide to not throw and let the
+                        // connection be reused since we should have received and
+                        // parsed all the bytes when we reach here.
+                        throw x;
+                    }
+                } else {
+                    propagateError(t);
+                }
+            }
+        }
+
+        @Override
+        public CompletionStage<U> getBody() {
+            return userSubscriber.getBody();
+        }
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            if (!subscribed) {
+                subscribed = true;
+                userSubscriber.onSubscribe(subscription);
+            } else {
+                // could be already subscribed and completed
+                // if an unexpected error occurred before the actual
+                // subscription - though that's not supposed
+                // happen.
+                assert completed.get();
+            }
+        }
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            assert !completed.get();
+            userSubscriber.onNext(item);
+        }
+        @Override
+        public void onError(Throwable throwable) {
+            complete(throwable);
+        }
+        @Override
+        public void onComplete() {
+            complete(null);
+        }
+    }
+
+    public <U> CompletableFuture<U> readBody(HttpResponse.BodySubscriber<U> p,
+                                         boolean return2Cache,
+                                         Executor executor) {
+        this.return2Cache = return2Cache;
+        final Http1BodySubscriber<U> subscriber = new Http1BodySubscriber<>(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();
+        ClientRefCountTracker refCountTracker = new ClientRefCountTracker();
+
+        // We need to keep hold on the client facade until the
+        // tracker has been incremented.
+        connection.client().reference();
+        executor.execute(() -> {
+            try {
+                content = new ResponseContent(
+                        connection, clen, headers, subscriber,
+                        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.
+                refCountTracker.acquire();
+                bodyReader.start(content.getBodyParser(
+                    (t) -> {
+                        try {
+                            if (t != null) {
+                                subscriber.onError(t);
+                                connection.close();
+                                cf.completeExceptionally(t);
+                            }
+                        } finally {
+                            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) {
+                            if (debug.on()) debug.log("Finished reading body: " + s);
+                            assert s == State.READING_BODY;
+                        }
+                        if (t != null) {
+                            subscriber.onError(t);
+                            cf.completeExceptionally(t);
+                        }
+                    } catch (Throwable x) {
+                        // not supposed to happen
+                        asyncReceiver.onReadError(x);
+                    } finally {
+                        // we're done: release the ref count for
+                        // the current operation.
+                        refCountTracker.tryRelease();
+                    }
+                });
+                connection.addTrailingOperation(trailingOp);
+            } catch (Throwable t) {
+               if (debug.on()) debug.log("Failed reading body: " + t);
+                try {
+                    subscriber.onError(t);
+                    cf.completeExceptionally(t);
+                } finally {
+                    asyncReceiver.onReadError(t);
+                }
+            } finally {
+                connection.client().unreference();
+            }
+        });
+        try {
+            p.getBody().whenComplete((U u, Throwable t) -> {
+                if (t == null)
+                    cf.complete(u);
+                else
+                    cf.completeExceptionally(t);
+            });
+        } catch (Throwable t) {
+            cf.completeExceptionally(t);
+            asyncReceiver.setRetryOnError(false);
+            asyncReceiver.onReadError(t);
+        }
+
+        return cf.whenComplete((s,t) -> {
+            if (t != null) {
+                // If an exception occurred, release the
+                // ref count for the current operation, as
+                // it may never be triggered otherwise
+                // (BodySubscriber ofInputStream)
+                // If there was no exception then the
+                // ref count will be/have been released when
+                // the last byte of the response is/was received
+                refCountTracker.tryRelease();
+            }
+        });
+    }
+
+
+    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.
+            if (debug.on())
+                debug.log(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.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();
+                if (debug.on())
+                    debug.log("Sending " + b.remaining() + "/" + b.capacity()
+                              + " bytes to header parser");
+                if (parser.parse(b)) {
+                    count -= b.remaining();
+                    if (debug.on())
+                        debug.log("Parsing headers completed. bytes=" + count);
+                    onComplete.accept(State.READING_HEADERS);
+                    cf.complete(State.READING_HEADERS);
+                }
+            } catch (Throwable t) {
+                if (debug.on())
+                    debug.log("Header parser failed to handle buffer: " + t);
+                cf.completeExceptionally(t);
+            }
+        }
+
+        @Override
+        public void close(Throwable error) {
+            // if there's no error nothing to do: the cf should/will
+            // be completed.
+            if (error != null) {
+                CompletableFuture<State> cf = this.cf;
+                if (cf != null) {
+                    if (debug.on())
+                        debug.log("close: completing header parser CF with " + error);
+                    cf.completeExceptionally(error);
+                }
+            }
+        }
+    }
+
+    // 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;
+            try {
+                parser.onSubscribe(s);
+            } catch (Throwable t) {
+                cf.completeExceptionally(t);
+                throw t;
+            }
+        }
+
+        @Override
+        final void handle(ByteBuffer b,
+                          BodyParser parser,
+                          CompletableFuture<State> cf) {
+            assert cf != null : "parsing not started";
+            assert parser != null : "no parser";
+            try {
+                if (debug.on())
+                    debug.log("Sending " + b.remaining() + "/" + b.capacity()
+                              + " bytes to body parser");
+                parser.accept(b);
+            } catch (Throwable t) {
+                if (debug.on())
+                    debug.log("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 final void close(Throwable error) {
+            CompletableFuture<State> cf = this.cf;
+            if (cf != null && !cf.isDone()) {
+                // we want to make sure dependent actions are triggered
+                // in order to make sure the client reference count
+                // is decremented
+                if (error != null) {
+                    if (debug.on())
+                        debug.log("close: completing body parser CF with " + error);
+                    cf.completeExceptionally(error);
+                } else {
+                    if (debug.on())
+                        debug.log("close: completing body parser CF");
+                    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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,257 @@
+/*
+ * 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;
+import java.net.URI;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashSet;
+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.Logger;
+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 {
+
+    final static Logger debug =
+            Utils.getDebugLogger("Http2ClientImpl"::toString, Utils.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) {
+                if (connection.closed) {
+                    if (debug.on())
+                        debug.log("removing found closed connection: %s", connection);
+                    connections.remove(key);
+                } else {
+                    // fast path if connection already exists
+                    if (debug.on())
+                        debug.log("found connection in the pool: %s", connection);
+                    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
+                if (debug.on()) debug.log("not found in connection pool");
+                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) {
+        if (debug.on()) debug.log("offering to the connection pool: %s", c);
+        if (c.closed) {
+            if (debug.on())
+                debug.log("skipping offered closed connection: %s", c);
+            return false;
+        }
+
+        String key = c.key();
+        synchronized(this) {
+            Http2Connection c1 = connections.putIfAbsent(key, c);
+            if (c1 != null) {
+                c.setSingleStream(true);
+                if (debug.on())
+                    debug.log("existing entry in connection pool for %s", key);
+                return false;
+            }
+            if (debug.on())
+                debug.log("put in the connection pool: %s", c);
+            return true;
+        }
+    }
+
+    void deleteConnection(Http2Connection c) {
+        if (debug.on())
+            debug.log("removing from the connection pool: %s", c);
+        synchronized (this) {
+            connections.remove(c.key());
+            if (debug.on())
+                debug.log("removed from the connection pool: %s", c);
+        }
+    }
+
+    void stop() {
+        if (debug.on()) debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,1275 @@
+/*
+ * 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.io.UncheckedIOException;
+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.Locale;
+import java.util.Map;
+import java.util.Set;
+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.Logger;
+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  {
+
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
+    final static Logger DEBUG_LOGGER =
+            Utils.getDebugLogger("Http2Connection"::toString, Utils.DEBUG);
+    private final Logger debugHpack =
+            Utils.getHpackLogger(this::dbgString, Utils.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) {
+                if (debug.on())
+                    debug.log("Preface not sent: buffering %d", buf.remaining());
+                synchronized (this) {
+                    if (!prefaceSent) {
+                        if (pending == null) pending = new ArrayList<>();
+                        pending.add(buf);
+                        if (debug.on())
+                            debug.log("there are now %d bytes buffered waiting for preface to be sent"
+                                    + Utils.remaining(pending)
+                            );
+                        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
+                if (debug.on()) debug.log(() -> "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) {
+                if (debug.on()) debug.log("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));
+        if (debugHpack.on()) {
+            debugHpack.log("For the record:" + super.toString());
+            debugHpack.log("Decoder created: %s", hpackIn);
+            debugHpack.log("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("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(); // tunnel or plain clear connection through proxy
+        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 && !isSecure) {
+            // clear connection through proxy: use
+            // proxy host / proxy port
+            host = proxy.getHostString();
+            port = proxy.getPort();
+        } else {
+            // either secure tunnel connection through proxy
+            // or direct connection to host, but in either
+            // case only that host can be reached through
+            // the connection: use target host / target port
+            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
+    {
+        if (debugHpack.on()) debugHpack.log("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;
+                    if (debug.on())
+                        debug.log(() -> "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) {
+                if (debug.on()) debug.log("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);
+                if (debug.on()) debug.log("H2 processed buffered data");
+            } else {
+                long c = ++count;
+                if (debug.on())
+                    debug.log("H2 Receiving(%d): %d", c, b.remaining());
+                framesController.processReceivedData(framesDecoder, buffer);
+                if (debug.on()) debug.log("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) {
+        if (debug.on()) debug.log(() -> "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 isServerInitiatedStream(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 {
+                if (debug.on())
+                    debug.log(() -> "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
+                    DecodingCallback decoder = new ValidatingHeadersConsumer();
+                    try {
+                        decodeHeaders((HeaderFrame) frame, decoder);
+                    } catch (UncheckedIOException e) {
+                        protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());
+                        return;
+                    }
+                }
+
+                if (!(frame instanceof ResetFrame)) {
+                    if (isServerInitiatedStream(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;
+                try {
+                    handlePushPromise(stream, pp);
+                } catch (UncheckedIOException e) {
+                    protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());
+                    return;
+                }
+            } else if (frame instanceof HeaderFrame) {
+                // decode headers (or continuation)
+                try {
+                    decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer());
+                } catch (UncheckedIOException e) {
+                    protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());
+                    return;
+                }
+                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 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) {
+        if (debug.on()) debug.log("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().streamUnreference();
+        }
+        // ## 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 newWindowSize = frame.getParameter(INITIAL_WINDOW_SIZE);
+            if (newWindowSize != -1) {
+                int oldWindowSize = serverSettings.getParameter(INITIAL_WINDOW_SIZE);
+                int diff = newWindowSize - oldWindowSize;
+                if (diff != 0) {
+                    windowController.adjustActiveStreams(diff);
+                }
+            }
+
+            serverSettings.update(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.enqueueUnordered(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");
+        if (debug.on())
+            debug.log("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().streamReference();
+        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(Locale.US);
+                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();
+                    if (debug.on())
+                        debug.log("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) {
+                    if (debug.on()) debug.log("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) {
+                if (debug.on())
+                    debug.log("onSubscribe: requesting Long.MAX_VALUE for reading");
+                subscription.request(Long.MAX_VALUE);
+            } else {
+                if (debug.on()) debug.log("onSubscribe: already completed");
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            if (debug.on()) debug.log(() -> "onNext: got " + Utils.remaining(item)
+                    + " bytes in " + item.size() + " buffers");
+            queue.addAll(item);
+            scheduler.runOrSchedule(client().theExecutor());
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            if (debug.on()) debug.log(() -> "onError: " + throwable);
+            error = throwable;
+            completed = true;
+            scheduler.runOrSchedule(client().theExecutor());
+        }
+
+        @Override
+        public void onComplete() {
+            if (debug.on()) debug.log("EOF");
+            error = new EOFException("EOF reached while reading");
+            completed = true;
+            scheduler.runOrSchedule(client().theExecutor());
+        }
+
+        @Override
+        public void dropSubscription() {
+            if (debug.on()) debug.log("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() + ")";
+    }
+
+    static class HeaderDecoder extends ValidatingHeadersConsumer {
+
+        HttpHeadersImpl headers;
+
+        HeaderDecoder() {
+            this.headers = new HttpHeadersImpl();
+        }
+
+        @Override
+        public void onDecoded(CharSequence name, CharSequence value) {
+            String n = name.toString();
+            String v = value.toString();
+            super.onDecoded(n, v);
+            headers.addHeader(n, v);
+        }
+
+        HttpHeadersImpl headers() {
+            return headers;
+        }
+    }
+
+    /*
+     * Checks RFC 7540 rules (relaxed) compliance regarding pseudo-headers.
+     */
+    static class ValidatingHeadersConsumer implements DecodingCallback {
+
+        private static final Set<String> PSEUDO_HEADERS =
+                Set.of(":authority", ":method", ":path", ":scheme", ":status");
+
+        /** Used to check that if there are pseudo-headers, they go first */
+        private boolean pseudoHeadersEnded;
+
+        /**
+         * Called when END_HEADERS was received. This consumer may be invoked
+         * again after reset() is called, but for a whole new set of headers.
+         */
+        void reset() {
+            pseudoHeadersEnded = false;
+        }
+
+        @Override
+        public void onDecoded(CharSequence name, CharSequence value)
+                throws UncheckedIOException
+        {
+            String n = name.toString();
+            if (n.startsWith(":")) {
+                if (pseudoHeadersEnded) {
+                    throw newException("Unexpected pseudo-header '%s'", n);
+                } else if (!PSEUDO_HEADERS.contains(n)) {
+                    throw newException("Unknown pseudo-header '%s'", n);
+                }
+            } else {
+                pseudoHeadersEnded = true;
+                if (!Utils.isValidName(n)) {
+                    throw newException("Bad header name '%s'", n);
+                }
+            }
+            String v = value.toString();
+            if (!Utils.isValidValue(v)) {
+                throw newException("Bad header value '%s'", v);
+            }
+        }
+
+        private UncheckedIOException newException(String message, String header)
+        {
+            return new UncheckedIOException(
+                    new IOException(String.format(message, header)));
+        }
+    }
+
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -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 implements 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,155 @@
+/*
+ * 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;
+import jdk.internal.net.http.common.OperationTrackers.Trackable;
+import jdk.internal.net.http.common.OperationTrackers.Tracker;
+
+/**
+ * An HttpClientFacade is a simple class that wraps an HttpClient implementation
+ * and delegates everything to its implementation delegate.
+ */
+final class HttpClientFacade extends HttpClient implements Trackable {
+
+    final HttpClientImpl impl;
+
+    /**
+     * Creates an HttpClientFacade.
+     */
+    HttpClientFacade(HttpClientImpl impl) {
+        this.impl = impl;
+    }
+
+    @Override // for tests
+    public Tracker getOperationsTracker() {
+        return impl.getOperationsTracker();
+    }
+
+    @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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,1166 @@
+/*
+ * 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.Reference;
+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.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+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.Logger;
+import jdk.internal.net.http.common.Pair;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.common.OperationTrackers.Trackable;
+import jdk.internal.net.http.common.OperationTrackers.Tracker;
+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.
+ */
+final class HttpClientImpl extends HttpClient implements Trackable {
+
+    static final boolean DEBUGELAPSED = Utils.TESTING || Utils.DEBUG;  // dev flag
+    static final boolean DEBUGTIMEOUT = false; // dev flag
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
+    final Logger debugelapsed = Utils.getDebugLogger(this::dbgString, DEBUGELAPSED);
+    final 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();
+    private final AtomicLong pendingHttp2StreamCount = 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);
+        if (debug.on())
+            debug.log("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 http2Count = pendingHttp2StreamCount.get();
+        final long webSocketCount = pendingWebSocketCount.get();
+        if (count == 0 && facade() == null) {
+            selmgr.wakeupSelector();
+        }
+        assert httpCount >= 0 : "count of HTTP/1.1 operations < 0";
+        assert http2Count >= 0 : "count of HTTP/2 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 streamReference() {
+        pendingHttp2StreamCount.incrementAndGet();
+        return pendingOperationCount.incrementAndGet();
+    }
+
+    // Decrements the pendingOperationCount.
+    final long streamUnreference() {
+        final long count = pendingOperationCount.decrementAndGet();
+        final long http2Count = pendingHttp2StreamCount.decrementAndGet();
+        final long httpCount = pendingHttpRequestCount.get();
+        final long webSocketCount = pendingWebSocketCount.get();
+        if (count == 0 && facade() == null) {
+            selmgr.wakeupSelector();
+        }
+        assert httpCount >= 0 : "count of HTTP/1.1 operations < 0";
+        assert http2Count >= 0 : "count of HTTP/2 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();
+        final long http2Count = pendingHttp2StreamCount.get();
+        if (count == 0 && facade() == null) {
+            selmgr.wakeupSelector();
+        }
+        assert httpCount >= 0 : "count of HTTP/1.1 operations < 0";
+        assert http2Count >= 0 : "count of HTTP/2 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();
+    }
+
+    final static class HttpClientTracker implements Tracker {
+        final AtomicLong httpCount;
+        final AtomicLong http2Count;
+        final AtomicLong websocketCount;
+        final AtomicLong operationsCount;
+        final Reference<?> reference;
+        final String name;
+        HttpClientTracker(AtomicLong http,
+                          AtomicLong http2,
+                          AtomicLong ws,
+                          AtomicLong ops,
+                          Reference<?> ref,
+                          String name) {
+            this.httpCount = http;
+            this.http2Count = http2;
+            this.websocketCount = ws;
+            this.operationsCount = ops;
+            this.reference = ref;
+            this.name = name;
+        }
+        @Override
+        public long getOutstandingOperations() {
+            return operationsCount.get();
+        }
+        @Override
+        public long getOutstandingHttpOperations() {
+            return httpCount.get();
+        }
+        @Override
+        public long getOutstandingHttp2Streams() { return http2Count.get(); }
+        @Override
+        public long getOutstandingWebSocketOperations() {
+            return websocketCount.get();
+        }
+        @Override
+        public boolean isFacadeReferenced() {
+            return reference.get() != null;
+        }
+        @Override
+        public String getName() {
+            return name;
+        }
+    }
+
+    public Tracker getOperationsTracker() {
+        return new HttpClientTracker(pendingHttpRequestCount,
+                pendingHttp2StreamCount,
+                pendingWebSocketCount,
+                pendingOperationCount,
+                facadeRef,
+                dbgTag);
+    }
+
+    // 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);
+    }
+
+    /**
+     * 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.on()) {
+            debugelapsed.log(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, null).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)
+    {
+        Objects.requireNonNull(userRequest);
+        Objects.requireNonNull(responseHandler);
+
+        AccessControlContext acc = null;
+        if (System.getSecurityManager() != null)
+            acc = AccessController.getContext();
+
+        // Clone the, possibly untrusted, HttpRequest
+        HttpRequestImpl requestImpl = new HttpRequestImpl(userRequest, proxySelector);
+        if (requestImpl.method().equals("CONNECT"))
+            throw new IllegalArgumentException("Unsupported method CONNECT");
+
+        long start = DEBUGELAPSED ? System.nanoTime() : 0;
+        reference();
+        try {
+            if (debugelapsed.on())
+                debugelapsed.log("ClientImpl (async) send %s", userRequest);
+
+            Executor executor = acc == null
+                    ? this.executor
+                    : new PrivilegedExecutor(this.executor, acc);
+
+            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
+            res = res.whenCompleteAsync((r, t) -> { /* do nothing */}, executor);
+
+            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.internal.httpclient.selectorTimeout=<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.getIntegerProperty(
+                "jdk.internal.httpclient.selectorTimeout",
+                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 List<AsyncTriggerEvent> deregistrations;
+        private final Logger debug;
+        private final 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<>();
+            deregistrations = new ArrayList<>();
+            selector = Selector.open();
+        }
+
+        void eventUpdated(AsyncEvent e) throws ClosedChannelException {
+            if (Thread.currentThread() == this) {
+                SelectionKey key = e.channel().keyFor(selector);
+                if (key != null && key.isValid()) {
+                    SelectorAttachment sa = (SelectorAttachment) key.attachment();
+                    sa.register(e);
+                } else if (e.interestOps() != 0){
+                    // We don't care about paused events.
+                    // These are actually handled by
+                    // SelectorAttachment::resetInterestOps later on.
+                    // But if we reach here when trying to resume an
+                    // event then it's better to fail fast.
+                    if (debug.on()) debug.log("No key for channel");
+                    e.abort(new IOException("No key for channel"));
+                }
+            } 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() {
+            if (debug.on()) debug.log("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<>();
+            List<Runnable> resetList = new ArrayList<>();
+            try {
+                while (!Thread.currentThread().isInterrupted()) {
+                    synchronized (this) {
+                        assert errorList.isEmpty();
+                        assert readyList.isEmpty();
+                        assert resetList.isEmpty();
+                        for (AsyncTriggerEvent event : deregistrations) {
+                            event.handle();
+                        }
+                        deregistrations.clear();
+                        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);
+                                if (debug.on())
+                                    debug.log("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();
+                    if (debugtimeout.on())
+                        debugtimeout.log("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();
+                    if (debugtimeout.on())
+                        debugtimeout.log("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);
+
+                    if (debugtimeout.on())
+                        debugtimeout.log("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);
+                        resetList.add(() -> sa.resetInterestOps(eventsOccurred));
+                    }
+
+                    selector.selectNow(); // complete cancellation
+                    selector.selectedKeys().clear();
+
+                    // handle selected events
+                    readyList.forEach((e) -> handleEvent(e, null));
+                    readyList.clear();
+
+                    // handle errors (closed channels etc...)
+                    errorList.forEach((p) -> handleEvent(p.first, p.second));
+                    errorList.clear();
+
+                    // reset interest ops for selected channels
+                    resetList.forEach(r -> r.run());
+                    resetList.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);
+                }
+                if (debug.on()) debug.log("shutting down", e);
+                if (Utils.ASSERTIONSENABLED && !debug.on()) {
+                    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();
+            }
+        }
+    }
+
+    final String debugInterestOps(SelectableChannel channel) {
+        try {
+            SelectionKey key = channel.keyFor(selmgr.selector);
+            if (key == null) return "channel not registered with selector";
+            String keyInterestOps = key.isValid()
+                    ? "key.interestOps=" + key.interestOps() : "invalid key";
+            return String.format("channel registered with selector, %s, sa.interestOps=%s",
+                                 keyInterestOps,
+                                 ((SelectorAttachment)key.attachment()).interestOps);
+        } catch (Throwable t) {
+            return String.valueOf(t);
+        }
+    }
+
+    /**
+     * 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 Logger debug =
+                Utils.getDebugLogger("SelectorAttachment"::toString, Utils.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();
+            // re register interest if we are not already interested
+            // in the event. If the event is paused, then the pause will
+            // be taken into account later when resetInterestOps is called.
+            boolean reRegister = (interestOps & newOps) != newOps;
+            interestOps |= newOps;
+            pending.add(e);
+            if (debug.on())
+                debug.log("Registering %s for %d (%s)", e, newOps, reRegister);
+            if (reRegister) {
+                // first time registration happens here also
+                try {
+                    chan.register(selector, interestOps, this);
+                } catch (Throwable x) {
+                    abortPending(x);
+                }
+            } else if (!chan.isOpen()) {
+                abortPending(new ClosedChannelException());
+            }
+        }
+
+        /**
+         * 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 && key != null && pending.isEmpty()) {
+                key.cancel();
+            } else {
+                try {
+                    if (key == null || !key.isValid()) {
+                        throw new CancelledKeyException();
+                    }
+                    key.interestOps(newOps);
+                    // double check after
+                    if (!chan.isOpen()) {
+                        abortPending(new ClosedChannelException());
+                        return;
+                    }
+                    assert key.interestOps() == newOps;
+                } catch (CancelledKeyException x) {
+                    // channel may have been closed
+                    if (debug.on()) debug.log("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 RawChannelTube.
+        // See RawChannelTube.
+        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 LinkedList<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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,478 @@
+/*
+ * 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.Logger;
+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 {
+
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
+    final static Logger DEBUG_LOGGER = Utils.getDebugLogger(
+            () -> "HttpConnection(SocketTube(?))", Utils.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.
+     * Returns true for tunnel connections, or clear connection to
+     * any host through proxy.
+     */
+    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) {
+        // The default proxy selector may select a proxy whose  address is
+        // unresolved. We must resolve the address before connecting to it.
+        InetSocketAddress proxy = Utils.resolveAddress(request.proxy());
+        HttpConnection c = null;
+        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;
+                if (DEBUG_LOGGER.on())
+                    DEBUG_LOGGER.log(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;
+                if (DEBUG_LOGGER.on())
+                    DEBUG_LOGGER.log(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 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<>();
+        final ConcurrentLinkedDeque<List<ByteBuffer>> priority = 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);
+                if (debug.on())
+                    debug.log("HttpPublisher: got request of "  + n + " from "
+                               + getConnectionFlow());
+                writeScheduler.runOrSchedule();
+            }
+
+            @Override
+            public void cancel() {
+                if (debug.on())
+                    debug.log("HttpPublisher: cancelled by " + getConnectionFlow());
+            }
+
+            private boolean isEmpty() {
+                return queue.isEmpty() && priority.isEmpty();
+            }
+
+            private List<ByteBuffer> poll() {
+                List<ByteBuffer> elem = priority.poll();
+                return elem == null ? queue.poll() : elem;
+            }
+
+            void flush() {
+                while (!isEmpty() && demand.tryDecrement()) {
+                    List<ByteBuffer> elem = poll();
+                    if (debug.on())
+                        debug.log("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("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();
+            priority.add(buffers);
+            debug.log("added %d bytes in the priority write queue", bytes);
+        }
+
+        @Override
+        public void signalEnqueued() throws IOException {
+            debug.log("signalling the publisher of the write queue");
+            signal();
+        }
+    }
+
+    String dbgTag;
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,233 @@
+/*
+ * 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.Locale;
+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;
+import static jdk.internal.net.http.common.Utils.newIAE;
+
+public class HttpRequestBuilderImpl implements 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;
+    }
+
+    static void checkURI(URI uri) {
+        String scheme = uri.getScheme();
+        if (scheme == null)
+            throw newIAE("URI with undefined scheme");
+        scheme = scheme.toLowerCase(Locale.US);
+        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();
+        b.uri = 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() {
+        return method0("DELETE", null);
+    }
+
+    @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");
+        if (!Utils.isValidName(method))
+            throw newIAE("illegal method \""
+                    + method.replace("\n","\\n")
+                    .replace("\r", "\\r")
+                    .replace("\t", "\\t")
+                    + "\"");
+        return method0(method, requireNonNull(body));
+    }
+
+    private HttpRequest.Builder method0(String method, BodyPublisher body) {
+        assert method != null;
+        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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,371 @@
+/*
+ * 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.Objects;
+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.common.Utils;
+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) {
+        String method = request.method();
+        if (method != null && !Utils.isValidName(method))
+            throw new IllegalArgumentException("illegal method \""
+                    + method.replace("\n","\\n")
+                    .replace("\r", "\\r")
+                    .replace("\t", "\\t")
+                    + "\"");
+        URI requestURI = Objects.requireNonNull(request.uri(),
+                "uri must be non null");
+        Duration timeout = request.timeout().orElse(null);
+        this.method = method == null ? "GET" : method;
+        this.userHeaders = ImmutableHeaders.validate(request.headers());
+        if (request instanceof HttpRequestImpl) {
+            // all cases exception WebSocket should have a new system headers
+            this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;
+            if (isWebSocket) {
+                this.systemHeaders = ((HttpRequestImpl) request).systemHeaders;
+            } else {
+                this.systemHeaders = new HttpHeadersImpl();
+            }
+        } else {
+            HttpRequestBuilderImpl.checkURI(requestURI);
+            checkTimeout(timeout);
+            this.systemHeaders = new HttpHeadersImpl();
+        }
+        this.systemHeaders.setHeader("User-Agent", USER_AGENT);
+        this.uri = requestURI;
+        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);
+        this.timeout = timeout;
+        this.version = request.version();
+        this.authority = null;
+    }
+
+    private static void checkTimeout(Duration duration) {
+        if (duration != null) {
+            if (duration.isNegative() || Duration.ZERO.equals(duration))
+                throw new IllegalArgumentException("Invalid duration: " + duration);
+        }
+    }
+
+    /** Returns a new instance suitable for redirection. */
+    public static HttpRequestImpl newInstanceForRedirection(URI uri,
+                                                            String method,
+                                                            HttpRequestImpl other) {
+        return new HttpRequestImpl(uri, method, other);
+    }
+
+    /** Returns a new instance suitable for authentication. */
+    public static HttpRequestImpl newInstanceForAuthentication(HttpRequestImpl other) {
+        return new HttpRequestImpl(other.uri(), other.method(), other);
+    }
+
+    /**
+     * Creates a HttpRequestImpl using fields of an existing request impl.
+     * The newly created HttpRequestImpl does not copy the system headers.
+     */
+    private HttpRequestImpl(URI uri,
+                            String method,
+                            HttpRequestImpl other) {
+        assert method == null || Utils.isValidName(method);
+        this.method = method == null? "GET" : method;
+        this.userHeaders = other.userHeaders;
+        this.isWebSocket = other.isWebSocket;
+        this.systemHeaders = new HttpHeadersImpl();
+        this.systemHeaders.setHeader("User-Agent", USER_AGENT);
+        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	Tue Apr 17 08:54:17 2018 -0700
@@ -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.SSLSession;
+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> implements HttpResponse<T>, RawChannel.Provider {
+
+    final int responseCode;
+    final Exchange<T> exchange;
+    final HttpRequest initialRequest;
+    final Optional<HttpResponse<T>> previousResponse;
+    final HttpHeaders headers;
+    final Optional<SSLSession> sslSession;
+    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.sslSession = Optional.ofNullable(response.getSSLSession());
+        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 Optional<SSLSession> sslSession() {
+        return sslSession;
+    }
+
+    @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 RawChannelTube(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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,116 @@
+/*
+ * 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.Objects;
+import java.util.TreeMap;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import java.net.http.HttpHeaders;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.Utils;
+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());
+    }
+
+    static ImmutableHeaders validate(HttpHeaders headers) {
+        if (headers instanceof ImmutableHeaders) {
+            return of(headers);
+        }
+        if (headers instanceof HttpHeadersImpl) {
+            return of(headers);
+        }
+        Map<String, List<String>> map = headers.map();
+        return new ImmutableHeaders(map, Utils.VALIDATE_USER_HEADER);
+    }
+
+    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()
+                .forEach(e -> addIfAllowed(e, headerAllowed, m));
+        this.map = unmodifiableMap(m);
+    }
+
+    private static void addIfAllowed(Map.Entry<String, List<String>> e,
+                                     BiPredicate<? super String, ? super List<String>> headerAllowed,
+                                     Map<String, List<String>> map) {
+        String key = e.getKey();
+        List<String> values = unmodifiableValues(e.getValue());
+        if (headerAllowed.test(key, values)) {
+            map.put(key, values);
+        }
+    }
+
+    private static List<String> unmodifiableValues(List<String> values) {
+        return unmodifiableList(new ArrayList<>(Objects.requireNonNull(values)));
+    }
+
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,466 @@
+/*
+ * 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<? super S, ? extends R> finisher;
+    private final Charset charset;
+    private final String eol;
+    private volatile LineSubscription downstream;
+
+    private LineSubscriberAdapter(S subscriber,
+                                  Function<? super S, ? extends 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<? super S, ? extends 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 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,373 @@
+/*
+ * 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.Iterator;
+import java.util.LinkedList;
+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.common.Log;
+import jdk.internal.net.http.common.Logger;
+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 Logger debug =
+            Utils.getDebugLogger("MultiExchange"::toString, Utils.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 retries & redirect
+    HttpRequestImpl previousreq; // used for retries & redirect
+    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 LinkedList<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.previousreq = null;
+        this.client = client;
+        this.filters = client.filterChain();
+        this.acc = acc;
+        this.executor = client.theExecutor();
+        this.responseHandler = responseHandler;
+
+        if (pushPromiseHandler != null) {
+            Executor executor = acc == null
+                    ? this.executor
+                    : new PrivilegedExecutor(this.executor, acc);
+            this.pushGroup = new PushGroup<>(pushPromiseHandler, request, executor);
+        } 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");
+        Iterator<HeaderFilter> reverseItr = filters.descendingIterator();
+        while (reverseItr.hasNext()) {
+            HeaderFilter filter = reverseItr.next();
+            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(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<>(r.request(), 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
+                // if currentreq == previousreq the filters have already
+                // been applied once. Applying them a second time might
+                // cause some headers values to be added twice: for
+                // instance, the same cookie might be added again.
+                if (currentreq != previousreq) {
+                    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) -> {
+                                previousreq = currentreq;
+                                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;
+    }
+
+    private static boolean retryPostValue() {
+        String s = Utils.getNetProperty("jdk.httpclient.enableAllMethodRetry");
+        if (s == "" || "true".equals(s))
+            return true;
+        return false;
+    }
+
+    /** True if ALL ( even non-idempotent ) requests can be automatic retried. */
+    private static final boolean RETRY_ALWAYS = retryPostValue();
+
+    /** Returns true is given request has an idempotent method. */
+    private static boolean isIdempotentRequest(HttpRequest request) {
+        String method = request.method();
+        switch (method) {
+            case "GET" :
+            case "HEAD" :
+                return true;
+            default :
+                return false;
+        }
+    }
+
+    /** Returns true if the given request can be automatically retried. */
+    private static boolean canRetryRequest(HttpRequest request) {
+        if (isIdempotentRequest(request))
+            return true;
+        if (RETRY_ALWAYS)
+            return true;
+        return false;
+    }
+
+    /**
+     * 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) {
+            Throwable cause = t;
+            if (t.getCause() != null) {
+                cause = t.getCause(); // unwrap the ConnectionExpiredException
+            }
+
+            if (!canRetryRequest(currentreq)) {
+                return failedFuture(cause); // fails with original cause
+            }
+
+            // allow the retry mechanism to do its work
+            retryCause = cause;
+            if (!expiredOnce) {
+                if (debug.on())
+                    debug.log("ConnectionExpiredException (async): retrying...", t);
+                expiredOnce = true;
+                // The connection was abruptly closed.
+                // We return null to retry the same request a second time.
+                // The request filters have already been applied to the
+                // currentreq, so we set previousreq = currentreq to
+                // prevent them from being applied again.
+                previousreq = currentreq;
+                return null;
+            } else {
+                if (debug.on())
+                    debug.log("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() {
+            if (debug.on())
+                debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,228 @@
+/*
+ * 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 SocketTube tube; // need SocketTube to call signalClosed().
+    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";
+                if (debug.on())
+                    debug.log("ConnectEvent: finishing connect");
+                boolean finished = chan.finishConnect();
+                assert finished : "Expected channel to be connected";
+                if (debug.on())
+                    debug.log("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(Utils.resolveAddress(address));
+            try {
+                 finished = AccessController.doPrivileged(pa);
+            } catch (PrivilegedActionException e) {
+                cf.completeExceptionally(e.getCause());
+            }
+            if (finished) {
+                if (debug.on()) debug.log("connect finished without blocking");
+                connected = true;
+                cf.complete(null);
+            } else {
+                if (debug.on()) debug.log("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);
+            if (debug.on())
+                debug.log("Receive buffer size is %s",
+                          chan.getOption(StandardSocketOptions.SO_RCVBUF));
+            return true;
+        } catch(IOException x) {
+            if (debug.on())
+                debug.log("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 void close() {
+        synchronized (this) {
+            if (closed) {
+                return;
+            }
+            closed = true;
+        }
+        try {
+            Log.logTrace("Closing: " + toString());
+            if (debug.on())
+                debug.log("Closing channel: " + client().debugInterestOps(chan));
+            chan.close();
+            tube.signalClosed();
+        } catch (IOException e) {
+            Log.logTrace("Closing resulted in " + e);
+        }
+    }
+
+
+    @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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PlainProxyConnection.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,43 @@
+/*
+ * 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);
+    }
+
+    @Override
+    public boolean isProxied() { return true; }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PlainTunnelingConnection.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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;
+
+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.BodyHandlers.discarding;
+
+/**
+ * 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() {
+        if (debug.on()) debug.log("Connecting plain connection");
+        return delegate.connectAsync()
+            .thenCompose((Void v) -> {
+                if (debug.on()) debug.log("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, discarding(), null, null);
+                Exchange<Void> connectExchange = new Exchange<>(req, mulEx);
+
+                return connectExchange
+                        .responseAsyncImpl(delegate)
+                        .thenCompose((Response resp) -> {
+                            CompletableFuture<Void> cf = new MinimalFuture<>();
+                            if (debug.on()) debug.log("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
+    boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    boolean isProxied() {
+        return true;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PrivilegedExecutor.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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 ProxyAuthenticationRequired} 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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,171 @@
+/*
+ * 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.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.util.concurrent.Executor;
+
+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 Executor executor;
+
+    int numberOfPushes;
+    int remainingPushes;
+    boolean noMorePushes = false;
+
+    PushGroup(PushPromiseHandler<T> pushPromiseHandler,
+              HttpRequestImpl initiatingRequest,
+              Executor executor) {
+        this(pushPromiseHandler, initiatingRequest, new MinimalFuture<>(), executor);
+    }
+
+    // Check mainBodyHandler before calling nested constructor.
+    private PushGroup(HttpResponse.PushPromiseHandler<T> pushPromiseHandler,
+                      HttpRequestImpl initiatingRequest,
+                      CompletableFuture<HttpResponse<T>> mainResponse,
+                      Executor executor) {
+        this.noMorePushesCF = new MinimalFuture<>();
+        this.pushPromiseHandler = pushPromiseHandler;
+        this.initiatingRequest = initiatingRequest;
+        this.executor = executor;
+    }
+
+    interface Acceptor<T> {
+        BodyHandler<T> bodyHandler();
+        CompletableFuture<HttpResponse<T>> cf();
+        boolean accepted();
+    }
+
+    private static class AcceptorImpl<T> implements Acceptor<T> {
+        private final Executor executor;
+        private volatile HttpResponse.BodyHandler<T> bodyHandler;
+        private volatile CompletableFuture<HttpResponse<T>> cf;
+
+        AcceptorImpl(Executor executor) {
+            this.executor = executor;
+        }
+
+        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.whenCompleteAsync((r,t) -> {}, executor);
+        }
+
+        @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<>(executor);
+        try {
+            pushPromiseHandler.applyPushPromise(initiatingRequest, pushRequest, acceptor::accept);
+        } catch (Throwable t) {
+            if (acceptor.accepted()) {
+                CompletableFuture<?> cf = acceptor.cf();
+                cf.completeExceptionally(t);
+            }
+            throw t;
+        }
+
+        synchronized (this) {
+            if (acceptor.accepted()) {
+                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/RawChannelTube.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,436 @@
+/*
+ * 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 jdk.internal.net.http.common.Demand;
+import jdk.internal.net.http.common.FlowTube;
+import jdk.internal.net.http.common.Logger;
+import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.websocket.RawChannel;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.lang.ref.Cleaner;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectionKey;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.lang.System.Logger.Level;
+
+/*
+ * I/O abstraction used to implement WebSocket.
+ *
+ */
+public class RawChannelTube implements RawChannel {
+
+    final HttpConnection connection;
+    final FlowTube tube;
+    final WritePublisher writePublisher;
+    final ReadSubscriber readSubscriber;
+    final Supplier<ByteBuffer> initial;
+    final AtomicBoolean inited = new AtomicBoolean();
+    final AtomicBoolean outputClosed = new AtomicBoolean();
+    final AtomicBoolean inputClosed = new AtomicBoolean();
+    final AtomicBoolean closed = new AtomicBoolean();
+    final String dbgTag;
+    final Logger debug;
+    private static final Cleaner cleaner =
+            Utils.ASSERTIONSENABLED  && Utils.DEBUG_WS ? Cleaner.create() : null;
+
+    RawChannelTube(HttpConnection connection,
+                   Supplier<ByteBuffer> initial) {
+        this.connection = connection;
+        this.tube = connection.getConnectionFlow();
+        this.initial = initial;
+        this.writePublisher = new WritePublisher();
+        this.readSubscriber = new ReadSubscriber();
+        dbgTag = "[WebSocket] RawChannelTube(" + tube.toString() +")";
+        debug = Utils.getWebSocketLogger(dbgTag::toString, Utils.DEBUG_WS);
+        connection.client().webSocketOpen();
+        connectFlows();
+        if (Utils.ASSERTIONSENABLED && Utils.DEBUG_WS) {
+            // this is just for debug...
+            cleaner.register(this, new CleanupChecker(closed, debug));
+        }
+    }
+
+    // Make sure no back reference to RawChannelTube can exist
+    // from this class. In particular it would be dangerous
+    // to reference connection, since connection has a reference
+    // to SocketTube with which a RawChannelTube is registered.
+    // Ditto for HttpClientImpl, which might have a back reference
+    // to the connection.
+    static final class CleanupChecker implements Runnable {
+        final AtomicBoolean closed;
+        final System.Logger debug;
+        CleanupChecker(AtomicBoolean closed, System.Logger debug) {
+            this.closed = closed;
+            this.debug = debug;
+        }
+
+        @Override
+        public void run() {
+            if (!closed.get()) {
+                debug.log(Level.DEBUG,
+                         "RawChannelTube was not closed before being released");
+            }
+        }
+    }
+
+    private void connectFlows() {
+        if (debug.on()) debug.log("connectFlows");
+        tube.connectFlows(writePublisher, readSubscriber);
+    }
+
+    class WriteSubscription implements Flow.Subscription {
+        final Flow.Subscriber<? super List<ByteBuffer>> subscriber;
+        final Demand demand = new Demand();
+        volatile boolean cancelled;
+        WriteSubscription(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            this.subscriber = subscriber;
+        }
+        @Override
+        public void request(long n) {
+            if (debug.on()) debug.log("WriteSubscription::request %d", n);
+            demand.increase(n);
+            RawEvent event;
+            while ((event = writePublisher.events.poll()) != null) {
+                if (debug.on()) debug.log("WriteSubscriber: handling event");
+                event.handle();
+                if (demand.isFulfilled()) break;
+            }
+        }
+        @Override
+        public void cancel() {
+            cancelled = true;
+            if (debug.on()) debug.log("WriteSubscription::cancel");
+            shutdownOutput();
+            RawEvent event;
+            while ((event = writePublisher.events.poll()) != null) {
+                if (debug.on()) debug.log("WriteSubscriber: handling event");
+                event.handle();
+            }
+        }
+    }
+
+    class WritePublisher implements FlowTube.TubePublisher {
+        final ConcurrentLinkedQueue<RawEvent> events = new ConcurrentLinkedQueue<>();
+        volatile WriteSubscription writeSubscription;
+        @Override
+        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            if (debug.on()) debug.log("WritePublisher::subscribe");
+            WriteSubscription subscription = new WriteSubscription(subscriber);
+            subscriber.onSubscribe(subscription);
+            writeSubscription = subscription;
+        }
+    }
+
+    class ReadSubscriber implements  FlowTube.TubeSubscriber {
+
+        volatile Flow.Subscription readSubscription;
+        volatile boolean completed;
+        long initialRequest;
+        final ConcurrentLinkedQueue<RawEvent> events = new ConcurrentLinkedQueue<>();
+        final ConcurrentLinkedQueue<ByteBuffer> buffers = new ConcurrentLinkedQueue<>();
+        final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+
+        void checkEvents() {
+            Flow.Subscription subscription = readSubscription;
+            if (subscription != null) {
+                Throwable error = errorRef.get();
+                while (!buffers.isEmpty() || error != null || closed.get() || completed) {
+                    RawEvent event = events.poll();
+                    if (event == null) break;
+                    if (debug.on()) debug.log("ReadSubscriber: handling event");
+                    event.handle();
+                }
+            }
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            //buffers.add(initial.get());
+            long n;
+            synchronized (this) {
+                readSubscription = subscription;
+                n = initialRequest;
+                initialRequest = 0;
+            }
+            if (debug.on()) debug.log("ReadSubscriber::onSubscribe");
+            if (n > 0) {
+                Throwable error = errorRef.get();
+                if (error == null && !closed.get() && !completed) {
+                    if (debug.on()) debug.log("readSubscription: requesting " + n);
+                    subscription.request(n);
+                }
+            }
+            checkEvents();
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            if (debug.on()) debug.log(() -> "ReadSubscriber::onNext "
+                    + Utils.remaining(item) + " bytes");
+            buffers.addAll(item);
+            checkEvents();
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            if (closed.get() || errorRef.compareAndSet(null, throwable)) {
+                if (debug.on()) debug.log("ReadSubscriber::onError", throwable);
+                if (buffers.isEmpty()) {
+                    checkEvents();
+                    shutdownInput();
+                }
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            if (debug.on()) debug.log("ReadSubscriber::onComplete");
+            completed = true;
+            if (buffers.isEmpty()) {
+                checkEvents();
+                shutdownInput();
+            }
+        }
+    }
+
+
+    /*
+     * 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
+     */
+    public void registerEvent(RawEvent event) throws IOException {
+        int interestOps = event.interestOps();
+        if ((interestOps & SelectionKey.OP_WRITE) != 0) {
+            if (debug.on()) debug.log("register write event");
+            if (outputClosed.get()) throw new IOException("closed output");
+            writePublisher.events.add(event);
+            WriteSubscription writeSubscription = writePublisher.writeSubscription;
+            if (writeSubscription != null) {
+                while (!writeSubscription.demand.isFulfilled()) {
+                    event = writePublisher.events.poll();
+                    if (event == null) break;
+                    event.handle();
+                }
+            }
+        }
+        if ((interestOps & SelectionKey.OP_READ) != 0) {
+            if (debug.on()) debug.log("register read event");
+            if (inputClosed.get()) throw new IOException("closed input");
+            readSubscriber.events.add(event);
+            readSubscriber.checkEvents();
+            if (readSubscriber.buffers.isEmpty()
+                    && !readSubscriber.events.isEmpty()) {
+                Flow.Subscription readSubscription =
+                        readSubscriber.readSubscription;
+                if (readSubscription == null) {
+                    synchronized (readSubscriber) {
+                        readSubscription = readSubscriber.readSubscription;
+                        if (readSubscription == null) {
+                            readSubscriber.initialRequest = 1;
+                            return;
+                        }
+                    }
+                }
+                assert  readSubscription != null;
+                if (debug.on()) debug.log("readSubscription: requesting 1");
+                readSubscription.request(1);
+            }
+        }
+    }
+
+    /**
+     * 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
+     */
+    public ByteBuffer initialByteBuffer() throws IllegalStateException {
+        if (inited.compareAndSet(false, true)) {
+            return initial.get();
+        } else throw new IllegalStateException("initial buffer already drained");
+    }
+
+    /*
+     * Returns a ByteBuffer with the data read or null if EOF is reached. Has no
+     * remaining bytes if no data available at the moment.
+     */
+    public ByteBuffer read() throws IOException {
+        if (debug.on()) debug.log("read");
+        Flow.Subscription readSubscription = readSubscriber.readSubscription;
+        if (readSubscription == null) return Utils.EMPTY_BYTEBUFFER;
+        ByteBuffer buffer = readSubscriber.buffers.poll();
+        if (buffer != null) {
+            if (debug.on()) debug.log("read: " + buffer.remaining());
+            return buffer;
+        }
+        Throwable error = readSubscriber.errorRef.get();
+        if (error != null) error = Utils.getIOException(error);
+        if (error instanceof EOFException) {
+            if (debug.on()) debug.log("read: EOFException");
+            shutdownInput();
+            return null;
+        }
+        if (error != null) {
+            if (debug.on()) debug.log("read: " + error);
+            if (closed.get()) {
+                return null;
+            }
+            shutdownInput();
+            throw Utils.getIOException(error);
+        }
+        if (readSubscriber.completed) {
+            if (debug.on()) debug.log("read: EOF");
+            shutdownInput();
+            return null;
+        }
+        if (inputClosed.get()) {
+            if (debug.on()) debug.log("read: CLOSED");
+            throw new IOException("closed output");
+        }
+        if (debug.on()) debug.log("read: nothing to read");
+        return Utils.EMPTY_BYTEBUFFER;
+    }
+
+    /*
+     * Writes a sequence of bytes to this channel from a subsequence of the
+     * given buffers.
+     */
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+        if (outputClosed.get()) {
+            if (debug.on()) debug.log("write: CLOSED");
+            throw new IOException("closed output");
+        }
+        WriteSubscription writeSubscription =  writePublisher.writeSubscription;
+        if (writeSubscription == null) {
+            if (debug.on()) debug.log("write: unsubscribed: 0");
+            return 0;
+        }
+        if (writeSubscription.cancelled) {
+            if (debug.on()) debug.log("write: CANCELLED");
+            shutdownOutput();
+            throw new IOException("closed output");
+        }
+        if (writeSubscription.demand.tryDecrement()) {
+            List<ByteBuffer> buffers = copy(srcs, offset, length);
+            long res = Utils.remaining(buffers);
+            if (debug.on()) debug.log("write: writing %d", res);
+            writeSubscription.subscriber.onNext(buffers);
+            return res;
+        } else {
+            if (debug.on()) debug.log("write: no demand: 0");
+            return 0;
+        }
+    }
+
+    /**
+     * 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
+     */
+    public void shutdownInput() {
+        if (inputClosed.compareAndSet(false, true)) {
+            if (debug.on()) debug.log("shutdownInput");
+            // TransportImpl will eventually call RawChannel::close.
+            // We must not call it here as this would close the socket
+            // and can cause an exception to back fire before
+            // TransportImpl and WebSocketImpl have updated their state.
+        }
+    }
+
+    /**
+     * 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
+     */
+    public void shutdownOutput() {
+        if (outputClosed.compareAndSet(false, true)) {
+            if (debug.on()) debug.log("shutdownOutput");
+            // TransportImpl will eventually call RawChannel::close.
+            // We must not call it here as this would close the socket
+            // and can cause an exception to back fire before
+            // TransportImpl and WebSocketImpl have updated their state.
+        }
+    }
+
+    /**
+     * Closes this channel.
+     *
+     * @throws IOException
+     *         If an I/O error occurs
+     */
+    @Override
+    public void close() {
+        if (closed.compareAndSet(false, true)) {
+            if (debug.on()) debug.log("close");
+            connection.client().webSocketClose();
+            connection.close();
+        }
+    }
+
+    private static List<ByteBuffer> copy(ByteBuffer[] src, int offset, int len) {
+        int count = Math.min(len, src.length - offset);
+        if (count <= 0) return Utils.EMPTY_BB_LIST;
+        if (count == 1) return List.of(Utils.copy(src[offset]));
+        if (count == 2) return List.of(Utils.copy(src[offset]), Utils.copy(src[offset+1]));
+        List<ByteBuffer> list = new ArrayList<>(count);
+        for (int i = 0; i < count; i++) {
+            list.add(Utils.copy(src[offset + i]));
+        }
+        return list;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,137 @@
+/*
+ * 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.Log;
+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);
+    }
+
+    private static String redirectedMethod(int statusCode, String orig) {
+        switch (statusCode) {
+            case 301:
+            case 302:
+                return orig.equals("POST") ? "GET" : orig;
+            case 303:
+                return "GET";
+            case 307:
+            case 308:
+                return orig;
+            default:
+                // unexpected but return orig
+                return orig;
+        }
+    }
+
+    /**
+     * Checks to see if a new request is 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());
+            String newMethod = redirectedMethod(rcode, method);
+            Log.logTrace("response code: {0}, redirected URI: {1}", rcode, redir);
+            if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) {
+                Log.logTrace("redirect to: {0} with method: {1}", redir, newMethod);
+                return HttpRequestImpl.newInstanceForRedirection(redir, newMethod, request);
+            } else {
+                Log.logTrace("not redirecting");
+                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 NORMAL:
+                return newScheme.equalsIgnoreCase(oldScheme)
+                        || newScheme.equalsIgnoreCase("https");
+            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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,424 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.FilePermission;
+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.Files;
+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);
+        }
+    }
+
+    /**
+     * Publishes the content of a given file.
+     *
+     * Privileged actions are performed within a limited doPrivileged that only
+     * asserts the specific, read, file permission that was checked during the
+     * construction of this FilePublisher.
+     */
+    public static class FilePublisher implements BodyPublisher  {
+
+        private static final FilePermission[] EMPTY_FILE_PERMISSIONS = new FilePermission[0];
+
+        private final File file;
+        private final FilePermission[] filePermissions;
+
+        private static String pathForSecurityCheck(Path path) {
+            return path.toFile().getPath();
+        }
+
+        /**
+         * Factory for creating FilePublisher.
+         *
+         * Permission checks are performed here before construction of the
+         * FilePublisher. Permission checking and construction are deliberately
+         * and tightly co-located.
+         */
+        public static FilePublisher create(Path path) throws FileNotFoundException {
+            FilePermission filePermission = null;
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                String fn = pathForSecurityCheck(path);
+                FilePermission readPermission = new FilePermission(fn, "read");
+                sm.checkPermission(readPermission);
+                filePermission = readPermission;
+            }
+
+            // existence check must be after permission checks
+            if (Files.notExists(path))
+                throw new FileNotFoundException(path + " not found");
+
+            return new FilePublisher(path, filePermission);
+        }
+
+        private FilePublisher(Path name, FilePermission filePermission) {
+            assert filePermission != null ? filePermission.getActions().equals("read") : true;
+            file = name.toFile();
+            this.filePermissions = filePermission == null ? EMPTY_FILE_PERMISSIONS
+                    : new FilePermission[] { filePermission };
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            InputStream is;
+            if (System.getSecurityManager() == null) {
+                try {
+                    is = new FileInputStream(file);
+                } catch (IOException ioe) {
+                    throw new UncheckedIOException(ioe);
+                }
+            } else {
+                try {
+                    PrivilegedExceptionAction<FileInputStream> pa =
+                            () -> new FileInputStream(file);
+                    is = AccessController.doPrivileged(pa, null, filePermissions);
+                } catch (PrivilegedActionException pae) {
+                    throw new UncheckedIOException((IOException) pae.getCause());
+                }
+            }
+            PullPublisher<ByteBuffer> publisher =
+                    new PullPublisher<>(() -> new StreamIterator(is));
+            publisher.subscribe(subscriber);
+        }
+
+        @Override
+        public long contentLength() {
+            if (System.getSecurityManager() == null) {
+                return file.length();
+            } else {
+                PrivilegedAction<Long> pa = () -> file.length();
+                return AccessController.doPrivileged(pa, null, filePermissions);
+            }
+        }
+    }
+
+    /**
+     * 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,131 @@
+/*
+ * 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.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.InetSocketAddress;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+import jdk.internal.net.http.common.Utils;
+
+/**
+ * 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;
+    final SSLSession sslSession;
+    final InetSocketAddress localAddress;
+
+    Response(HttpRequestImpl req,
+             Exchange<?> exchange,
+             HttpHeaders headers,
+             HttpConnection connection,
+             int statusCode,
+             HttpClient.Version version) {
+        this(req, exchange, headers, connection, statusCode, version,
+                "CONNECT".equalsIgnoreCase(req.method()));
+    }
+
+    Response(HttpRequestImpl req,
+             Exchange<?> exchange,
+             HttpHeaders headers,
+             HttpConnection connection,
+             int statusCode,
+             HttpClient.Version version,
+             boolean isConnectResponse) {
+        this.headers = ImmutableHeaders.of(headers);
+        this.request = req;
+        this.version = version;
+        this.exchange = exchange;
+        this.statusCode = statusCode;
+        this.isConnectResponse = isConnectResponse;
+        if (connection != null) {
+            InetSocketAddress a;
+            try {
+                a = (InetSocketAddress)connection.channel().getLocalAddress();
+            } catch (IOException e) {
+                a = null;
+            }
+            this.localAddress = a;
+            if (connection instanceof AbstractAsyncSSLConnection) {
+                AbstractAsyncSSLConnection cc = (AbstractAsyncSSLConnection)connection;
+                SSLEngine engine = cc.getEngine();
+                sslSession = Utils.immutableSession(engine.getSession());
+            } else {
+                sslSession = null;
+            }
+        } else {
+            sslSession = null;
+            localAddress = null;
+        }
+    }
+
+    HttpRequestImpl request() {
+        return request;
+    }
+
+    HttpClient.Version version() {
+        return version;
+    }
+
+    HttpHeaders headers() {
+        return headers;
+    }
+
+    int statusCode() {
+        return statusCode;
+    }
+
+    SSLSession getSSLSession() {
+        return sslSession;
+    }
+
+    @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());
+        sb.append(" ").append(version);
+        if (localAddress != null)
+            sb.append(" Local port:  ").append(localAddress.getPort());
+        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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,279 @@
+/*
+ * 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.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+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.ResponseInfo;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import jdk.internal.net.http.ResponseSubscribers.PathSubscriber;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
+public final class ResponseBodyHandlers {
+
+    private ResponseBodyHandlers() { }
+
+    private static final String pathForSecurityCheck(Path path) {
+        return path.toFile().getPath();
+    }
+
+    /**
+     * A Path body handler.
+     */
+    public static class PathBodyHandler implements BodyHandler<Path>{
+        private final Path file;
+        private final List<OpenOption> openOptions;  // immutable list
+        private final FilePermission filePermission;
+
+        /**
+         * Factory for creating PathBodyHandler.
+         *
+         * Permission checks are performed here before construction of the
+         * PathBodyHandler. Permission checking and construction are
+         * deliberately and tightly co-located.
+         */
+        public static PathBodyHandler create(Path file,
+                                             List<OpenOption> openOptions) {
+            FilePermission filePermission = null;
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                String fn = pathForSecurityCheck(file);
+                FilePermission writePermission = new FilePermission(fn, "write");
+                sm.checkPermission(writePermission);
+                filePermission = writePermission;
+            }
+            return new PathBodyHandler(file, openOptions, filePermission);
+        }
+
+        private PathBodyHandler(Path file,
+                                List<OpenOption> openOptions,
+                                FilePermission filePermission) {
+            this.file = file;
+            this.openOptions = openOptions;
+            this.filePermission = filePermission;
+        }
+
+        @Override
+        public BodySubscriber<Path> apply(ResponseInfo responseInfo) {
+            return new PathSubscriber(file, openOptions, filePermission);
+        }
+    }
+
+    /** 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.
+    public static class FileDownloadBodyHandler implements BodyHandler<Path> {
+        private final Path directory;
+        private final List<OpenOption> openOptions;
+        private final FilePermission[] filePermissions;  // may be null
+
+        /**
+         * Factory for creating FileDownloadBodyHandler.
+         *
+         * Permission checks are performed here before construction of the
+         * FileDownloadBodyHandler. Permission checking and construction are
+         * deliberately and tightly co-located.
+         */
+        public static FileDownloadBodyHandler create(Path directory,
+                                                     List<OpenOption> openOptions) {
+            FilePermission filePermissions[] = null;
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                String fn = pathForSecurityCheck(directory);
+                FilePermission writePermission = new FilePermission(fn, "write");
+                String writePathPerm = fn + File.separatorChar + "*";
+                FilePermission writeInDirPermission = new FilePermission(writePathPerm, "write");
+                sm.checkPermission(writeInDirPermission);
+                FilePermission readPermission = new FilePermission(fn, "read");
+                sm.checkPermission(readPermission);
+
+                // read permission is only needed before determine the below checks
+                // only write permission is required when downloading to the file
+                filePermissions = new FilePermission[] { writePermission, writeInDirPermission };
+            }
+
+            // existence, etc, checks must be after permission checks
+            if (Files.notExists(directory))
+                throw new IllegalArgumentException("non-existent directory: " + directory);
+            if (!Files.isDirectory(directory))
+                throw new IllegalArgumentException("not a directory: " + directory);
+            if (!Files.isWritable(directory))
+                throw new IllegalArgumentException("non-writable directory: " + directory);
+
+            return new FileDownloadBodyHandler(directory, openOptions, filePermissions);
+
+        }
+
+        private FileDownloadBodyHandler(Path directory,
+                                       List<OpenOption> openOptions,
+                                       FilePermission... filePermissions) {
+            this.directory = directory;
+            this.openOptions = openOptions;
+            this.filePermissions = filePermissions;
+        }
+
+        /** The "attachment" disposition-type and separator. */
+        static final String DISPOSITION_TYPE = "attachment;";
+
+        /** The "filename" parameter. */
+        static final Pattern FILENAME = Pattern.compile("filename\\s*=", CASE_INSENSITIVE);
+
+        static final List<String> PROHIBITED = List.of(".", "..", "", "~" , "|");
+
+        static final UncheckedIOException unchecked(ResponseInfo rinfo,
+                                                    String msg) {
+            String s = String.format("%s in response [%d, %s]", msg, rinfo.statusCode(), rinfo.headers());
+            return new UncheckedIOException(new IOException(s));
+        }
+
+        @Override
+        public BodySubscriber<Path> apply(ResponseInfo responseInfo) {
+            String dispoHeader = responseInfo.headers().firstValue("Content-Disposition")
+                    .orElseThrow(() -> unchecked(responseInfo, "No Content-Disposition header"));
+
+            if (!dispoHeader.regionMatches(true, // ignoreCase
+                                           0, DISPOSITION_TYPE,
+                                           0, DISPOSITION_TYPE.length())) {
+                throw unchecked(responseInfo, "Unknown Content-Disposition type");
+            }
+
+            Matcher matcher = FILENAME.matcher(dispoHeader);
+            if (!matcher.find()) {
+                throw unchecked(responseInfo, "Bad Content-Disposition filename parameter");
+            }
+            int n = matcher.end();
+
+            int semi = dispoHeader.substring(n).indexOf(";");
+            String filenameParam;
+            if (semi < 0) {
+                filenameParam = dispoHeader.substring(n);
+            } else {
+                filenameParam = dispoHeader.substring(n, n + semi);
+            }
+
+            // strip all but the last path segment
+            int x = filenameParam.lastIndexOf("/");
+            if (x != -1) {
+                filenameParam = filenameParam.substring(x+1);
+            }
+            x = filenameParam.lastIndexOf("\\");
+            if (x != -1) {
+                filenameParam = filenameParam.substring(x+1);
+            }
+
+            filenameParam = filenameParam.trim();
+
+            if (filenameParam.startsWith("\"")) {  // quoted-string
+                if (!filenameParam.endsWith("\"") || filenameParam.length() == 1) {
+                    throw unchecked(responseInfo,
+                            "Badly quoted Content-Disposition filename parameter");
+                }
+                filenameParam = filenameParam.substring(1, filenameParam.length() -1 );
+            } else {  // token,
+                if (filenameParam.contains(" ")) {  // space disallowed
+                    throw unchecked(responseInfo,
+                            "unquoted space in Content-Disposition filename parameter");
+                }
+            }
+
+            if (PROHIBITED.contains(filenameParam)) {
+                throw unchecked(responseInfo,
+                        "Prohibited Content-Disposition filename parameter:"
+                                + filenameParam);
+            }
+
+            Path file = Paths.get(directory.toString(), filenameParam);
+
+            if (!file.startsWith(directory)) {
+                throw unchecked(responseInfo,
+                        "Resulting file, " + file.toString() + ", outside of given directory");
+            }
+
+            return new PathSubscriber(file, openOptions, filePermissions);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,476 @@
+/*
+ * 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.Logger;
+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 {
+
+    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 Logger debug = Utils.getDebugLogger(this::dbgString, Utils.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) {
+            if (debug.on())
+                debug.log("onSubscribe: "  + pusher.getClass().getName());
+            pusher.onSubscribe(this.sub = sub);
+        }
+
+        @Override
+        public void accept(ByteBuffer b) {
+            if (closedExceptionally != null) {
+                if (debug.on())
+                    debug.log("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));
+                            if (debug.on()) debug.log("Chunks sent");
+                        }
+                        if (debug.on()) debug.log("done!");
+                        assert closedExceptionally == null;
+                        assert state == ChunkState.DONE;
+                        onFinished.run();
+                        pusher.onComplete();
+                        if (debug.on()) debug.log("subscriber completed");
+                        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));
+                    if (debug.on()) debug.log("Chunk sent");
+                }
+                assert state == ChunkState.DONE || !b.hasRemaining();
+            } catch(Throwable t) {
+                if (debug.on())
+                    debug.log("Error while processing buffer: %s", (Object)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) {
+                if (debug.on()) debug.log(() ->  "Trying to read chunk len"
+                        + " (remaining in buffer:"+chunk.remaining()+")");
+                int clen = chunklen = tryReadChunkLen(chunk);
+                if (clen == -1) return READMORE;
+                if (debug.on()) debug.log("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) {
+                if (debug.on())
+                    debug.log("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;
+                if (debug.on()) debug.log("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();
+                if (debug.on())
+                    debug.log("Reading chunk: available %d, needed %d",
+                              bytesread, unfulfilled);
+
+                int bytes2return = Math.min(bytesread, unfulfilled);
+                if (debug.on())
+                    debug.log( "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) {
+                if (debug.on())
+                    debug.log("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;
+                    if (debug.on()) debug.log("Ready to read next chunk");
+                }
+            }
+            if (returnBuffer == READMORE) {
+                if (debug.on()) debug.log("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()) {
+                    if (debug.on())
+                        debug.log("Sending chunk to consumer (%d)", b1.remaining());
+                    out.add(b1);
+                }
+                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 Logger debug = Utils.getDebugLogger(this::dbgString, Utils.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) {
+            if (debug.on())
+                debug.log("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) {
+                if (debug.on())
+                    debug.log("already closed: " + closedExceptionally);
+                return;
+            }
+            boolean completed = false;
+            try {
+                int unfulfilled = remaining;
+                if (debug.on())
+                    debug.log("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.
+                    if (debug.on())
+                        debug.log("Parser got all expected bytes: completing");
+                    assert closedExceptionally == null;
+                    onFinished.run();
+                    pusher.onComplete();
+                    completed = true;
+                    onComplete.accept(closedExceptionally); // should be null
+                } else {
+                    assert b.remaining() == 0;
+                }
+            } catch (Throwable t) {
+                if (debug.on()) debug.log("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/ResponseInfoImpl.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http;
+
+import java.net.http.HttpResponse.ResponseInfo;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpClient;
+
+class ResponseInfoImpl implements ResponseInfo {
+    private final int statusCode;
+    private final HttpHeaders headers;
+    private final HttpClient.Version version;
+
+    ResponseInfoImpl(Response response) {
+        this.statusCode = response.statusCode();
+        this.headers = response.headers();
+        this.version = response.version();
+    }
+
+    ResponseInfoImpl(int statusCode, HttpHeaders headers, HttpClient.Version version) {
+        this.statusCode = statusCode;
+        this.headers = headers;
+        this.version = version;
+    }
+
+    /**
+     * Provides the response status code
+     * @return the response status code
+     */
+    public int statusCode() {
+        return statusCode;
+    }
+
+    /**
+     * Provides the response headers
+     * @return the response headers
+     */
+    public HttpHeaders headers() {
+        return headers;
+    }
+
+    /**
+     * provides the response protocol version
+     * @return the response protocol version
+     */
+    public HttpClient.Version version() {
+        return version;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,897 @@
+/*
+ * 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.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.System.Logger.Level;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpResponse;
+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.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.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+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.Log;
+import jdk.internal.net.http.common.Logger;
+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);
+        }
+
+    }
+
+    /**
+     * A Subscriber that writes the flow of data to a given file.
+     *
+     * Privileged actions are performed within a limited doPrivileged that only
+     * asserts the specific, write, file permissions that were checked during
+     * the construction of this PathSubscriber.
+     */
+    public static class PathSubscriber implements BodySubscriber<Path> {
+
+        private static final FilePermission[] EMPTY_FILE_PERMISSIONS = new FilePermission[0];
+
+        private final Path file;
+        private final OpenOption[] options;
+        private final FilePermission[] filePermissions;
+        private final CompletableFuture<Path> result = new MinimalFuture<>();
+
+        private volatile Flow.Subscription subscription;
+        private volatile FileChannel out;
+
+        private static final String pathForSecurityCheck(Path path) {
+            return path.toFile().getPath();
+        }
+
+        /**
+         * Factory for creating PathSubscriber.
+         *
+         * Permission checks are performed here before construction of the
+         * PathSubscriber. Permission checking and construction are deliberately
+         * and tightly co-located.
+         */
+        public static PathSubscriber create(Path file,
+                                            List<OpenOption> options) {
+            FilePermission filePermission = null;
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                String fn = pathForSecurityCheck(file);
+                FilePermission writePermission = new FilePermission(fn, "write");
+                sm.checkPermission(writePermission);
+                filePermission = writePermission;
+            }
+            return new PathSubscriber(file, options, filePermission);
+        }
+
+        // pp so handler implementations in the same package can construct
+        /*package-private*/ PathSubscriber(Path file,
+                                           List<OpenOption> options,
+                                           FilePermission... filePermissions) {
+            this.file = file;
+            this.options = options.stream().toArray(OpenOption[]::new);
+            this.filePermissions =
+                    filePermissions == null ? EMPTY_FILE_PERMISSIONS : filePermissions;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            this.subscription = subscription;
+            if (System.getSecurityManager() == null) {
+                try {
+                    out = FileChannel.open(file, options);
+                } catch (IOException ioe) {
+                    result.completeExceptionally(ioe);
+                    return;
+                }
+            } else {
+                try {
+                    PrivilegedExceptionAction<FileChannel> pa =
+                            () -> FileChannel.open(file, options);
+                    out = AccessController.doPrivileged(pa, null, filePermissions);
+                } 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 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 Logger debug =
+                Utils.getDebugLogger("HttpResponseInputStream"::toString, Utils.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...
+
+                        if (debug.on()) debug.log("Taking list of Buffers");
+                        List<ByteBuffer> lb = buffers.take();
+                        currentListItr = lb.iterator();
+                        if (debug.on()) debug.log("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) {
+                            if (debug.on()) debug.log("Increased demand by 1");
+                            s.request(1);
+                        }
+                        assert currentListItr != null;
+                        if (lb.isEmpty()) continue;
+                    }
+                    assert currentListItr != null;
+                    assert currentListItr.hasNext();
+                    if (debug.on()) debug.log("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
+                    if (debug.on())
+                        debug.log("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 {
+                if (debug.on()) debug.log("next item received");
+                if (!buffers.offer(t)) {
+                    throw new IllegalStateException("queue is full");
+                }
+                if (debug.on()) debug.log("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 MappingSubscriber<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<? super S,? extends R> finisher;
+        private volatile Subscription subscription;
+
+        public SubscriberAdapter(S subscriber, Function<? super S,? extends 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.
+     *
+     * @param <T> the upstream body type
+     * @param <U> this subscriber's body type
+     */
+    public static class MappingSubscriber<T,U> implements BodySubscriber<U> {
+        private final BodySubscriber<T> upstream;
+        private final Function<? super T,? extends U> mapper;
+
+        public MappingSubscriber(BodySubscriber<T> upstream,
+                                 Function<? super T,? extends U> mapper) {
+            this.upstream = Objects.requireNonNull(upstream);
+            this.mapper = Objects.requireNonNull(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 BodySubscriber that returns a Publisher<List<ByteBuffer>>
+    static class PublishingBodySubscriber
+            implements BodySubscriber<Flow.Publisher<List<ByteBuffer>>> {
+        private final MinimalFuture<Flow.Subscription>
+                subscriptionCF = new MinimalFuture<>();
+        private final MinimalFuture<SubscriberRef>
+                subscribedCF = new MinimalFuture<>();
+        private AtomicReference<SubscriberRef>
+                subscriberRef = new AtomicReference<>();
+        private final CompletionStage<Flow.Publisher<List<ByteBuffer>>> body =
+                subscriptionCF.thenCompose(
+                        (s) -> MinimalFuture.completedFuture(this::subscribe));
+
+        // We use the completionCF to ensure that only one of
+        // onError or onComplete is ever called.
+        private final MinimalFuture<Void> completionCF;
+        private PublishingBodySubscriber() {
+            completionCF = new MinimalFuture<>();
+            completionCF.whenComplete(
+                    (r,t) -> subscribedCF.thenAccept( s -> complete(s, t)));
+        }
+
+        // An object that holds a reference to a Flow.Subscriber.
+        // The reference is cleared when the subscriber is completed - either
+        // normally or exceptionally, or when the subscription is cancelled.
+        static final class SubscriberRef {
+            volatile Flow.Subscriber<? super List<ByteBuffer>> ref;
+            SubscriberRef(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+                ref = subscriber;
+            }
+            Flow.Subscriber<? super List<ByteBuffer>> get() {
+                return ref;
+            }
+            Flow.Subscriber<? super List<ByteBuffer>> clear() {
+                Flow.Subscriber<? super List<ByteBuffer>> res = ref;
+                ref = null;
+                return res;
+            }
+        }
+
+        // A subscription that wraps an upstream subscription and
+        // holds a reference to a subscriber. The subscriber reference
+        // is cleared when the subscription is cancelled
+        final static class SubscriptionRef implements Flow.Subscription {
+            final Flow.Subscription subscription;
+            final SubscriberRef subscriberRef;
+            SubscriptionRef(Flow.Subscription subscription,
+                            SubscriberRef subscriberRef) {
+                this.subscription = subscription;
+                this.subscriberRef = subscriberRef;
+            }
+            @Override
+            public void request(long n) {
+                if (subscriberRef.get() != null) {
+                    subscription.request(n);
+                }
+            }
+            @Override
+            public void cancel() {
+                subscription.cancel();
+                subscriberRef.clear();
+            }
+
+            void subscribe() {
+                Subscriber<?> subscriber = subscriberRef.get();
+                if (subscriber != null) {
+                    subscriber.onSubscribe(this);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return "SubscriptionRef/"
+                        + subscription.getClass().getName()
+                        + "@"
+                        + System.identityHashCode(subscription);
+            }
+        }
+
+        // This is a callback for the subscribedCF.
+        // Do not call directly!
+        private void complete(SubscriberRef ref, Throwable t) {
+            assert ref != null;
+            Subscriber<?> s = ref.clear();
+            // maybe null if subscription was cancelled
+            if (s == null) return;
+            if (t == null) {
+                try {
+                    s.onComplete();
+                } catch (Throwable x) {
+                    s.onError(x);
+                }
+            } else {
+                s.onError(t);
+            }
+        }
+
+        private void signalError(Throwable err) {
+            if (err == null) {
+                err = new NullPointerException("null throwable");
+            }
+            completionCF.completeExceptionally(err);
+        }
+
+        private void signalComplete() {
+            completionCF.complete(null);
+        }
+
+        private void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            Objects.requireNonNull(subscriber, "subscriber must not be null");
+            SubscriberRef ref = new SubscriberRef(subscriber);
+            if (subscriberRef.compareAndSet(null, ref)) {
+                subscriptionCF.thenAccept((s) -> {
+                    SubscriptionRef subscription = new SubscriptionRef(s,ref);
+                    try {
+                        subscription.subscribe();
+                        subscribedCF.complete(ref);
+                    } catch (Throwable t) {
+                        if (Log.errors()) {
+                            Log.logError("Failed to call onSubscribe: " +
+                                    "cancelling subscription: " + t);
+                            Log.logError(t);
+                        }
+                        subscription.cancel();
+                    }
+                });
+            } else {
+                subscriber.onSubscribe(new Flow.Subscription() {
+                    @Override public void request(long n) { }
+                    @Override public void cancel() { }
+                });
+                subscriber.onError(new IllegalStateException(
+                        "This publisher has already one subscriber"));
+            }
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            subscriptionCF.complete(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            try {
+                // cannot be called before onSubscribe()
+                assert subscriptionCF.isDone();
+                SubscriberRef ref = subscriberRef.get();
+                // cannot be called before subscriber calls request(1)
+                assert ref != null;
+                Flow.Subscriber<? super List<ByteBuffer>>
+                        subscriber = ref.get();
+                if (subscriber != null) {
+                    // may be null if subscription was cancelled.
+                    subscriber.onNext(item);
+                }
+            } catch (Throwable err) {
+                signalError(err);
+                subscriptionCF.thenAccept(s -> s.cancel());
+            }
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            // cannot be called before onSubscribe();
+            assert suppress(subscriptionCF.isDone(),
+                    "onError called before onSubscribe",
+                    throwable);
+            // onError can be called before request(1), and therefore can
+            // be called before subscriberRef is set.
+            signalError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            // cannot be called before onSubscribe()
+            if (!subscriptionCF.isDone()) {
+                signalError(new InternalError(
+                        "onComplete called before onSubscribed"));
+            } else {
+                // onComplete can be called before request(1),
+                // and therefore can be called before subscriberRef
+                // is set.
+                signalComplete();
+            }
+        }
+
+        @Override
+        public CompletionStage<Flow.Publisher<List<ByteBuffer>>> getBody() {
+            return body;
+        }
+
+        private boolean suppress(boolean condition,
+                                 String assertion,
+                                 Throwable carrier) {
+            if (!condition) {
+                if (carrier != null) {
+                    carrier.addSuppressed(new AssertionError(assertion));
+                } else if (Log.errors()) {
+                    Log.logError(new AssertionError(assertion));
+                }
+            }
+            return true;
+        }
+
+    }
+
+    public static BodySubscriber<Flow.Publisher<List<ByteBuffer>>>
+    createPublisher() {
+        return new PublishingBodySubscriber();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,1073 @@
+/*
+ * 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.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.Logger;
+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 is subscribed to the write publisher.
+ */
+final class SocketTube implements FlowTube {
+
+    final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.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();
+    }
+
+    /**
+     * 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                                      //
+    // ======================================================================//
+
+    void signalClosed() {
+        // Ensures that the subscriber will be terminated and that future
+        // subscribers will be notified when the connection is closed.
+        readPublisher.subscriptionImpl.signalError(
+                new IOException("connection closed locally"));
+    }
+
+    /**
+     * 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.on()) {
+            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;
+            Demand 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(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(() -> "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 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 WriteSubscription subscription;
+        volatile List<ByteBuffer> current;
+        volatile boolean completed;
+        final AsyncTriggerEvent startSubscription =
+                new AsyncTriggerEvent(this::signalError, this::startSubscription);
+        final WriteEvent writeEvent = new WriteEvent(channel, this);
+        final Demand writeDemand = new Demand();
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            WriteSubscription previous = this.subscription;
+            if (debug.on()) debug.log("subscribed for writing");
+            try {
+                boolean needEvent = current == null;
+                if (needEvent) {
+                    if (previous != null && previous.upstreamSubscription != subscription) {
+                        previous.dropSubscription();
+                    }
+                }
+                this.subscription = new WriteSubscription(subscription);
+                if (needEvent) {
+                    if (debug.on())
+                        debug.log("write: registering startSubscription event");
+                    client.registerEvent(startSubscription);
+                }
+            } catch (Throwable t) {
+                signalError(t);
+            }
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> bufs) {
+            assert current == null : dbgString() // this is a queue of 1.
+                    + "w.onNext current: " + current;
+            assert subscription != null : dbgString()
+                    + "w.onNext: subscription is 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");
+        }
+
+        // Don't use a SequentialScheduler here: rely on onNext() being invoked
+        // sequentially, and not being invoked if there is no demand, request(1).
+        // onNext is usually called from within a user / executor thread.
+        // Initial writing will be performed in that thread. If for some reason,
+        // not all the data can be written, a writeEvent will be registered, and
+        // writing will resume in the the selector manager thread when the
+        // writeEvent is fired.
+        //
+        // If this method is invoked in the selector manager thread (because of
+        // a writeEvent), then the executor will be used to invoke request(1),
+        // ensuring that onNext() won't be invoked from within the selector
+        // thread. If not in the selector manager thread, then request(1) is
+        // invoked directly.
+        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);
+                if (debug.on()) debug.log("trying to write: %d", remaining);
+                long written = writeAvailable(bufs);
+                if (debug.on()) debug.log("wrote: %d", written);
+                assert written >= 0 : "negative number of bytes written:" + written;
+                assert written <= remaining;
+                if (remaining - written == 0) {
+                    current = null;
+                    if (writeDemand.tryDecrement()) {
+                        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();
+            }
+        }
+
+        // Kick off the initial request:1 that will start the writing side.
+        // Invoked in the selector manager thread.
+        void startSubscription() {
+            try {
+                if (debug.on()) debug.log("write: starting subscription");
+                assert client.isSelectorThread();
+                // make sure read registrations are handled before;
+                readPublisher.subscriptionImpl.handlePending();
+                if (debug.on()) debug.log("write: offloading requestMore");
+                // start writing;
+                client.theExecutor().execute(this::requestMore);
+            } catch(Throwable t) {
+                signalError(t);
+            }
+        }
+
+        void requestMore() {
+           WriteSubscription subscription = this.subscription;
+           subscription.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);
+            if (debug.on())
+                debug.log( "write completed, %d yet to send", remaining);
+            debugState("InternalWriteSubscriber::onComplete");
+        }
+
+        void resumeWriteEvent(boolean inSelectorThread) {
+            if (debug.on()) debug.log("scheduling write event");
+            resumeEvent(writeEvent, this::signalError);
+        }
+
+        void signalWritable() {
+            if (debug.on()) debug.log("channel is writable");
+            tryFlushCurrent(true);
+        }
+
+        void signalError(Throwable error) {
+            debug.log(() -> "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
+            Logger debug() { return debug; }
+        }
+
+        final class WriteSubscription implements Flow.Subscription {
+            final Flow.Subscription upstreamSubscription;
+            volatile boolean cancelled;
+            WriteSubscription(Flow.Subscription subscription) {
+                this.upstreamSubscription = subscription;
+            }
+
+            @Override
+            public void request(long n) {
+                if (cancelled) return;
+                upstreamSubscription.request(n);
+            }
+
+            @Override
+            public void cancel() {
+                dropSubscription();
+                upstreamSubscription.cancel();
+            }
+
+            void dropSubscription() {
+                synchronized (InternalWriteSubscriber.this) {
+                    cancelled = true;
+                    if (debug.on()) debug.log("write: resetting demand to 0");
+                    writeDemand.reset();
+                }
+            }
+
+            void requestMore() {
+                try {
+                    if (completed || cancelled) return;
+                    boolean requestMore;
+                    long d;
+                    // don't fiddle with demand after cancel.
+                    // see dropSubscription.
+                    synchronized (InternalWriteSubscriber.this) {
+                        if (cancelled) return;
+                        d = writeDemand.get();
+                        requestMore = writeDemand.increaseIfFulfilled();
+                    }
+                    if (requestMore) {
+                        if (debug.on()) debug.log("write: requesting more...");
+                        upstreamSubscription.request(1);
+                    } else {
+                        if (debug.on())
+                            debug.log("write: no need to request more: %d", d);
+                    }
+                } catch (Throwable t) {
+                    if (debug.on())
+                        debug.log("write: error while requesting more: " + t);
+                    cancelled = true;
+                    signalError(t);
+                    subscription.cancel();
+                } finally {
+                    debugState("leaving requestMore: ");
+                }
+            }
+        }
+    }
+
+    // ===================================================================== //
+    //                              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) {
+                if (debug.on())
+                    debug.log("read publisher: dropping pending subscriber: "
+                              + previous.subscriber);
+                previous.errorRef.compareAndSet(null, errorRef.get());
+                previous.signalOnSubscribe();
+                if (subscriptionImpl.completed) {
+                    previous.signalCompletion();
+                } else {
+                    previous.subscriber.dropSubscription();
+                }
+            }
+
+            if (debug.on()) debug.log("read publisher got subscriber");
+            subscriptionImpl.signalSubscribe();
+            debugState("leaving read.subscribe: ");
+        }
+
+        void signalError(Throwable error) {
+            if (debug.on()) debug.log("error signalled " + 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 {
+                    if (debug.on())
+                        debug.log("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) {
+                    if (debug.on())
+                        debug.log("forwarding error to subscriber: " + error);
+                    subscriber.onError(error);
+                } else {
+                    if (debug.on()) debug.log("completing subscriber");
+                    subscriber.onComplete();
+                }
+            }
+
+            void signalOnSubscribe() {
+                if (subscribed || cancelled) return;
+                synchronized (this) {
+                    if (subscribed || cancelled) return;
+                    subscribed = true;
+                }
+                subscriber.onSubscribe(this);
+                if (debug.on()) debug.log("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.
+                    if (debug.on())
+                        debug.log("handling pending subscription while completed");
+                    handlePending();
+                } else {
+                    try {
+                        if (debug.on()) debug.log("registering subscribe event");
+                        client.registerEvent(subscribeEvent);
+                    } catch (Throwable t) {
+                        signalError(t);
+                        handlePending();
+                    }
+                }
+            }
+
+            final void handleSubscribeEvent() {
+                assert client.isSelectorThread();
+                debug.log("subscribe event raised");
+                readScheduler.runOrSchedule();
+                if (readScheduler.isStopped() || completed) {
+                    // if already completed or stopped we can handle any
+                    // pending connection directly from here.
+                    if (debug.on())
+                        debug.log("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.
+             *
+             * See Reactive Streams specification, rules 2.7 and 3.4.
+             */
+            @Override
+            public final void request(long n) {
+                if (n > 0L) {
+                    boolean wasFulfilled = demand.increase(n);
+                    if (wasFulfilled) {
+                        if (debug.on()) debug.log("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() {
+                if (debug.on()) debug.log("resuming read event");
+                resumeEvent(readEvent, this::signalError);
+            }
+
+            private void pauseReadEvent() {
+                if (debug.on()) debug.log("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;
+                }
+                if (debug.on()) debug.log("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()) {
+                            if (debug.on())
+                                debug.log("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;
+                        Throwable error = errorRef.get();
+                        if (current == null)  {
+                            assert error != null;
+                            if (debug.on())
+                                debug.log("error raised before subscriber subscribed: %s",
+                                          (Object)error);
+                            return;
+                        }
+                        TubeSubscriber subscriber = current.subscriber;
+                        if (error != null) {
+                            completed = true;
+                            // safe to pause here because we're finished anyway.
+                            pauseReadEvent();
+                            if (debug.on())
+                                debug.log("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) {
+                                        if (debug.on()) debug.log("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.
+                                    if (debug.on())
+                                        debug.log("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!
+                                    if (debug.on()) debug.log("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 {
+                            if (debug.on()) debug.log("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) {
+                    if (debug.on()) debug.log("Unexpected exception in read loop", t);
+                    signalError(t);
+                } finally {
+                    handlePending();
+                }
+            }
+
+            boolean handlePending() {
+                ReadSubscription pending = pendingSubscription.getAndSet(null);
+                if (pending == null) return false;
+                if (debug.on())
+                    debug.log("handling pending subscription for %s",
+                            pending.subscriber);
+                ReadSubscription current = subscription;
+                if (current != null && current != pending && !completed) {
+                    current.subscriber.dropSubscription();
+                }
+                if (debug.on()) debug.log("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 {
+                    if (debug.on()) debug.log("socket tube is already stopped");
+                }
+                if (debug.on()) debug.log("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
+            Logger debug() { return debug; }
+        }
+    }
+
+    // ===================================================================== //
+    //                   Socket Channel Read/Write                           //
+    // ===================================================================== //
+    static final int MAX_BUFFERS = 3;
+    static final List<ByteBuffer> EOF = List.of();
+    static final List<ByteBuffer> NOTHING = List.of(Utils.EMPTY_BYTEBUFFER);
+
+    // readAvailable() will read bytes into the 'current' ByteBuffer until
+    // the ByteBuffer is full, or 0 or -1 (EOF) is returned by read().
+    // When that happens, a slice of the data that has been read so far
+    // is inserted into the returned buffer list, and if the current buffer
+    // has remaining space, that space will be used to read more data when
+    // the channel becomes readable again.
+    private volatile ByteBuffer current;
+    private List<ByteBuffer> readAvailable() throws IOException {
+        ByteBuffer buf = current;
+        buf = (buf == null || !buf.hasRemaining())
+                ? (current = buffersSource.get()) : buf;
+        assert buf.hasRemaining();
+
+        int read;
+        int pos = buf.position();
+        List<ByteBuffer> list = null;
+        while (buf.hasRemaining()) {
+            try {
+                while ((read = channel.read(buf)) > 0) {
+                    if (!buf.hasRemaining())
+                        break;
+                }
+            } catch (IOException x) {
+                if (buf.position() == pos && list == null) {
+                    // no bytes have been read, just throw...
+                    throw x;
+                } else {
+                    // some bytes have been read, return them and fail next time
+                    errorRef.compareAndSet(null, x);
+                    read = 0; // ensures outer loop will exit
+                }
+            }
+
+            // nothing read;
+            if (buf.position() == pos) {
+                // An empty list signals the end of data, and should only be
+                // returned if read == -1. If some data has already been read,
+                // then it must be returned. -1 will be returned next time
+                // the caller attempts to read something.
+                if (list == null) {
+                    // nothing read - list was null - return EOF or NOTHING
+                    list = read == -1 ? EOF : NOTHING;
+                }
+                break;
+            }
+
+            // check whether this buffer has still some free space available.
+            // if so, we will keep it for the next round.
+            final boolean hasRemaining = buf.hasRemaining();
+
+            // creates a slice to add to the list
+            int limit = buf.limit();
+            buf.limit(buf.position());
+            buf.position(pos);
+            ByteBuffer slice = buf.slice();
+
+            // restore buffer state to what it was before creating the slice
+            buf.position(buf.limit());
+            buf.limit(limit);
+
+            // add the buffer to the list
+            list = addToList(list, slice.asReadOnlyBuffer());
+            if (read <= 0 || list.size() == MAX_BUFFERS) {
+                break;
+            }
+
+            buf = hasRemaining ? buf : (current = buffersSource.get());
+            pos = buf.position();
+            assert buf.hasRemaining();
+        }
+        return list;
+    }
+
+    private <T> List<T> addToList(List<T> list, T item) {
+        int size = list == null ? 0 : list.size();
+        switch (size) {
+            case 0: return List.of(item);
+            case 1: return List.of(list.get(0), item);
+            case 2: return List.of(list.get(0), list.get(1), item);
+            default: // slow path if MAX_BUFFERS > 3
+                ArrayList<T> res = new ArrayList<>(list);
+                res.add(item);
+                return res;
+        }
+    }
+
+    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) {
+            try {
+                long w = channel.write(srcs);
+                assert w >= 0 : "negative number of bytes written:" + w;
+                if (w == 0) {
+                    break;
+                }
+                written += w;
+            } catch (IOException x) {
+                if (written == 0) {
+                    // no bytes were written just throw
+                    throw x;
+                } else {
+                    // return how many bytes were written, will fail next time
+                    break;
+                }
+            }
+        }
+        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) {
+        if (debug.on()) debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,1272 @@
+/*
+ * 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.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 Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
+
+    final ConcurrentLinkedQueue<Http2Frame> inputQ = new ConcurrentLinkedQueue<>();
+    final SequentialScheduler sched =
+            SequentialScheduler.synchronizedScheduler(this::schedule);
+    final SubscriptionBase userSubscription =
+            new SubscriptionBase(sched, this::cancel, this::onSubscriptionError);
+
+    /**
+     * 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 HeadersConsumer rspHeadersConsumer;
+    final HttpHeadersImpl responseHeaders;
+    final HttpHeadersImpl requestPseudoHeaders;
+    volatile HttpResponse.BodySubscriber<T> responseSubscriber;
+    final HttpRequest.BodyPublisher requestPublisher;
+    volatile RequestSubscriber requestSubscriber;
+    volatile int responseCode;
+    volatile Response response;
+    // The exception with which this stream was canceled.
+    private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+    final CompletableFuture<Void> requestBodyCF = new MinimalFuture<>();
+    volatile CompletableFuture<T> responseBodyCF;
+    volatile HttpResponse.BodySubscriber<T> pendingResponseSubscriber;
+    volatile boolean stopRequested;
+
+    /** 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() {
+        boolean onCompleteCalled = false;
+        HttpResponse.BodySubscriber<T> subscriber = responseSubscriber;
+        try {
+            if (subscriber == null) {
+                subscriber = responseSubscriber = pendingResponseSubscriber;
+                if (subscriber == null) {
+                    // can't process anything yet
+                    return;
+                } else {
+                    if (debug.on()) debug.log("subscribing user subscriber");
+                    subscriber.onSubscribe(userSubscription);
+                }
+            }
+            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");
+                    if (debug.on()) debug.log("incoming: onComplete");
+                    sched.stop();
+                    subscriber.onComplete();
+                    onCompleteCalled = true;
+                    setEndStreamReceived();
+                    return;
+                } else if (userSubscription.tryDecrement()) {
+                    inputQ.remove();
+                    Log.logTrace("responseSubscriber.onNext {0}", size);
+                    if (debug.on()) debug.log("incoming: onNext(%d)", size);
+                    subscriber.onNext(dsts);
+                    if (consumed(df)) {
+                        Log.logTrace("responseSubscriber.onComplete");
+                        if (debug.on()) debug.log("incoming: onComplete");
+                        sched.stop();
+                        subscriber.onComplete();
+                        onCompleteCalled = true;
+                        setEndStreamReceived();
+                        return;
+                    }
+                } else {
+                    if (stopRequested) break;
+                    return;
+                }
+            }
+        } catch (Throwable throwable) {
+            errorRef.compareAndSet(null, throwable);
+        }
+
+        Throwable t = errorRef.get();
+        if (t != null) {
+            sched.stop();
+            try {
+                if (!onCompleteCalled) {
+                    if (debug.on())
+                        debug.log("calling subscriber.onError: %s", (Object)t);
+                    subscriber.onError(t);
+                } else {
+                    if (debug.on())
+                        debug.log("already completed: dropping error %s", (Object)t);
+                }
+            } catch (Throwable x) {
+                Log.logError("Subscriber::onError threw exception: {0}", (Object)t);
+            } finally {
+                cancelImpl(t);
+            }
+        }
+    }
+
+    // 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)
+    {
+        try {
+            Log.logTrace("Reading body on stream {0}", streamid);
+            BodySubscriber<T> bodySubscriber = handler.apply(new ResponseInfoImpl(response));
+            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;
+        } catch (Throwable t) {
+            // may be thrown by handler.apply
+            cancelImpl(t);
+            return MinimalFuture.failedFuture(t);
+        }
+    }
+
+    @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(() -> {
+            try {
+                bodySubscriber.getBody().whenComplete((T body, Throwable t) -> {
+                    if (t == null)
+                        responseBodyCF.complete(body);
+                    else
+                        responseBodyCF.completeExceptionally(t);
+                });
+            } catch(Throwable t) {
+                cancelImpl(t);
+            }
+        });
+
+        if (isCanceled()) {
+            Throwable t = getCancelCause();
+            responseBodyCF.completeExceptionally(t);
+        } else {
+            pendingResponseSubscriber = 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 = new HeadersConsumer();
+        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 {
+        if (debug.on()) debug.log("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, connection(),
+                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());
+        }
+
+        // this will clear the response headers
+        rspHeadersConsumer.reset();
+
+        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 = null;
+        boolean accepted = false;
+        try {
+            acceptor = pushGroup.acceptPushRequest(pushRequest);
+            accepted = acceptor.accepted();
+        } catch (Throwable t) {
+            if (debug.on())
+                debug.log("PushPromiseHandler::applyPushPromise threw exception %s",
+                          (Object)t);
+        }
+        if (!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;
+        }
+
+        assert accepted && acceptor != null;
+        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.getRawQuery();
+        String path = uri.getRawPath();
+        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() {
+        if (debug.on()) debug.log("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) {
+            if (debug.on()) debug.log("Released stream %d", streamid);
+            // remove this stream from the Http2Connection map.
+            connection.closeStream(streamid);
+        } else {
+            if (debug.on()) debug.log("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);
+        if (debug.on()) debug.log("Registered stream %d", id);
+    }
+
+    void signalWindowUpdate() {
+        RequestSubscriber subscriber = requestSubscriber;
+        assert subscriber != null;
+        if (debug.on()) debug.log("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;
+            if (debug.on())
+                debug.log("RequestSubscriber: onSubscribe, request 1");
+            subscription.request(1);
+        }
+
+        @Override
+        public void onNext(ByteBuffer item) {
+            if (debug.on())
+                debug.log("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) {
+            if (debug.on())
+                debug.log(() -> "RequestSubscriber: onError: " + throwable);
+            // ensure that errors are handled within the flow.
+            if (errorRef.compareAndSet(null, throwable)) {
+                sendScheduler.runOrSchedule();
+            }
+        }
+
+        @Override
+        public void onComplete() {
+            if (debug.on()) debug.log("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);
+                    cancelImpl(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()) {
+                        if (debug.on()) debug.log("trySend: %d", item.remaining());
+                        assert !endStreamSent : "internal error, send data after END_STREAM flag";
+                        DataFrame df = getDataFrame(item);
+                        if (df == null) {
+                            if (debug.on())
+                                debug.log("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;
+                            }
+                        }
+                        if (debug.on())
+                            debug.log("trySend: sending: %d", df.getDataLength());
+                        connection.sendDataFrame(df);
+                    }
+                    assert !item.hasRemaining();
+                    ByteBuffer b = outgoing.removeFirst();
+                    assert b == item;
+                } while (outgoing.peekFirst() != null);
+
+                if (debug.on()) debug.log("trySend: request 1");
+                subscription.request(1);
+            } catch (Throwable ex) {
+                if (debug.on()) debug.log("trySend: ", ex);
+                sendScheduler.stop();
+                subscription.cancel();
+                requestBodyCF.completeExceptionally(ex);
+                // need to cancel the stream to 1. tell the server
+                // we don't want to receive any more data and
+                // 2. ensure that the operation ref count will be
+                // decremented on the HttpClient.
+                cancelImpl(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());
+        try {
+            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);
+            }
+        } catch (Throwable t) {
+            cancelImpl(t);
+            requestBodyCF.completeExceptionally(t);
+        }
+        return requestBodyCF;
+    }
+
+    @Override
+    void cancel() {
+        cancel(new IOException("Stream " + streamid + " cancelled"));
+    }
+
+    void onSubscriptionError(Throwable t) {
+        errorRef.compareAndSet(null, t);
+        if (debug.on()) debug.log("Got subscription error: %s", (Object)t);
+        // This is the special case where the subscriber
+        // has requested an illegal number of items.
+        // In this case, the error doesn't come from
+        // upstream, but from downstream, and we need to
+        // handle the error without waiting for the inputQ
+        // to be exhausted.
+        stopRequested = true;
+        sched.runOrSchedule();
+    }
+
+    @Override
+    void cancel(IOException cause) {
+        cancelImpl(cause);
+    }
+
+    // This method sends a RST_STREAM frame
+    void cancelImpl(Throwable e) {
+        errorRef.compareAndSet(null, e);
+        if (debug.on()) debug.log("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) {
+                if (closing = !closed) { // assigning closing to !closed
+                    closed=true;
+                }
+            }
+        }
+        if (closing) { // true if the stream has not been closed yet
+            if (responseSubscriber != null || pendingResponseSubscriber != null)
+                sched.runOrSchedule();
+        }
+        completeResponseExceptionally(e);
+        if (!requestBodyCF.isDone()) {
+            requestBodyCF.completeExceptionally(errorRef.get()); // we may be sending the body..
+        }
+        if (responseBodyCF != null) {
+            responseBodyCF.completeExceptionally(errorRef.get());
+        }
+        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, connection(),
+                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("):\n");
+                Log.dumpHeaders(sb, "    ", responseHeaders);
+                Log.logHeaders(sb.toString());
+            }
+
+            rspHeadersConsumer.reset();
+
+            // 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 errorRef.get() != 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 errorRef.get();
+    }
+
+    final String dbgString() {
+        return connection.dbgString() + "/Stream("+streamid+")";
+    }
+
+    private class HeadersConsumer extends Http2Connection.ValidatingHeadersConsumer {
+
+        void reset() {
+            super.reset();
+            responseHeaders.clear();
+        }
+
+        @Override
+        public void onDecoded(CharSequence name, CharSequence value)
+                throws UncheckedIOException
+        {
+            String n = name.toString();
+            String v = value.toString();
+            super.onDecoded(n, v);
+            responseHeaders.addHeader(n, v);
+            if (Log.headers() && Log.trace()) {
+                Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}",
+                             streamid, n, v);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/TimeoutEvent.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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/WindowController.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,330 @@
+/*
+ * 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.Logger;
+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 Logger debug =
+            Utils.getDebugLogger("WindowController"::toString, Utils.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 & 0x1) == 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
+                if (debug.on())
+                    debug.log("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;
+            if (debug.on())
+                debug.log("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;
+            if (debug.on())
+                debug.log("Connection window size is now %d (amount added %d)",
+                          size, amount);
+
+            // 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) {
+                // The stream may have been cancelled.
+                if (debug.on())
+                    debug.log("WARNING: No entry found for streamid: %s. May be cancelled?",
+                              streamid);
+            } else {
+                size += amount;
+                if (size < 0)
+                    return false;
+                streams.put(streamid, size);
+                if (debug.on())
+                    debug.log("Stream %s window size is now %s (amount added %d)",
+                              streamid, size, amount);
+
+                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);
+                    if (debug.on())
+                        debug.log("Stream %s window size is now %s (adjusting amount %d)",
+                                  streamid, size, adjustAmount);
+                }
+            }
+        } 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,90 @@
+/*
+ * 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.Logger;
+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 Logger debug =
+            Utils.getDebugLogger(this::dbgString, Utils.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);
+        if (debug.on())
+            debug.log("maxFrameSize=%d, initWindowSize=%d, limit=%d",
+                      maxFrameSize, initWindowSize, limit);
+    }
+
+    abstract int getStreamId();
+
+    void update(int delta) {
+        if (debug.on()) debug.log("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) {
+        if (debug.on()) debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,268 @@
+/*
+ * 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;
+
+/**
+ * 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)}.
+ */
+final 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 WS_NAME  = "jdk.internal.httpclient.websocket.debug";
+    final static String HPACK_NAME = "jdk.internal.httpclient.hpack.debug";
+    final static System.Logger HTTP = System.getLogger(HTTP_NAME);
+    final static System.Logger WS = System.getLogger(WS_NAME);
+    final static System.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 System.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(System.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 final boolean on() {
+        return debugOn;
+    }
+
+    @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 createWebSocketLogger(Supplier<String> dbgTag,
+                                                    Level outLevel,
+                                                    Level errLevel) {
+        return new DebugLogger(WS, 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,124 @@
+/*
+ * 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;
+    }
+
+    /**
+     * Increases this demand by 1 but only if it is fulfilled.
+     * @return true if the demand was increased, false otherwise.
+     */
+    public boolean increaseIfFulfilled() {
+        return val.compareAndSet(0, 1);
+    }
+
+    /**
+     * 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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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.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.TreeMap;
+
+/**
+ * Implementation of HttpHeaders.
+ *
+ * The public HttpHeaders API provides a read-only view, while the
+ * non-HttpHeaders members allow for implementation specific mutation, e.g.
+ * during creation, etc.
+ */
+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(headersMap());
+    }
+
+    // non-HttpHeaders private mutators
+
+    public HttpHeadersImpl deepCopy() {
+        HttpHeadersImpl h1 = newDeepCopy();
+        for (Map.Entry<String, List<String>> entry : headersMap().entrySet()) {
+            List<String> valuesCopy = new ArrayList<>(entry.getValue());
+            h1.headersMap().put(entry.getKey(), valuesCopy);
+        }
+        return h1;
+    }
+
+    public void addHeader(String name, String value) {
+        headersMap().computeIfAbsent(name, k -> new ArrayList<>(1))
+                    .add(value);
+    }
+
+    public void setHeader(String name, String value) {
+        // headers typically have one value
+        List<String> values = new ArrayList<>(1);
+        values.add(value);
+        headersMap().put(name, values);
+    }
+
+    public void clear() {
+        headersMap().clear();
+    }
+
+    protected HttpHeadersImpl newDeepCopy() {
+        return new HttpHeadersImpl();
+    }
+
+    protected Map<String, List<String>> headersMap() {
+        return headers;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/ImmutableExtendedSSLSession.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,149 @@
+/*
+ * 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.common;
+
+import java.security.Principal;
+import java.util.List;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.ExtendedSSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SNIServerName;
+
+/**
+ * All mutating methods throw UnsupportedOperationException
+ */
+public class ImmutableExtendedSSLSession extends ExtendedSSLSession {
+    private final ExtendedSSLSession delegate;
+
+    ImmutableExtendedSSLSession(ExtendedSSLSession session) {
+        this.delegate = session;
+    }
+
+    public byte[] getId() {
+        return delegate.getId();
+    }
+
+    public SSLSessionContext getSessionContext() {
+        return delegate.getSessionContext();
+    }
+
+    public long getCreationTime() {
+        return delegate.getCreationTime();
+    }
+
+    public long getLastAccessedTime() {
+        return delegate.getLastAccessedTime();
+    }
+
+    public void invalidate() {
+        throw new UnsupportedOperationException("session is not mutable");
+    }
+
+    public boolean isValid() {
+        return delegate.isValid();
+    }
+
+    public void putValue(String name, Object value) {
+        throw new UnsupportedOperationException("session is not mutable");
+    }
+
+    public Object getValue(String name) {
+        return delegate.getValue(name);
+    }
+
+    public void removeValue(String name) {
+        throw new UnsupportedOperationException("session is not mutable");
+    }
+
+    public String [] getValueNames() {
+        return delegate.getValueNames();
+    }
+
+    public java.security.cert.Certificate [] getPeerCertificates()
+            throws SSLPeerUnverifiedException {
+        return delegate.getPeerCertificates();
+    }
+
+    public java.security.cert.Certificate [] getLocalCertificates() {
+        return delegate.getLocalCertificates();
+    }
+
+    @Deprecated
+    public javax.security.cert.X509Certificate [] getPeerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        return delegate.getPeerCertificateChain();
+    }
+
+    public Principal getPeerPrincipal()
+            throws SSLPeerUnverifiedException {
+        return delegate.getPeerPrincipal();
+    }
+
+    public Principal getLocalPrincipal() {
+        return delegate.getLocalPrincipal();
+    }
+
+    public String getCipherSuite() {
+        return delegate.getCipherSuite();
+    }
+
+    public String getProtocol() {
+        return delegate.getProtocol();
+    }
+
+    public String getPeerHost() {
+        return delegate.getPeerHost();
+    }
+
+    public int getPeerPort() {
+        return delegate.getPeerPort();
+    }
+
+    public int getPacketBufferSize() {
+        return delegate.getPacketBufferSize();
+    }
+
+    public int getApplicationBufferSize() {
+        return delegate.getApplicationBufferSize();
+    }
+
+    public String[] getLocalSupportedSignatureAlgorithms() {
+        return delegate.getLocalSupportedSignatureAlgorithms();
+    }
+
+    public String[] getPeerSupportedSignatureAlgorithms() {
+        return delegate.getPeerSupportedSignatureAlgorithms();
+    }
+
+    public List<SNIServerName> getRequestedServerNames()  {
+        return delegate.getRequestedServerNames();
+    }
+
+    public List<byte[]> getStatusResponses() {
+        return delegate.getStatusResponses();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/ImmutableSSLSession.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,132 @@
+/*
+ * 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.common;
+
+import java.security.Principal;
+import java.util.List;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SNIServerName;
+
+/**
+ * All mutating methods throw UnsupportedOperationException
+ */
+public class ImmutableSSLSession implements SSLSession {
+    private final SSLSession delegate;
+
+    ImmutableSSLSession(SSLSession session) {
+        this.delegate = session;
+    }
+
+    public byte[] getId() {
+        return delegate.getId();
+    }
+
+    public SSLSessionContext getSessionContext() {
+        return delegate.getSessionContext();
+    }
+
+    public long getCreationTime() {
+        return delegate.getCreationTime();
+    }
+
+    public long getLastAccessedTime() {
+        return delegate.getLastAccessedTime();
+    }
+
+    public void invalidate() {
+        throw new UnsupportedOperationException("session is not mutable");
+    }
+
+    public boolean isValid() {
+        return delegate.isValid();
+    }
+
+    public void putValue(String name, Object value) {
+        throw new UnsupportedOperationException("session is not mutable");
+    }
+
+    public Object getValue(String name) {
+        return delegate.getValue(name);
+    }
+
+    public void removeValue(String name) {
+        throw new UnsupportedOperationException("session is not mutable");
+    }
+
+    public String [] getValueNames() {
+        return delegate.getValueNames();
+    }
+
+    public java.security.cert.Certificate [] getPeerCertificates()
+            throws SSLPeerUnverifiedException {
+        return delegate.getPeerCertificates();
+    }
+
+    public java.security.cert.Certificate [] getLocalCertificates() {
+        return delegate.getLocalCertificates();
+    }
+
+    @Deprecated
+    public javax.security.cert.X509Certificate [] getPeerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        return delegate.getPeerCertificateChain();
+    }
+
+    public Principal getPeerPrincipal()
+            throws SSLPeerUnverifiedException {
+        return delegate.getPeerPrincipal();
+    }
+
+    public Principal getLocalPrincipal() {
+        return delegate.getLocalPrincipal();
+    }
+
+    public String getCipherSuite() {
+        return delegate.getCipherSuite();
+    }
+
+    public String getProtocol() {
+        return delegate.getProtocol();
+    }
+
+    public String getPeerHost() {
+        return delegate.getPeerHost();
+    }
+
+    public int getPeerPort() {
+        return delegate.getPeerPort();
+    }
+
+    public int getPacketBufferSize() {
+        return delegate.getPacketBufferSize();
+    }
+
+    public int getApplicationBufferSize() {
+        return delegate.getApplicationBufferSize();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Log.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,322 @@
+/*
+ * 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;
+                    default:
+                        // ignore bad values
+                }
+                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(Locale.US)) {
+                                case "control":
+                                    frametypes |= CONTROL;
+                                    break;
+                                case "data":
+                                    frametypes |= DATA;
+                                    break;
+                                case "window":
+                                    frametypes |= WINDOW_UPDATES;
+                                    break;
+                                case "all":
+                                    frametypes = ALL;
+                                    break;
+                                default:
+                                    // ignore bad values
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        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.getEndpointIdentificationAlgorithm() != null) {
+            sb.append("\n    endpointIdAlg: {")
+                .append(params.size()).append("}");
+            params.add(p.getEndpointIdentificationAlgorithm());
+        }
+
+        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();
+            String sep = "";
+            for (Map.Entry<String,List<String>> entry : entries) {
+                String key = entry.getKey();
+                List<String> values = entry.getValue();
+                if (values == null || values.isEmpty()) {
+                    // should not happen
+                    sb.append(sep);
+                    sb.append(prefix).append(key).append(':');
+                    sep = "\n";
+                    continue;
+                }
+                for (String value : values) {
+                    sb.append(sep);
+                    sb.append(prefix).append(key).append(':');
+                    sb.append(' ').append(value);
+                    sep = "\n";
+                }
+            }
+            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/Logger.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,156 @@
+/*
+ * 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.common;
+
+import java.util.function.Supplier;
+
+/**
+ * An internal {@code System.Logger} that is used for internal
+ * debugging purposes in the {@link java.net.http} module.
+ * <p>
+ * Though not enforced, this interface is designed for emitting
+ * debug messages with Level.DEBUG.
+ * <p>
+ * It defines {@code log} methods that default to {@code Level.DEBUG},
+ * so that they can be called in statements like:
+ * <pre>{@code debug.log("some %s with %d %s", message(), one(), params());}</pre>
+ *
+ * @implSpec
+ * This interface is implemented by loggers returned by
+ * {@link Utils#getDebugLogger(Supplier, boolean)},
+ * {@link Utils#getWebSocketLogger(Supplier, boolean)}and
+ * {@link Utils#getHpackLogger(Supplier, boolean)}.
+ * It is not designed to be implemented by any other
+ * loggers. Do not use outside of this module.
+ */
+public interface Logger extends System.Logger {
+    /**
+     * Tells whether this logger is on.
+     * @implSpec The default implementation for this method calls
+     * {@code this.isLoggable(Level.DEBUG);}
+     */
+    public default boolean on() {
+        return isLoggable(Level.DEBUG);
+    }
+
+    /**
+     * Logs a message.
+     *
+     * @implSpec The default implementation for this method calls
+     * {@code this.log(Level.DEBUG, msg);}
+     *
+     * @param msg the string message (or a key in the message catalog, if
+     * this logger is a {@link
+     * System.LoggerFinder#getLocalizedLogger(java.lang.String,
+     * java.util.ResourceBundle, java.lang.Module) localized logger});
+     * can be {@code null}.
+     */
+    public default void log(String msg) {
+        log(Level.DEBUG, msg);
+    }
+
+    /**
+     * Logs a lazily supplied message.
+     *
+     * @implSpec The default implementation for this method calls
+     * {@code this.log(Level.DEBUG, msgSupplier);}
+     *
+     * @param msgSupplier a supplier function that produces a message.
+     *
+     * @throws NullPointerException if {@code msgSupplier} is {@code null}.
+     */
+    public default void log(Supplier<String> msgSupplier) {
+        log(Level.DEBUG, msgSupplier);
+    }
+
+    /**
+     * Logs a message produced from the given object.
+     *
+     * @implSpec The default implementation for this method calls
+     * {@code this.log(Level.DEBUG, obj);}
+     *
+     * @param obj the object to log.
+     *
+     * @throws NullPointerException if {@code obj} is {@code null}.
+     */
+    public default void log(Object obj) {
+        log(Level.DEBUG,  obj);
+    }
+
+    /**
+     * Logs a message associated with a given throwable.
+     *
+     * @implSpec The default implementation for this method calls
+     * {@code this.log(Level.DEBUG, msg, thrown);}
+     *
+     * @param msg the string message (or a key in the message catalog, if
+     * this logger is a {@link
+     * System.LoggerFinder#getLocalizedLogger(java.lang.String,
+     * java.util.ResourceBundle, java.lang.Module) localized logger});
+     * can be {@code null}.
+     * @param thrown a {@code Throwable} associated with the log message;
+     *        can be {@code null}.
+     */
+    public default void log(String msg, Throwable thrown) {
+        this.log(Level.DEBUG, msg, thrown);
+    }
+
+    /**
+     * Logs a lazily supplied message associated with a given throwable.
+     *
+     * @implSpec The default implementation for this method calls
+     * {@code this.log(Level.DEBUG, msgSupplier, thrown);}
+     *
+     * @param msgSupplier a supplier function that produces a message.
+     * @param thrown a {@code Throwable} associated with log message;
+     *               can be {@code null}.
+     *
+     * @throws NullPointerException if {@code msgSupplier} is {@code null}.
+     */
+    public default void log(Supplier<String> msgSupplier, Throwable thrown) {
+        log(Level.DEBUG, msgSupplier, thrown);
+    }
+
+    /**
+     * Logs a message with an optional list of parameters.
+     *
+     * @implSpec The default implementation for this method calls
+     * {@code this.log(Level.DEBUG, format, params);}
+     *
+     * @param format the string message format in
+     * {@link String#format(String, Object...)} or {@link
+     * java.text.MessageFormat} format, (or a key in the message
+     * catalog, if this logger is a {@link
+     * System.LoggerFinder#getLocalizedLogger(java.lang.String,
+     * java.util.ResourceBundle, java.lang.Module) localized logger});
+     * can be {@code null}.
+     * @param params an optional list of parameters to the message (may be
+     * none).
+     */
+    public default void log(String format, Object... params) {
+        log(Level.DEBUG, format, params);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/MinimalFuture.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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/OperationTrackers.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,85 @@
+/*
+ * 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.common;
+
+import java.net.http.HttpClient;
+
+/**
+ * A small class allowing to track how many operations are
+ * left outstanding on an instance of HttpClient.
+ */
+public final class OperationTrackers {
+    private OperationTrackers() {
+        throw new InternalError("not instantiable");
+    }
+
+    /**
+     * A tracker can return the current value of
+     * operation counters maintained by an instance
+     * of {@link Trackable}, such as an HttpClientImpl.
+     */
+    public interface Tracker {
+        // The total number of outstanding operations
+        long getOutstandingOperations();
+        // The number of outstanding HTTP/1.1 operations.
+        // A single HTTP/1.1 request may increment this counter
+        // multiple times, so the value returned will be >= to
+        // the number of active HTTP/1.1 connections, but will
+        // still be 0 if there are no active connections.
+        long getOutstandingHttpOperations();
+        // The number of active HTTP/2 streams
+        long getOutstandingHttp2Streams();
+        // The number of active WebSockets
+        long getOutstandingWebSocketOperations();
+        // Whether the facade returned to the
+        // user is still referenced
+        boolean isFacadeReferenced();
+        // The name of the object being tracked.
+        String getName();
+    }
+
+    /**
+     * Implemented by objects that maintain operation counters.
+     */
+    public interface Trackable {
+        Tracker getOperationsTracker();
+    }
+
+    /**
+     * Returns a tracker to track pending operations started on
+     * an HttpClient instance. May return null if this isn't
+     * an HttpClientImpl or HttpClientFacade.
+     * @param client the HttpClient instance to track.
+     * @return A tracker or null.
+     */
+    public static Tracker getTracker(HttpClient client) {
+        if (client instanceof Trackable) {
+            return ((Trackable)client).getOperationsTracker();
+        } else {
+            return null;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Pair.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,928 @@
+/*
+ * 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
+ *                         +------------------+
+ *
+ * Errors are reported to the downReader Flow.Subscriber
+ *
+ * }
+ * </pre>
+ */
+public class SSLFlowDelegate {
+
+    final Logger debug =
+            Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
+
+    final Executor exec;
+    final Reader reader;
+    final Writer writer;
+    final SSLEngine engine;
+    final String tubeName; // hack
+    final CompletableFuture<String> alpnCF; // completes on initial handshake
+    final static ByteBuffer SENTINEL = Utils.EMPTY_BYTEBUFFER;
+    volatile boolean close_notify_received;
+    final CompletableFuture<Void> readerCF;
+    final CompletableFuture<Void> writerCF;
+    static AtomicInteger scount = new AtomicInteger(1);
+    final int id;
+
+    /**
+     * 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.id = scount.getAndIncrement();
+        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);
+        this.readerCF = reader.completion();
+        this.writerCF = reader.completion();
+        readerCF.exceptionally(this::stopOnError);
+        writerCF.exceptionally(this::stopOnError);
+
+        CompletableFuture.allOf(reader.completion(), writer.completion())
+            .thenRun(this::normalStop);
+        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();
+        if (debug.on()) debug.log("setALPN = %s", alpn);
+        alpnCF.complete(alpn);
+    }
+
+    public String monitor() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SSL: id ").append(id);
+        sb.append(" HS state: " + states(handshakeState));
+        sb.append(" Engine state: " + engine.getHandshakeStatus().toString());
+        sb.append(" LL : ");
+        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;
+        final Object readBufferLock = new Object();
+        final Logger debugr = Utils.getDebugLogger(this::dbgString, Utils.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) {
+            if (debugr.on())
+                debugr.log("Adding %d bytes to read buffer",
+                           Utils.remaining(buffers));
+            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() {
+            if (debugr.on()) debugr.log("stop");
+            scheduler.stop();
+        }
+
+        AtomicInteger count = new AtomicInteger(0);
+
+        // work function where it all happens
+        void processData() {
+            try {
+                if (debugr.on())
+                    debugr.log("processData:"
+                           + " readBuf remaining:" + readBuf.remaining()
+                           + ", state:" + states(handshakeState)
+                           + ", engine handshake status:" + 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);
+                            if (debugr.on())
+                                debugr.log("Unwrapped: %s", result.result);
+                        }
+                        if (result.bytesProduced() > 0) {
+                            if (debugr.on())
+                                debugr.log("sending %d", result.bytesProduced());
+                            count.addAndGet(result.bytesProduced());
+                            outgoing(result.destBuffer, false);
+                        }
+                        if (result.status() == Status.BUFFER_UNDERFLOW) {
+                            if (debugr.on()) debugr.log("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) {
+                            if (debugr.on()) debugr.log("Closed: completing");
+                            outgoing(Utils.EMPTY_BB_LIST, true);
+                            return;
+                        }
+                        if (result.handshaking() && !complete) {
+                            if (debugr.on()) debugr.log("handshaking");
+                            if (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) {
+                    if (debugr.on()) debugr.log("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);
+            }
+        }
+
+        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);
+                }
+            }
+        }
+    }
+
+    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");
+            try {
+                while (true) {
+                    Thread.sleep(20 * 1000);
+                    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-");
+                }
+            } catch (InterruptedException e) {
+                System.out.println("Monitor exiting with " + e);
+            }
+        }
+    }
+
+    /**
+     * 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 Logger debugw =  Utils.getDebugLogger(this::dbgString, Utils.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) {
+                if (debugw.on()) debugw.log("adding SENTINEL");
+                completing = true;
+                writeList.add(SENTINEL);
+            } else {
+                writeList.addAll(buffers);
+            }
+            if (debugw.on())
+                debugw.log("added " + buffers.size()
+                           + " (" + Utils.remaining(buffers)
+                           + " bytes) to the writeList");
+            scheduler.runOrSchedule();
+        }
+
+        public final String dbgString() {
+            return "SSL Writer(" + tubeName + ")";
+        }
+
+        protected void onSubscribe() {
+            if (debugw.on()) debugw.log("onSubscribe initiating handshaking");
+            addData(HS_TRIGGER);  // initiates handshaking
+        }
+
+        void schedule() {
+            scheduler.runOrSchedule();
+        }
+
+        void stop() {
+            if (debugw.on()) debugw.log("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 {
+                if (debugw.on())
+                    debugw.log("processData, writeList remaining:"
+                                + Utils.remaining(writeList) + ", hsTriggered:"
+                                + hsTriggered() + ", needWrap:" + needWrap());
+
+                while (Utils.remaining(writeList) > 0 || hsTriggered() || needWrap()) {
+                    ByteBuffer[] outbufs = writeList.toArray(Utils.EMPTY_BB_ARRAY);
+                    EngineResult result = wrapBuffers(outbufs);
+                    if (debugw.on())
+                        debugw.log("wrapBuffer returned %s", result.result);
+
+                    if (result.status() == Status.CLOSED) {
+                        if (!upstreamCompleted) {
+                            upstreamCompleted = true;
+                            upstreamSubscription.cancel();
+                        }
+                        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()) {
+                        if (debugw.on()) debugw.log("handshaking");
+                        doHandshake(result, WRITER);  // ok to ignore return
+                        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 (needWrap()) {
+                            continue;
+                        } else {
+                            return;
+                        }
+                    }
+                }
+                if (completing && Utils.remaining(writeList) == 0) {
+                    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);
+            }
+        }
+
+        @SuppressWarnings("fallthrough")
+        EngineResult wrapBuffers(ByteBuffer[] src) throws SSLException {
+            if (debugw.on())
+                debugw.log("wrapping " + Utils.remaining(src) + " bytes");
+            ByteBuffer dst = getNetBuffer();
+            while (true) {
+                SSLEngineResult sslResult = engine.wrap(src, dst);
+                if (debugw.on()) debugw.log("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
+                        if (debugw.on()) debugw.log("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:
+                        if (debugw.on()) debugw.log("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;
+                        if (debugw.on())
+                            debugw.log("OK => produced: %d, not wrapped: %d",
+                                       dest.remaining(),  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";
+                        if (debug.on()) debug.log("BUFFER_UNDERFLOW");
+                        return new EngineResult(sslResult);
+                    default:
+                        if (debugw.on())
+                            debugw.log("result: %s", sslResult.getStatus());
+                        assert false : "result:" + sslResult.getStatus();
+                }
+            }
+        }
+
+        private boolean needWrap() {
+            return engine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP;
+        }
+
+        private void sendResultBytes(EngineResult result) {
+            if (result.bytesProduced() > 0) {
+                if (debugw.on())
+                    debugw.log("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) {
+        if (debug.on()) debug.log("handleError", t);
+        readerCF.completeExceptionally(t);
+        writerCF.completeExceptionally(t);
+        // no-op if already completed
+        alpnCF.completeExceptionally(t);
+        reader.stop();
+        writer.stop();
+    }
+
+    boolean stopped;
+
+    private synchronized void normalStop() {
+        if (stopped)
+            return;
+        stopped = true;
+        reader.stop();
+        writer.stop();
+    }
+
+    private Void stopOnError(Throwable currentlyUnused) {
+        // maybe log, etc
+        normalStop();
+        return null;
+    }
+
+    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 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;
+            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 boolean doHandshake(EngineResult r, int caller) {
+        // unconditionally sets the HANDSHAKING bit, while preserving DOING_TASKS
+        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:
+                int s = handshakeState.getAndUpdate((current) -> current | DOING_TASKS);
+                if ((s & DOING_TASKS) > 0) // someone else was doing tasks
+                    return false;
+
+                if (debug.on()) debug.log("obtaining and initiating task execution");
+                List<Runnable> tasks = obtainTasks();
+                executeTasks(tasks);
+                return false;  // executeTasks will resume activity
+            case NEED_WRAP:
+                if (caller == READER) {
+                    writer.addData(HS_TRIGGER);
+                    return false;
+                }
+                break;
+            case NEED_UNWRAP:
+            case NEED_UNWRAP_AGAIN:
+                // do nothing else
+                // receiving-side data will trigger unwrap
+                break;
+            default:
+                throw new InternalError("Unexpected handshake status:"
+                                        + r.handshakeStatus());
+        }
+        return true;
+    }
+
+    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) {
+        if (tasks.isEmpty())
+            return;
+        exec.execute(() -> {
+            try {
+                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);
+            }
+        });
+    }
+
+    // FIXME: acknowledge a received CLOSE request from peer
+    EngineResult doClosure(EngineResult r) throws IOException {
+        if (debug.on())
+            debug.log("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()) {
+                if (debug.on()) debug.log("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;
+        }
+
+        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 + ")";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,588 @@
+/*
+ * 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.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 {
+
+    final Logger debug =
+            Utils.getDebugLogger(this::dbgString, Utils.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 Logger debug;
+        volatile boolean subscribedCalled;
+        volatile boolean subscribedDone;
+        volatile boolean completed;
+        volatile Throwable error;
+        DelegateWrapper(Flow.Subscriber<? super List<ByteBuffer>> delegate,
+                        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) {
+                if (debug.on())
+                    debug.log("Subscriber completed before subscribe: forwarding %s",
+                              (Object)x);
+                delegate.onError(x);
+            } else if (finished) {
+                if (debug.on())
+                    debug.log("Subscriber completed before subscribe: calling onComplete()");
+                delegate.onComplete();
+            }
+        }
+
+        @Override
+        public void onError(Throwable t) {
+            if (completed) {
+                if (debug.on())
+                    debug.log("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 {
+                if (debug.on())
+                    debug.log("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) {
+                if (debug.on()) debug.log("DelegateWrapper: completing subscriber");
+                delegate.onComplete();
+            } else {
+                if (debug.on())
+                    debug.log("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) {
+            if (debug.on())
+                debug.log("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) {
+                if (debug.on())
+                    debug.log("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) {
+                if (debug.on())
+                    debug.log("SSLSubscriberWrapper (reader) " +
+                              "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) {
+            if (debug.on())
+                debug.log("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) {
+                if (debug.on())
+                    debug.log("SSLSubscriberWrapper (reader) onSubscribeImpl: "
+                              + "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.
+                if (debug.on())
+                    debug.log("SSLSubscriberWrapper (reader) onSubscribeImpl: "
+                              + "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().
+                if (debug.on())
+                    debug.log("SSLSubscriberWrapper (reader) onSubscribeImpl: "
+                              + "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;
+            if (debug.on())
+                debug.log("%s: onErrorImpl: %s", this, throwable);
+            DelegateWrapper subscriberImpl;
+            synchronized (this) {
+                subscriberImpl = subscribed;
+            }
+            if (subscriberImpl != null) {
+                subscriberImpl.onError(failed);
+            } else {
+                if (debug.on())
+                    debug.log("%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()) {
+                if (debug.on())
+                    debug.log("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) {
+        if (debug.on()) debug.log("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;
+            if (debug.on()) debug.log("setSubscription: demand=%d", demand);
+            if (demand > 0)
+                sub.request(demand);
+        }
+
+        @Override
+        public void request(long n) {
+            writeDemand.increase(n);
+            if (debug.on()) debug.log("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: ";
+        if (debug.on())
+            debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,475 @@
+/*
+ * 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.ArrayList;
+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?
+{
+    final Logger debug =
+            Utils.getDebugLogger(this::dbgString, Utils.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<Void>();
+        cf.whenComplete((v,t) -> {
+            if (t != null)
+                errorCommon(t);
+        });
+        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 is 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();
+            if (debug.on())
+                debug.log("completionAcknowledged upstreamCompleted:%s,"
+                          + " downstreamCompleted:%s, closing:%s",
+                          upstreamCompleted, downstreamCompleted, closing);
+            if (!upstreamCompleted && !closing) {
+                throw new IllegalStateException("upstream not completed");
+            }
+            completionAcknowledged = true;
+        } else {
+            if (debug.on())
+                debug.log("Adding %d to outputQ queue", Utils.remaining(buffers));
+            outputQ.add(buffers);
+        }
+        if (debug.on())
+            debug.log("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) {
+                if (debug.on())
+                    debug.log("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 && outputQ.isEmpty()) {
+                synchronized(this) {
+                    if (downstreamCompleted)
+                        return;
+                    downstreamCompleted = true;
+                }
+                if (debug.on())
+                    debug.log("DownstreamPusher: forwarding error downstream: " + error);
+                pushScheduler.stop();
+                outputQ.clear();
+                downstreamSubscriber.onError(error);
+                return;
+            }
+
+            // OK - no error, let's proceed
+            if (!outputQ.isEmpty()) {
+                if (debug.on())
+                    debug.log("DownstreamPusher: queue not empty, downstreamSubscription: %s",
+                              downstreamSubscription);
+            } else {
+                if (debug.on())
+                    debug.log("DownstreamPusher: queue empty, downstreamSubscription: %s",
+                               downstreamSubscription);
+            }
+
+            while (!outputQ.isEmpty() && downstreamSubscription.tryDecrement()) {
+                List<ByteBuffer> b = outputQ.poll();
+                if (debug.on())
+                    debug.log("DownstreamPusher: Pushing %d bytes downstream",
+                              Utils.remaining(b));
+                downstreamSubscriber.onNext(b);
+            }
+            upstreamWindowUpdate();
+            checkCompletion();
+        }
+    }
+
+    void upstreamWindowUpdate() {
+        long downstreamQueueSize = outputQ.size();
+        long upstreamWindowSize = upstreamWindow.get();
+        long n = upstreamWindowUpdate(upstreamWindowSize, downstreamQueueSize);
+        if (debug.on())
+            debug.log("upstreamWindowUpdate, "
+                      + "downstreamQueueSize:%d, upstreamWindow:%d",
+                      downstreamQueueSize, upstreamWindowSize);
+        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));
+        if (debug.on())
+            debug.log("calling downstreamSubscriber::onSubscribe on %s",
+                      downstreamSubscriber);
+        downstreamSubscriber.onSubscribe(downstreamSubscription);
+        onSubscribe();
+    }
+
+    @Override
+    public void onNext(List<ByteBuffer> item) {
+        if (debug.on()) debug.log("onNext");
+        long prev = upstreamWindow.getAndDecrement();
+        if (prev <= 0)
+            throw new IllegalStateException("invalid onNext call");
+        incomingCaller(item, false);
+        upstreamWindowUpdate();
+    }
+
+    private void upstreamRequest(long n) {
+        if (debug.on()) debug.log("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) {
+        if (debug.on()) debug.log("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)) {
+            if (debug.on()) debug.log("error", throwable);
+            pushScheduler.runOrSchedule();
+            upstreamCompleted = true;
+            cf.completeExceptionally(throwable);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void close() {
+        errorCommon(new RuntimeException("wrapper closed"));
+    }
+
+    public void close(Throwable t) {
+        errorCommon(t);
+    }
+
+    private void incomingCaller(List<ByteBuffer> l, boolean complete) {
+        try {
+            incoming(l, complete);
+        } catch(Throwable t) {
+            errorCommon(t);
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        if (debug.on()) debug.log("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) {
+            if (debug.on()) debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,101 @@
+/*
+ * 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;
+import java.util.function.Consumer;
+
+/**
+ * Maintains subscription counter and provides primitives for:
+ * - accessing window
+ * - reducing window when delivering items externally
+ * - resume delivery when window was zero previously
+ */
+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;
+    final Consumer<Throwable> onError;
+
+    public SubscriptionBase(SequentialScheduler scheduler, Runnable cancelAction) {
+        this(scheduler, cancelAction, null);
+    }
+
+    public SubscriptionBase(SequentialScheduler scheduler,
+                            Runnable cancelAction,
+                            Consumer<Throwable> onError) {
+        this.scheduler = scheduler;
+        this.cancelAction = cancelAction;
+        this.cancelled = new AtomicBoolean(false);
+        this.onError = onError;
+    }
+
+    @Override
+    public void request(long n) {
+        try {
+            if (demand.increase(n))
+                scheduler.runOrSchedule();
+        } catch(Throwable t) {
+            if (onError != null) {
+                if (cancelled.getAndSet(true))
+                    return;
+                onError.accept(t);
+            } else throw t;
+        }
+    }
+
+    @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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,962 @@
+/*
+ * 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 sun.net.NetProperties;
+import sun.net.util.IPAddressUtil;
+import sun.net.www.HeaderParser;
+
+import javax.net.ssl.ExtendedSSLSession;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+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.Level;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URLPermission;
+import java.net.http.HttpHeaders;
+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.lang.String.format;
+import static java.util.Objects.requireNonNull;
+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_WS = // Revisit: temporary dev flag.
+            getBooleanProperty(DebugLogger.WS_NAME, false);
+    public static final boolean DEBUG_HPACK = // Revisit: temporary dev flag.
+            getBooleanProperty(DebugLogger.HPACK_NAME, false);
+    public static final boolean TESTING = DEBUG;
+
+    public static final boolean isHostnameVerificationDisabled = // enabled by default
+            hostnameVerificationDisabledValue();
+
+    private static boolean hostnameVerificationDisabledValue() {
+        String prop = getProperty("jdk.internal.httpclient.disableHostnameVerification");
+        if (prop == null)
+            return false;
+        return prop.isEmpty() ? true : Boolean.parseBoolean(prop);
+    }
+
+    /**
+     * 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);
+
+    public static final BiPredicate<String, List<String>> VALIDATE_USER_HEADER =
+            (name, lv) -> {
+                requireNonNull(name, "header name");
+                requireNonNull(lv, "header values");
+                if (!isValidName(name)) {
+                    throw newIAE("invalid header name: \"%s\"", name);
+                }
+                if (!Utils.ALLOWED_HEADERS.test(name)) {
+                    throw newIAE("restricted header name: \"%s\"", name);
+                }
+                for (String value : lv) {
+                    requireNonNull(value, "header value");
+                    if (!isValidValue(value)) {
+                        throw newIAE("invalid header value for %s: \"%s\"", name, value);
+                    }
+                }
+                return true;
+            };
+
+    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 IllegalArgumentException newIAE(String message, Object... args) {
+        return new IllegalArgumentException(format(message, args));
+    }
+    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();
+    }
+
+    public static class ServerName {
+        ServerName(String name, boolean isLiteral) {
+            this.name = name;
+            this.isLiteral = isLiteral;
+        }
+
+        final String name;
+        final boolean isLiteral;
+
+        public String getName() {
+            return name;
+        }
+
+        public boolean isLiteral() {
+            return isLiteral;
+        }
+    }
+
+    /**
+     * Analyse the given address and determine if it is literal or not,
+     * returning the address in String form.
+     */
+    public static ServerName getServerName(InetSocketAddress addr) {
+        String host = addr.getHostString();
+        byte[] literal = IPAddressUtil.textToNumericFormatV4(host);
+        if (literal == null) {
+            // not IPv4 literal. Check IPv6
+            literal = IPAddressUtil.textToNumericFormatV6(host);
+            return new ServerName(host, literal != null);
+        } else {
+            return new ServerName(host, true);
+        }
+    }
+
+    private static boolean isLoopbackLiteral(byte[] bytes) {
+        if (bytes.length == 4) {
+            return bytes[0] == 127;
+        } else if (bytes.length == 16) {
+            for (int i=0; i<14; i++)
+                if (bytes[i] != 0)
+                    return false;
+            if (bytes[15] != 1)
+                return false;
+            return true;
+        } else
+            throw new InternalError();
+    }
+
+    /*
+     * 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) {
+        for (int i = 0; i < token.length(); i++) {
+            char c = token.charAt(i);
+            if (c > 255) {
+                return false;
+            }
+            if (c == ' ' || c == '\t') {
+                continue;
+            } else if (!fieldvchar[c]) {
+                return false; // forbidden byte
+            }
+        }
+        return true;
+    }
+
+
+    public static int getIntegerNetProperty(String name, int defaultValue) {
+        return AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
+                NetProperties.getInteger(name, defaultValue));
+    }
+
+    public static String getNetProperty(String name) {
+        return AccessController.doPrivileged((PrivilegedAction<String>) () ->
+                NetProperties.get(name));
+    }
+
+    public static boolean getBooleanProperty(String name, boolean def) {
+        return AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
+                Boolean.parseBoolean(System.getProperty(name, String.valueOf(def))));
+    }
+
+    public static String getProperty(String name) {
+        return AccessController.doPrivileged((PrivilegedAction<String>) () ->
+                System.getProperty(name));
+    }
+
+    public static int getIntegerProperty(String name, int defaultValue) {
+        return AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
+                Integer.parseInt(System.getProperty(name, String.valueOf(defaultValue))));
+    }
+
+    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 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 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 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 HPACK internal debug traces
+     */
+    public static Logger getHpackLogger(Supplier<String> dbgTag, Level errLevel) {
+        Level outLevel = 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 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 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
+     *            stderr (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 errLevel = on ? Level.ALL : Level.OFF;
+        return getHpackLogger(dbgTag, errLevel);
+    }
+
+    /**
+     * Get a logger for debug WebSocket 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.websocket.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 getWebSocketLogger(this::dbgTag, Level.ALL);}.
+     *          This is also equivalent to calling
+     *          {@code getWSLogger(this::dbgTag, true);}.
+     *          To obtain a logger that will only forward to the internal logger,
+     *          use {@code getWebSocketLogger(this::dbgTag, Level.OFF);}.
+     *          This is also equivalent to calling
+     *          {@code getWSLogger(this::dbgTag, false);}.
+     *
+     * @param dbgTag A lambda that returns a string that identifies the caller
+     *               (e.g: "WebSocket(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 HPACK internal debug traces
+     */
+    public static Logger getWebSocketLogger(Supplier<String> dbgTag, Level errLevel) {
+        Level outLevel = Level.OFF;
+        return DebugLogger.createWebSocketLogger(dbgTag, outLevel, errLevel);
+    }
+
+    /**
+     * Get a logger for debug WebSocket 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.websocket.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 getWebSocketLogger(this::dbgTag, true);}.
+     *          This is also equivalent to calling
+     *          {@code getWebSocketLogger(this::dbgTag, Level.ALL);}.
+     *          To obtain a logger that will only forward to the internal logger,
+     *          use {@code getWebSocketLogger(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: "WebSocket(3)")
+     * @param on  Whether messages should also be printed on
+     *            stderr (in addition to be forwarded to the internal logger).
+     *
+     * @return A logger for WebSocket internal debug traces
+     */
+    public static Logger getWebSocketLogger(Supplier<String> dbgTag, boolean on) {
+        Level errLevel = on ? Level.ALL : Level.OFF;
+        return getWebSocketLogger(dbgTag, errLevel);
+    }
+
+    /**
+     * SSLSessions returned to user are wrapped in an immutable object
+     */
+    public static SSLSession immutableSession(SSLSession session) {
+        if (session instanceof ExtendedSSLSession)
+            return new ImmutableExtendedSSLSession((ExtendedSSLSession)session);
+        else
+            return new ImmutableSSLSession(session);
+    }
+
+    /**
+     * Enabled by default. May be disabled for testing. Use with care
+     */
+    public static boolean isHostnameVerificationDisabled() {
+        return isHostnameVerificationDisabled;
+    }
+
+    public static InetSocketAddress resolveAddress(InetSocketAddress address) {
+        if (address != null && address.isUnresolved()) {
+            // The default proxy selector may select a proxy whose  address is
+            // unresolved. We must resolve the address before connecting to it.
+            address = new InetSocketAddress(address.getHostString(), address.getPort());
+        }
+        return address;
+    }
+
+    /**
+     * Returns the smallest (closest to zero) positive number {@code m} (which
+     * is also a power of 2) such that {@code n <= m}.
+     * <pre>{@code
+     *          n  pow2Size(n)
+     * -----------------------
+     *          0           1
+     *          1           1
+     *          2           2
+     *          3           4
+     *          4           4
+     *          5           8
+     *          6           8
+     *          7           8
+     *          8           8
+     *          9          16
+     *         10          16
+     *        ...         ...
+     * 2147483647  1073741824
+     * } </pre>
+     *
+     * The result is capped at {@code 1 << 30} as beyond that int wraps.
+     *
+     * @param n
+     *         capacity
+     *
+     * @return the size of the array
+     * @apiNote Used to size arrays in circular buffers (rings), usually in
+     * order to squeeze extra performance substituting {@code %} operation for
+     * {@code &}, which is up to 2 times faster.
+     */
+    public static int pow2Size(int n) {
+        if (n < 0) {
+            throw new IllegalArgumentException();
+        } else if (n == 0) {
+            return 1;
+        } else if (n >= (1 << 30)) { // 2^31 is a negative int
+            return 1 << 30;
+        } else {
+            return 1 << (32 - Integer.numberOfLeadingZeros(n - 1));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/frame/ContinuationFrame.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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.Logger;
+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 Logger debug =
+            Utils.getDebugLogger("FramesDecoder"::toString, Utils.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) {
+            if (debug.on())
+                debug.log("closed: ignoring buffer (%s bytes)",
+                          inBoundBuffer.remaining());
+            inBoundBuffer.position(inBoundBuffer.limit());
+            return;
+        }
+        int remaining = inBoundBuffer.remaining();
+        if (debug.on()) debug.log("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;
+                    if (debug.on()) debug.log("copied: %d", remaining);
+                } else {
+                    if (debug.on()) debug.log("added: %d", remaining);
+                    tailBuffers.add(inBoundBuffer);
+                    tailSize += remaining;
+                }
+            }
+        }
+        if (debug.on())
+            debug.log("Tail size is now: %d, current=", tailSize,
+                      (currentBuffer == null ? 0 : currentBuffer.remaining()));
+        Http2Frame frame;
+        while ((frame = nextFrame()) != null) {
+            if (debug.on()) debug.log("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 {
+                    if (debug.on())
+                        debug.log("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 {
+                if (debug.on())
+                    debug.log("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;
+        if (debug.on()) debug.log("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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,174 @@
+/*
+ * 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 synchronized void update(SettingsFrame updated) {
+        for (int i = 0; i < MAX_PARAM; i++) {
+            if (updated.parameters[i] != -1) {
+                parameters[i] = updated.parameters[i];
+            }
+        }
+    }
+
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,592 @@
+/*
+ * 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.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();
+
+    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 SimpleHeaderTable 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 SimpleHeaderTable(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 {
+            SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue);
+            action.onIndexed(intValue, f.name, f.value);
+        } finally {
+            state = State.READY;
+        }
+    }
+
+    private SimpleHeaderTable.HeaderField getHeaderFieldAt(int index)
+            throws IOException
+    {
+        SimpleHeaderTable.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));
+                }
+                SimpleHeaderTable.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));
+                }
+                SimpleHeaderTable.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));
+                }
+                SimpleHeaderTable.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
+    }
+
+    SimpleHeaderTable 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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,528 @@
+/*
+ * 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;
+    // The default implementation of Encoder does not use dynamic region of the
+    // HeaderTable. Thus the performance profile should be similar to that of
+    // SimpleHeaderTable.
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,232 @@
+/*
+ * 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.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+/*
+ * Adds reverse lookup to SimpleHeaderTable. Separated from SimpleHeaderTable
+ * for performance reasons. Decoder does not need this functionality. On the
+ * other hand, Encoder does.
+ */
+final class HeaderTable extends SimpleHeaderTable {
+
+    //
+    // 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 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);
+        }
+    }
+
+    //                name  ->    (value ->    [index])
+    private final Map<String, Map<String, Deque<Long>>> map;
+    private long counter = 1;
+
+    public HeaderTable(int maxSize, Logger logger) {
+        super(maxSize, logger);
+        map = new HashMap<>();
+    }
+
+    //
+    // This 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 = search(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;
+            }
+        }
+    }
+
+    @Override
+    protected void add(HeaderField f) {
+        super.add(f);
+        Map<String, Deque<Long>> values = map.computeIfAbsent(f.name, k -> new HashMap<>());
+        Deque<Long> indexes = values.computeIfAbsent(f.value, k -> new LinkedList<>());
+        long counterSnapshot = counter++;
+        indexes.add(counterSnapshot);
+        assert indexesUniqueAndOrdered(indexes);
+    }
+
+    private boolean indexesUniqueAndOrdered(Deque<Long> indexes) {
+        long maxIndexSoFar = -1;
+        for (long l : indexes) {
+            if (l <= maxIndexSoFar) {
+                return false;
+            } else {
+                maxIndexSoFar = l;
+            }
+        }
+        return true;
+    }
+
+    int search(String name, String value) {
+        Map<String, Deque<Long>> values = map.get(name);
+        if (values == null) {
+            return 0;
+        }
+        Deque<Long> indexes = values.get(value);
+        if (indexes != null) {
+            return (int) (counter - indexes.peekLast());
+        } else {
+            assert !values.isEmpty();
+            Long any = values.values().iterator().next().peekLast(); // Iterator allocation
+            return -(int) (counter - any);
+        }
+    }
+
+    @Override
+    protected HeaderField remove() {
+        HeaderField f = super.remove();
+        Map<String, Deque<Long>> values = map.get(f.name);
+        Deque<Long> indexes = values.get(f.value);
+        Long index = indexes.pollFirst();
+        if (indexes.isEmpty()) {
+            values.remove(f.value);
+        }
+        assert index != null;
+        if (values.isEmpty()) {
+            map.remove(f.name);
+        }
+        return f;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Huffman.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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 & 127) + 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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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/SimpleHeaderTable.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,368 @@
+/*
+ * 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.util.NoSuchElementException;
+
+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;
+
+/*
+ * A header table consists of two tables, the static table and the dynamic
+ * table. Following the vocabulary of RFC 7541, the length of the header table
+ * is the total number of entries in both the static and the dynamic tables.
+ * The size of the table is the sum of the sizes of its dynamic table's entries.
+ */
+class SimpleHeaderTable {
+
+    protected 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")
+    };
+
+    protected static final int STATIC_TABLE_LENGTH = staticTable.length - 1;
+    protected static final int ENTRY_SIZE = 32;
+
+    private final Logger logger;
+
+    private int maxSize;
+    private int size;
+
+    public SimpleHeaderTable(int maxSize, Logger logger) {
+        this.logger = logger;
+        setMaxSize(maxSize);
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public int maxSize() {
+        return maxSize;
+    }
+
+    public int length() {
+        return STATIC_TABLE_LENGTH + buffer.size;
+    }
+
+    HeaderField get(int index) {
+        checkIndex(index);
+        if (index <= STATIC_TABLE_LENGTH) {
+            return staticTable[index];
+        } else {
+            return buffer.get(index - STATIC_TABLE_LENGTH - 1);
+        }
+    }
+
+    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;
+        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;
+        // A header table cannot accommodate more entries than this
+        int upperBound = maxSize / ENTRY_SIZE;
+        buffer.resize(upperBound);
+    }
+
+    HeaderField evictEntry() {
+        HeaderField f = 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%%)",
+                      buffer.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 = buffer.size; i <= size; i++) {
+            HeaderField e = buffer.get(i - 1);
+            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)?
+    protected 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 // TODO: remove since used only for testing
+        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 // TODO: remove since used only for testing
+        public int hashCode() {
+            return 31 * name.hashCode() + value.hashCode();
+        }
+    }
+
+    private final CircularBuffer<HeaderField> buffer = new CircularBuffer<>(0);
+
+    protected void add(HeaderField f) {
+        buffer.add(f);
+    }
+
+    protected HeaderField remove() {
+        return buffer.remove();
+    }
+
+    //                    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/SizeUpdateWriter.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,497 @@
+/*
+ * 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.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;
+    static final int MAX_CONTROL_FRAME_PAYLOAD_LENGTH = 125;
+
+    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);
+
+        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/MessageDecoder.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,280 @@
+/*
+ * 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.Logger;
+import jdk.internal.net.http.common.Utils;
+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.
+ */
+/* Exposed for testing purposes */
+class MessageDecoder implements Frame.Consumer {
+
+    private static final Logger debug =
+            Utils.getWebSocketLogger("[Input]"::toString, Utils.DEBUG_WS);
+
+    private final MessageStreamConsumer output;
+    private final UTF8AccumulatingDecoder decoder = new UTF8AccumulatingDecoder();
+    private boolean fin;
+    private Opcode opcode, originatingOpcode;
+    private long payloadLen;
+    private long unconsumedPayloadLen;
+    private ByteBuffer binaryData;
+
+    MessageDecoder(MessageStreamConsumer output) {
+        this.output = requireNonNull(output);
+    }
+
+    /* Exposed for testing purposes */
+    MessageStreamConsumer getOutput() {
+        return output;
+    }
+
+    @Override
+    public void fin(boolean value) {
+        if (debug.on()) {
+            debug.log("fin %s", value);
+        }
+        fin = value;
+    }
+
+    @Override
+    public void rsv1(boolean value) {
+        if (debug.on()) {
+            debug.log("rsv1 %s", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Unexpected rsv1 bit");
+        }
+    }
+
+    @Override
+    public void rsv2(boolean value) {
+        if (debug.on()) {
+            debug.log("rsv2 %s", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Unexpected rsv2 bit");
+        }
+    }
+
+    @Override
+    public void rsv3(boolean value) {
+        if (debug.on()) {
+            debug.log("rsv3 %s", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Unexpected rsv3 bit");
+        }
+    }
+
+    @Override
+    public void opcode(Opcode v) {
+        if (debug.on()) {
+            debug.log("opcode %s", 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.on()) {
+            debug.log("mask %s", value);
+        }
+        if (value) {
+            throw new FailWebSocketException("Masked frame received");
+        }
+    }
+
+    @Override
+    public void payloadLen(long value) {
+        if (debug.on()) {
+            debug.log("payloadLen %s", value);
+        }
+        if (opcode.isControl()) {
+            if (value > Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH) {
+                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) {
+        // `MessageDecoder.mask(boolean)` is where a masked frame is detected and
+        // reported on; `MessageDecoder.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.on()) {
+            debug.log("payload %s", data);
+        }
+        unconsumedPayloadLen -= data.remaining();
+        boolean lastPayloadChunk = unconsumedPayloadLen == 0;
+        if (opcode.isControl()) {
+            if (binaryData != null) { // An intermediate or the last chunk
+                binaryData.put(data);
+            } else if (!lastPayloadChunk) { // 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 < Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH
+                        : dump(remaining);
+                binaryData = ByteBuffer.allocate(
+                        Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH).put(data);
+            } else { // The only chunk
+                binaryData = ByteBuffer.allocate(data.remaining()).put(data);
+            }
+        } else {
+            boolean last = fin && lastPayloadChunk;
+            boolean text = opcode == Opcode.TEXT || originatingOpcode == Opcode.TEXT;
+            if (!text) {
+                output.onBinary(data.slice(), last);
+                data.position(data.limit()); // Consume
+            } else {
+                boolean binaryNonEmpty = data.hasRemaining();
+                CharBuffer textData;
+                try {
+                    textData = decoder.decode(data, 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, otherwise:
+                    output.onText(textData, last);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void endFrame() {
+        if (debug.on()) {
+            debug.log("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 <= Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH
+                            : 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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageEncoder.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,381 @@
+/*
+ * 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.websocket;
+
+import jdk.internal.net.http.common.Logger;
+import jdk.internal.net.http.common.Utils;
+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.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+
+/*
+ * A stateful producer of binary representations of WebSocket messages being
+ * sent from the client to the server.
+ *
+ * An encoding method is given an original message and a byte buffer to put the
+ * resulting bytes to. The method is called until it returns true. Then the
+ * reset method is called. The whole sequence repeats with next message.
+ */
+public class MessageEncoder {
+
+    private static final Logger debug =
+            Utils.getWebSocketLogger("[Output]"::toString, Utils.DEBUG_WS);
+
+    private final SecureRandom maskingKeySource = new SecureRandom();
+    private final Frame.HeaderWriter headerWriter = new Frame.HeaderWriter();
+    private final Frame.Masker payloadMasker = new Frame.Masker();
+    private final CharsetEncoder charsetEncoder
+            = StandardCharsets.UTF_8.newEncoder()
+                                    .onMalformedInput(CodingErrorAction.REPORT)
+                                    .onUnmappableCharacter(CodingErrorAction.REPORT);
+    /*
+     * This buffer is used both to encode characters to UTF-8 and to calculate
+     * the length of the resulting frame's payload. The length of the payload
+     * must be known before the frame's header can be written.
+     * For implementation reasons, this buffer must have a capacity of at least
+     * the maximum size of a Close frame payload, which is 125 bytes
+     * (or Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH).
+     */
+    private final ByteBuffer intermediateBuffer = createIntermediateBuffer(
+            Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH);
+    private final ByteBuffer headerBuffer = ByteBuffer.allocate(
+            Frame.MAX_HEADER_SIZE_BYTES);
+
+    private boolean started;
+    private boolean flushing;
+    private boolean moreText = true;
+    private long headerCount;
+    /* Has the previous frame got its fin flag set? */
+    private boolean previousFin = true;
+    /* Was the previous frame TEXT or a CONTINUATION thereof? */
+    private boolean previousText;
+    private boolean closed;
+
+    /*
+     * How many bytes of the current message have been already encoded.
+     *
+     * Even though the user hands their buffers over to us, they still can
+     * manipulate these buffers while we are getting data out of them.
+     * The number of produced bytes guards us from such behaviour in the
+     * case of messages that must be restricted in size (Ping, Pong and Close).
+     * For other messages this measure provides a best-effort attempt to detect
+     * concurrent changes to buffer.
+     *
+     * Making a shallow copy (duplicate/slice) and then checking the size
+     * precondition on it would also solve the problem, but at the cost of this
+     * extra copy.
+     */
+    private int actualLen;
+
+    /*
+     * How many bytes were originally there in the message, before the encoding
+     * started.
+     */
+    private int expectedLen;
+
+    /* Exposed for testing purposes */
+    protected ByteBuffer createIntermediateBuffer(int minSize) {
+        int capacity = Utils.getIntegerNetProperty(
+                "jdk.httpclient.websocket.intermediateBufferSize", 16384);
+        return ByteBuffer.allocate(Math.max(minSize, capacity));
+    }
+
+    public void reset() {
+        // Do not reset the message stream state fields, e.g. previousFin,
+        // previousText. Just an individual message state:
+        started = false;
+        flushing = false;
+        moreText = true;
+        headerCount = 0;
+        actualLen = 0;
+    }
+
+    /*
+     * Encodes text messages by cutting them into fragments of maximum size of
+     * intermediateBuffer.capacity()
+     */
+    public boolean encodeText(CharBuffer src, boolean last, ByteBuffer dst)
+            throws IOException
+    {
+        if (debug.on()) {
+            debug.log("encode text src=[pos=%s lim=%s cap=%s] last=%s dst=%s",
+                      src.position(), src.limit(), src.capacity(), last, dst);
+        }
+        if (closed) {
+            throw new IOException("Output closed");
+        }
+        if (!started) {
+            if (!previousText && !previousFin) {
+                // Previous data message was a partial binary message
+                throw new IllegalStateException("Unexpected text message");
+            }
+            started = true;
+            headerBuffer.position(0).limit(0);
+            intermediateBuffer.position(0).limit(0);
+            charsetEncoder.reset();
+        }
+        while (true) {
+            if (debug.on()) {
+                debug.log("put");
+            }
+            if (!putAvailable(headerBuffer, dst)) {
+                return false;
+            }
+            if (debug.on()) {
+                debug.log("mask");
+            }
+            if (maskAvailable(intermediateBuffer, dst) < 0) {
+                return false;
+            }
+            if (debug.on()) {
+                debug.log("moreText");
+            }
+            if (!moreText) {
+                previousFin = last;
+                previousText = true;
+                return true;
+            }
+            intermediateBuffer.clear();
+            CoderResult r = null;
+            if (!flushing) {
+                r = charsetEncoder.encode(src, intermediateBuffer, true);
+                if (r.isUnderflow()) {
+                    flushing = true;
+                }
+            }
+            if (flushing) {
+                r = charsetEncoder.flush(intermediateBuffer);
+                if (r.isUnderflow()) {
+                    moreText = false;
+                }
+            }
+            if (r.isError()) {
+                try {
+                    r.throwException();
+                } catch (CharacterCodingException e) {
+                    throw new IOException("Malformed text message", e);
+                }
+            }
+            if (debug.on()) {
+                debug.log("frame #%s", headerCount);
+            }
+            intermediateBuffer.flip();
+            Opcode opcode = previousFin && headerCount == 0
+                    ? Opcode.TEXT : Opcode.CONTINUATION;
+            boolean fin = last && !moreText;
+            setupHeader(opcode, fin, intermediateBuffer.remaining());
+            headerCount++;
+        }
+    }
+
+    private boolean putAvailable(ByteBuffer src, ByteBuffer dst) {
+        int available = dst.remaining();
+        if (available >= src.remaining()) {
+            dst.put(src);
+            return true;
+        } else {
+            int lim = src.limit();                   // save the limit
+            src.limit(src.position() + available);
+            dst.put(src);
+            src.limit(lim);                          // restore the limit
+            return false;
+        }
+    }
+
+    public boolean encodeBinary(ByteBuffer src, boolean last, ByteBuffer dst)
+            throws IOException
+    {
+        if (debug.on()) {
+            debug.log("encode binary src=%s last=%s dst=%s",
+                      src, last, dst);
+        }
+        if (closed) {
+            throw new IOException("Output closed");
+        }
+        if (!started) {
+            if (previousText && !previousFin) {
+                // Previous data message was a partial text message
+                throw new IllegalStateException("Unexpected binary message");
+            }
+            expectedLen = src.remaining();
+            Opcode opcode = previousFin ? Opcode.BINARY : Opcode.CONTINUATION;
+            setupHeader(opcode, last, expectedLen);
+            previousFin = last;
+            previousText = false;
+            started = true;
+        }
+        if (!putAvailable(headerBuffer, dst)) {
+            return false;
+        }
+        int count = maskAvailable(src, dst);
+        actualLen += Math.abs(count);
+        if (count >= 0 && actualLen != expectedLen) {
+            throw new IOException("Concurrent message modification");
+        }
+        return count >= 0;
+    }
+
+    private int maskAvailable(ByteBuffer src, ByteBuffer dst) {
+        int r0 = dst.remaining();
+        payloadMasker.transferMasking(src, dst);
+        int masked = r0 - dst.remaining();
+        return src.hasRemaining() ? -masked : masked;
+    }
+
+    public boolean encodePing(ByteBuffer src, ByteBuffer dst)
+            throws IOException
+    {
+        if (debug.on()) {
+            debug.log("encode ping src=%s dst=%s", src, dst);
+        }
+        if (closed) {
+            throw new IOException("Output closed");
+        }
+        if (!started) {
+            expectedLen = src.remaining();
+            if (expectedLen > Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH) {
+                throw new IllegalArgumentException("Long message: " + expectedLen);
+            }
+            setupHeader(Opcode.PING, true, expectedLen);
+            started = true;
+        }
+        if (!putAvailable(headerBuffer, dst)) {
+            return false;
+        }
+        int count = maskAvailable(src, dst);
+        actualLen += Math.abs(count);
+        if (count >= 0 && actualLen != expectedLen) {
+            throw new IOException("Concurrent message modification");
+        }
+        return count >= 0;
+    }
+
+    public boolean encodePong(ByteBuffer src, ByteBuffer dst)
+            throws IOException
+    {
+        if (debug.on()) {
+            debug.log("encode pong src=%s dst=%s",
+                      src, dst);
+        }
+        if (closed) {
+            throw new IOException("Output closed");
+        }
+        if (!started) {
+            expectedLen = src.remaining();
+            if (expectedLen > Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH) {
+                throw new IllegalArgumentException("Long message: " + expectedLen);
+            }
+            setupHeader(Opcode.PONG, true, expectedLen);
+            started = true;
+        }
+        if (!putAvailable(headerBuffer, dst)) {
+            return false;
+        }
+        int count = maskAvailable(src, dst);
+        actualLen += Math.abs(count);
+        if (count >= 0 && actualLen != expectedLen) {
+            throw new IOException("Concurrent message modification");
+        }
+        return count >= 0;
+    }
+
+    public boolean encodeClose(int statusCode, CharBuffer reason, ByteBuffer dst)
+            throws IOException
+    {
+        if (debug.on()) {
+            debug.log("encode close statusCode=%s reason=[pos=%s lim=%s cap=%s] dst=%s",
+                      statusCode, reason.position(), reason.limit(), reason.capacity(), dst);
+        }
+        if (closed) {
+            throw new IOException("Output closed");
+        }
+        if (!started) {
+            if (debug.on()) {
+                debug.log("reason [pos=%s lim=%s cap=%s]",
+                          reason.position(), reason.limit(), reason.capacity());
+            }
+            intermediateBuffer.position(0).limit(Frame.MAX_CONTROL_FRAME_PAYLOAD_LENGTH);
+            intermediateBuffer.putChar((char) statusCode);
+            CoderResult r = charsetEncoder.reset().encode(reason, intermediateBuffer, true);
+            if (r.isUnderflow()) {
+                if (debug.on()) {
+                    debug.log("flushing");
+                }
+                r = charsetEncoder.flush(intermediateBuffer);
+            }
+            if (debug.on()) {
+                debug.log("encoding result: %s", r);
+            }
+            if (r.isError()) {
+                try {
+                    r.throwException();
+                } catch (CharacterCodingException e) {
+                    throw new IOException("Malformed reason", e);
+                }
+            } else if (r.isOverflow()) {
+                // Here the 125 bytes size is ensured by the check for overflow
+                throw new IOException("Long reason");
+            } else if (!r.isUnderflow()) {
+                throw new InternalError(); // assertion
+            }
+            intermediateBuffer.flip();
+            setupHeader(Opcode.CLOSE, true, intermediateBuffer.remaining());
+            started = true;
+            closed = true;
+            if (debug.on()) {
+                debug.log("intermediateBuffer=%s", intermediateBuffer);
+            }
+        }
+        if (!putAvailable(headerBuffer, dst)) {
+            return false;
+        }
+        return maskAvailable(intermediateBuffer, dst) >= 0;
+    }
+
+    private void setupHeader(Opcode opcode, boolean fin, long payloadLen) {
+        if (debug.on()) {
+            debug.log("frame opcode=%s fin=%s len=%s",
+                      opcode, fin, payloadLen);
+        }
+        headerBuffer.clear();
+        int mask = maskingKeySource.nextInt();
+        headerWriter.fin(fin)
+                    .opcode(opcode)
+                    .payloadLen(payloadLen)
+                    .mask(mask)
+                    .write(headerBuffer);
+        headerBuffer.flip();
+        payloadMasker.mask(mask);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageQueue.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,376 @@
+/*
+ * 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.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+import static jdk.internal.net.http.common.Utils.pow2Size;
+
+/*
+ * A FIFO message storage facility.
+ *
+ * The queue supports at most one consumer and an arbitrary number of producers.
+ * Methods `peek`, `remove` and `isEmpty` must not be invoked concurrently.
+ * Methods `addText`, `addBinary`, `addPing`, `addPong` and `addClose` may be
+ * invoked concurrently.
+ *
+ * This queue is of a bounded size. The queue pre-allocates array of the said
+ * size and fills it with `Message` elements. The resulting structure never
+ * changes. This allows to avoid re-allocation and garbage collection of
+ * elements and arrays thereof. For this reason `Message` elements are never
+ * returned from the `peek` method. Instead their components passed to the
+ * provided callback.
+ *
+ * The queue consists of:
+ *
+ *   - a ring array of n + 1 `Message` elements
+ *   - indexes H and T denoting the head and the tail elements of the queue
+ *     respectively
+ *
+ * Each `Message` element contains a boolean flag. This flag is an auxiliary
+ * communication between the producers and the consumer. The flag shows
+ * whether or not the element is ready to be consumed (peeked at, removed). The
+ * flag is required since updating an element involves many fields and thus is
+ * not an atomic action. An addition to the queue happens in two steps:
+ *
+ * # Step 1
+ *
+ * Producers race with each other to secure an index for the element they add.
+ * T is atomically advanced [1] only if the advanced value doesn't equal to H
+ * (a producer doesn't bump into the head of the queue).
+ *
+ * # Step 2
+ *
+ * Once T is advanced in the previous step, the producer updates the message
+ * fields of the element at the previous value of T and then sets the flag of
+ * this element.
+ *
+ * A removal happens in a single step. The consumer gets the element at index H.
+ * If the flag of this element is set, the consumer clears the fields of the
+ * element, clears the flag and finally advances H.
+ *
+ * ----------------------------------------------------------------------------
+ * [1] To advance the index is to change it from i to (i + 1) % (n + 1).
+ */
+public class MessageQueue {
+
+    private final Message[] elements;
+
+    private final AtomicInteger tail = new AtomicInteger();
+    private volatile int head;
+
+    public MessageQueue(int capacity) {
+        if (capacity < 1) {
+            throw new IllegalArgumentException();
+        }
+        int s = pow2Size(capacity + 1);
+        assert s % 2 == 0 : s;
+        Message[] array = new Message[s];
+        for (int i = 0; i < array.length; i++) {
+            array[i] = new Message();
+        }
+        elements = array;
+    }
+
+    /* Exposed for testing purposes */
+    protected static int effectiveCapacityOf(int n) {
+        return pow2Size(n + 1) - 1;
+    }
+
+    public <T> void addText(CharBuffer message,
+                            boolean isLast,
+                            T attachment,
+                            BiConsumer<? super T, ? super Throwable> action,
+                            CompletableFuture<T> future)
+            throws IOException
+    {
+        add(MessageQueue.Type.TEXT, null, null, message, isLast, -1, attachment,
+            action, future);
+    }
+
+    private <T> void add(Type type,
+                         Supplier<? extends ByteBuffer> binarySupplier,
+                         ByteBuffer binary,
+                         CharBuffer text,
+                         boolean isLast,
+                         int statusCode,
+                         T attachment,
+                         BiConsumer<? super T, ? super Throwable> action,
+                         CompletableFuture<? super T> future)
+            throws IOException
+    {
+        // Pong "subtype" is determined by whichever field (data carrier)
+        // is not null. Both fields cannot be null or non-null simultaneously.
+        assert type != Type.PONG || (binary == null ^ binarySupplier == null);
+        int h, currentTail, newTail;
+        do {
+            h = head;
+            currentTail = tail.get();
+            newTail = (currentTail + 1) & (elements.length - 1);
+            if (newTail == h) {
+                throw new IOException("Queue full");
+            }
+        } while (!tail.compareAndSet(currentTail, newTail));
+        Message t = elements[currentTail];
+        if (t.ready) {
+            throw new InternalError();
+        }
+        t.type = type;
+        t.binarySupplier = binarySupplier;
+        t.binary = binary;
+        t.text = text;
+        t.isLast = isLast;
+        t.statusCode = statusCode;
+        t.attachment = attachment;
+        t.action = action;
+        t.future = future;
+        t.ready = true;
+    }
+
+    public <T> void addBinary(ByteBuffer message,
+                              boolean isLast,
+                              T attachment,
+                              BiConsumer<? super T, ? super Throwable> action,
+                              CompletableFuture<? super T> future)
+            throws IOException
+    {
+        add(MessageQueue.Type.BINARY, null, message, null, isLast, -1, attachment,
+            action, future);
+    }
+
+    public <T> void addPing(ByteBuffer message,
+                            T attachment,
+                            BiConsumer<? super T, ? super Throwable> action,
+                            CompletableFuture<? super T> future)
+            throws IOException
+    {
+        add(MessageQueue.Type.PING, null, message, null, false, -1, attachment,
+            action, future);
+    }
+
+    public <T> void addPong(ByteBuffer message,
+                            T attachment,
+                            BiConsumer<? super T, ? super Throwable> action,
+                            CompletableFuture<? super T> future)
+            throws IOException
+    {
+        add(MessageQueue.Type.PONG, null, message, null, false, -1, attachment,
+            action, future);
+    }
+
+    public <T> void addPong(Supplier<? extends ByteBuffer> message,
+                            T attachment,
+                            BiConsumer<? super T, ? super Throwable> action,
+                            CompletableFuture<? super T> future)
+            throws IOException
+    {
+        add(MessageQueue.Type.PONG, message, null, null, false, -1, attachment,
+            action, future);
+    }
+
+    public <T> void addClose(int statusCode,
+                             CharBuffer reason,
+                             T attachment,
+                             BiConsumer<? super T, ? super Throwable> action,
+                             CompletableFuture<? super T> future)
+            throws IOException
+    {
+        add(MessageQueue.Type.CLOSE, null, null, reason, false, statusCode,
+            attachment, action, future);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <R, E extends Throwable> R peek(QueueCallback<R, E> callback)
+            throws E
+    {
+        Message h = elements[head];
+        if (!h.ready) {
+            return callback.onEmpty();
+        }
+        Type type = h.type;
+        switch (type) {
+            case TEXT:
+                try {
+                    return (R) callback.onText(h.text, h.isLast, h.attachment,
+                                               h.action, h.future);
+                } catch (Throwable t) {
+                    // Something unpleasant is going on here with the compiler.
+                    // If this seemingly useless catch is omitted, the compiler
+                    // reports an error:
+                    //
+                    //   java: unreported exception java.lang.Throwable;
+                    //   must be caught or declared to be thrown
+                    //
+                    // My guess is there is a problem with both the type
+                    // inference for the method AND @SuppressWarnings("unchecked")
+                    // being working at the same time.
+                    throw (E) t;
+                }
+            case BINARY:
+                try {
+                    return (R) callback.onBinary(h.binary, h.isLast, h.attachment,
+                                                 h.action, h.future);
+                } catch (Throwable t) {
+                    throw (E) t;
+                }
+            case PING:
+                try {
+                    return (R) callback.onPing(h.binary, h.attachment, h.action,
+                                               h.future);
+                } catch (Throwable t) {
+                    throw (E) t;
+                }
+            case PONG:
+                try {
+                    if (h.binarySupplier != null) {
+                        return (R) callback.onPong(h.binarySupplier, h.attachment,
+                                                   h.action, h.future);
+                    } else {
+                        return (R) callback.onPong(h.binary, h.attachment, h.action,
+                                                   h.future);
+                    }
+                } catch (Throwable t) {
+                    throw (E) t;
+                }
+            case CLOSE:
+                try {
+                    return (R) callback.onClose(h.statusCode, h.text, h.attachment,
+                                                h.action, h.future);
+                } catch (Throwable t) {
+                    throw (E) t;
+                }
+            default:
+                throw new InternalError(String.valueOf(type));
+        }
+    }
+
+    public boolean isEmpty() {
+        return !elements[head].ready;
+    }
+
+    public void remove() {
+        int currentHead = head;
+        Message h = elements[currentHead];
+        if (!h.ready) {
+            throw new InternalError("Queue empty");
+        }
+        h.type = null;
+        h.binarySupplier = null;
+        h.binary = null;
+        h.text = null;
+        h.attachment = null;
+        h.action = null;
+        h.future = null;
+        h.ready = false;
+        head = (currentHead + 1) & (elements.length - 1);
+    }
+
+    private enum Type {
+
+        TEXT,
+        BINARY,
+        PING,
+        PONG,
+        CLOSE
+    }
+
+    /*
+     * A callback for consuming a queue element's fields. Can return a result of
+     * type T or throw an exception of type E. This design allows to avoid
+     * "returning" results or "throwing" errors by updating some objects from
+     * the outside of the methods.
+     */
+    public interface QueueCallback<R, E extends Throwable> {
+
+        <T> R onText(CharBuffer message,
+                     boolean isLast,
+                     T attachment,
+                     BiConsumer<? super T, ? super Throwable> action,
+                     CompletableFuture<? super T> future) throws E;
+
+        <T> R onBinary(ByteBuffer message,
+                       boolean isLast,
+                       T attachment,
+                       BiConsumer<? super T, ? super Throwable> action,
+                       CompletableFuture<? super T> future) throws E;
+
+        <T> R onPing(ByteBuffer message,
+                     T attachment,
+                     BiConsumer<? super T, ? super Throwable> action,
+                     CompletableFuture<? super T> future) throws E;
+
+        <T> R onPong(ByteBuffer message,
+                     T attachment,
+                     BiConsumer<? super T, ? super Throwable> action,
+                     CompletableFuture<? super T> future) throws E;
+
+        <T> R onPong(Supplier<? extends ByteBuffer> message,
+                     T attachment,
+                     BiConsumer<? super T, ? super Throwable> action,
+                     CompletableFuture<? super T> future) throws E;
+
+        <T> R onClose(int statusCode,
+                      CharBuffer reason,
+                      T attachment,
+                      BiConsumer<? super T, ? super Throwable> action,
+                      CompletableFuture<? super T> future) throws E;
+
+        /* The queue is empty*/
+        R onEmpty() throws E;
+    }
+
+    /*
+     * A union of components of all WebSocket message types; also a node in a
+     * queue.
+     *
+     * A `Message` never leaves the context of the queue, thus the reference to
+     * it cannot be retained by anyone other than the queue.
+     */
+    private static class Message {
+
+        private volatile boolean ready;
+
+        // -- The source message fields --
+
+        private Type type;
+        private Supplier<? extends ByteBuffer> binarySupplier;
+        private ByteBuffer binary;
+        private CharBuffer text;
+        private boolean isLast;
+        private int statusCode;
+        private Object attachment;
+        @SuppressWarnings("rawtypes")
+        private BiConsumer action;
+        @SuppressWarnings("rawtypes")
+        private CompletableFuture future;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/MessageStreamConsumer.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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.websocket;
+
+import java.nio.ByteBuffer;
+
+/*
+ * A callback for consuming messages and related events on the stream.
+ */
+interface MessageStreamConsumer {
+
+    void onText(CharSequence data, boolean last);
+
+    void onBinary(ByteBuffer data, boolean last);
+
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -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.BodyHandlers;
+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, BodyHandlers.discarding())
+                      .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/RawChannel.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,111 @@
+/*
+ * 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;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+/*
+ * A WebSocket view of the underlying communication channel. This view provides
+ * an asynchronous exchange of WebSocket messages rather than asynchronous
+ * exchange of bytes.
+ *
+ * Methods sendText, sendBinary, sendPing, sendPong and sendClose initiate a
+ * corresponding operation and return a CompletableFuture (CF) which will
+ * complete once the operation has completed (succeeded or failed).
+ *
+ * These methods are designed such that their clients may take an advantage on
+ * possible implementation optimizations. Namely, these methods:
+ *
+ * 1. May return null which is considered the same as a CF completed normally
+ * 2. Accept an arbitrary attachment to complete a CF with
+ * 3. Accept an action to take once the operation has completed
+ *
+ * All of the above allows not to create unnecessary instances of CF.
+ * For example, if a message has been sent straight away, there's no need to
+ * create a CF (given the parties agree on the meaning of null and are prepared
+ * to handle it).
+ * If the result of a returned CF is useless to the client, they may specify the
+ * exact instance (attachment) they want the CF to complete with. Thus, no need
+ * to create transforming stages (e.g. thenApply(useless -> myResult)).
+ * If there is the same action that needs to be done each time the CF completes,
+ * the client may pass it directly to the method instead of creating a dependant
+ * stage (e.g. whenComplete(action)).
+ */
+public interface Transport {
+
+    <T> CompletableFuture<T> sendText(CharSequence message,
+                                      boolean isLast,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action);
+
+    <T> CompletableFuture<T> sendBinary(ByteBuffer message,
+                                        boolean isLast,
+                                        T attachment,
+                                        BiConsumer<? super T, ? super Throwable> action);
+
+    <T> CompletableFuture<T> sendPing(ByteBuffer message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action);
+
+    <T> CompletableFuture<T> sendPong(ByteBuffer message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action);
+
+    /*
+     * Sends a Pong message with initially unknown data. Used for sending the
+     * most recent automatic Pong reply.
+     */
+    <T> CompletableFuture<T> sendPong(Supplier<? extends ByteBuffer> message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action);
+
+    <T> CompletableFuture<T> sendClose(int statusCode,
+                                       String reason,
+                                       T attachment,
+                                       BiConsumer<? super T, ? super Throwable> action);
+
+    void request(long n);
+
+    /*
+     * Why is this method needed? Since receiving of messages 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(); // TODO: hide
+
+    /*
+     * If this method is invoked, then all pending and subsequent send
+     * operations will fail with IOException.
+     */
+    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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+@FunctionalInterface
+public interface TransportFactory {
+
+    Transport createTransport(MessageQueue queue,
+                              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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+public class TransportFactoryImpl implements TransportFactory {
+
+    private final RawChannel channel;
+
+    public TransportFactoryImpl(RawChannel channel) {
+        this.channel = channel;
+    }
+
+    @Override
+    public Transport createTransport(MessageQueue queue,
+                                     MessageStreamConsumer consumer) {
+        return new TransportImpl(queue, 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,765 @@
+/*
+ * 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.Logger;
+import jdk.internal.net.http.common.MinimalFuture;
+import jdk.internal.net.http.common.SequentialScheduler;
+import jdk.internal.net.http.common.SequentialScheduler.CompleteRestartableTask;
+import jdk.internal.net.http.common.Utils;
+
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.SelectionKey;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+import static jdk.internal.net.http.websocket.TransportImpl.ChannelState.AVAILABLE;
+import static jdk.internal.net.http.websocket.TransportImpl.ChannelState.CLOSED;
+import static jdk.internal.net.http.websocket.TransportImpl.ChannelState.UNREGISTERED;
+import static jdk.internal.net.http.websocket.TransportImpl.ChannelState.WAITING;
+
+public class TransportImpl implements Transport {
+
+    // -- Debugging infrastructure --
+
+    private static final Logger debug =
+            Utils.getWebSocketLogger("[Transport]"::toString, Utils.DEBUG_WS);
+
+    /* Used for correlating enters to and exists from a method */
+    private final AtomicLong counter = new AtomicLong();
+
+    private final SequentialScheduler sendScheduler = new SequentialScheduler(new SendTask());
+
+    private final MessageQueue queue;
+    private final MessageEncoder encoder = new MessageEncoder();
+    /* A reusable buffer for writing, initially with no remaining bytes */
+    private final ByteBuffer dst = createWriteBuffer().position(0).limit(0);
+    /* This array is created once for gathering writes accepted by RawChannel */
+    private final ByteBuffer[] dstArray = new ByteBuffer[]{dst};
+    private final MessageStreamConsumer messageConsumer;
+    private final MessageDecoder decoder;
+    private final Frame.Reader reader = new Frame.Reader();
+
+    private final Demand demand = new Demand();
+    private final SequentialScheduler receiveScheduler;
+    private final RawChannel channel;
+    private final Object closeLock = new Object();
+    private final RawChannel.RawEvent writeEvent = new WriteEvent();
+    private final RawChannel.RawEvent readEvent = new ReadEvent();
+    private final AtomicReference<ChannelState> writeState
+            = new AtomicReference<>(UNREGISTERED);
+    private ByteBuffer data;
+    private volatile ChannelState readState = UNREGISTERED;
+    private boolean inputClosed;
+    private boolean outputClosed;
+
+    public TransportImpl(MessageQueue queue, MessageStreamConsumer consumer,
+                         RawChannel channel) {
+        this.queue = queue;
+        this.messageConsumer = consumer;
+        this.channel = channel;
+        this.decoder = new MessageDecoder(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());
+    }
+
+    private ByteBuffer createWriteBuffer() {
+        String name = "jdk.httpclient.websocket.writeBufferSize";
+        int capacity = Utils.getIntegerNetProperty(name, 16384);
+        if (debug.on()) {
+            debug.log("write buffer capacity %s", capacity);
+        }
+
+        // TODO (optimization?): allocateDirect if SSL?
+        return ByteBuffer.allocate(capacity);
+    }
+
+    private boolean write() throws IOException {
+        if (debug.on()) {
+            debug.log("writing to the channel");
+        }
+        long count = channel.write(dstArray, 0, dstArray.length);
+        if (debug.on()) {
+            debug.log("%s bytes written", count);
+        }
+        for (ByteBuffer b : dstArray) {
+            if (b.hasRemaining()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public <T> CompletableFuture<T> sendText(CharSequence message,
+                                             boolean isLast,
+                                             T attachment,
+                                             BiConsumer<? super T, ? super Throwable> action) {
+        long id = 0;
+        if (debug.on()) {
+            id = counter.incrementAndGet();
+            debug.log("enter send text %s message.length=%s last=%s",
+                              id, message.length(), isLast);
+        }
+        // TODO (optimization?):
+        // These sendXXX methods might be a good place to decide whether or not
+        // we can write straight ahead, possibly returning null instead of
+        // creating a CompletableFuture
+
+        // Even if the text is already CharBuffer, the client will not be happy
+        // if they discover the position is changing. So, no instanceof
+        // cheating, wrap always.
+        CharBuffer text = CharBuffer.wrap(message);
+        MinimalFuture<T> f = new MinimalFuture<>();
+        try {
+            queue.addText(text, isLast, attachment, action, f);
+            sendScheduler.runOrSchedule();
+        } catch (IOException e) {
+            action.accept(null, e);
+            f.completeExceptionally(e);
+        }
+        if (debug.on()) {
+            debug.log("exit send text %s returned %s", id, f);
+        }
+        return f;
+    }
+
+    @Override
+    public <T> CompletableFuture<T> sendBinary(ByteBuffer message,
+                                               boolean isLast,
+                                               T attachment,
+                                               BiConsumer<? super T, ? super Throwable> action) {
+        long id = 0;
+        if (debug.on()) {
+            id = counter.incrementAndGet();
+            debug.log("enter send binary %s message.remaining=%s last=%s",
+                              id, message.remaining(), isLast);
+        }
+        MinimalFuture<T> f = new MinimalFuture<>();
+        try {
+            queue.addBinary(message, isLast, attachment, action, f);
+            sendScheduler.runOrSchedule();
+        } catch (IOException e) {
+            action.accept(null, e);
+            f.completeExceptionally(e);
+        }
+        if (debug.on()) {
+            debug.log("exit send binary %s returned %s", id, f);
+        }
+        return f;
+    }
+
+    @Override
+    public <T> CompletableFuture<T> sendPing(ByteBuffer message,
+                                             T attachment,
+                                             BiConsumer<? super T, ? super Throwable> action) {
+        long id = 0;
+        if (debug.on()) {
+            id = counter.incrementAndGet();
+            debug.log("enter send ping %s message.remaining=%s",
+                              id, message.remaining());
+        }
+        MinimalFuture<T> f = new MinimalFuture<>();
+        try {
+            queue.addPing(message, attachment, action, f);
+            sendScheduler.runOrSchedule();
+        } catch (IOException e) {
+            action.accept(null, e);
+            f.completeExceptionally(e);
+        }
+        if (debug.on()) {
+            debug.log("exit send ping %s returned %s", id, f);
+        }
+        return f;
+    }
+
+    @Override
+    public <T> CompletableFuture<T> sendPong(ByteBuffer message,
+                                             T attachment,
+                                             BiConsumer<? super T, ? super Throwable> action) {
+        long id = 0;
+        if (debug.on()) {
+            id = counter.incrementAndGet();
+            debug.log("enter send pong %s message.remaining=%s",
+                              id, message.remaining());
+        }
+        MinimalFuture<T> f = new MinimalFuture<>();
+        try {
+            queue.addPong(message, attachment, action, f);
+            sendScheduler.runOrSchedule();
+        } catch (IOException e) {
+            action.accept(null, e);
+            f.completeExceptionally(e);
+        }
+        if (debug.on()) {
+            debug.log("exit send pong %s returned %s", id, f);
+        }
+        return f;
+    }
+
+    @Override
+    public <T> CompletableFuture<T> sendPong(Supplier<? extends ByteBuffer> message,
+                                             T attachment,
+                                             BiConsumer<? super T, ? super Throwable> action) {
+        long id = 0;
+        if (debug.on()) {
+            id = counter.incrementAndGet();
+            debug.log("enter send pong %s supplier=%s",
+                      id, message);
+        }
+        MinimalFuture<T> f = new MinimalFuture<>();
+        try {
+            queue.addPong(message, attachment, action, f);
+            sendScheduler.runOrSchedule();
+        } catch (IOException e) {
+            action.accept(null, e);
+            f.completeExceptionally(e);
+        }
+        if (debug.on()) {
+            debug.log("exit send pong %s returned %s", id, f);
+        }
+        return f;
+    }
+
+    @Override
+    public <T> CompletableFuture<T> sendClose(int statusCode,
+                                              String reason,
+                                              T attachment,
+                                              BiConsumer<? super T, ? super Throwable> action) {
+        long id = 0;
+        if (debug.on()) {
+            id = counter.incrementAndGet();
+            debug.log("enter send close %s statusCode=%s reason.length=%s",
+                              id, statusCode, reason.length());
+        }
+        MinimalFuture<T> f = new MinimalFuture<>();
+        try {
+            queue.addClose(statusCode, CharBuffer.wrap(reason), attachment, action, f);
+            sendScheduler.runOrSchedule();
+        } catch (IOException e) {
+            action.accept(null, e);
+            f.completeExceptionally(e);
+        }
+        if (debug.on()) {
+            debug.log("exit send close %s returned %s", id, f);
+        }
+        return f;
+    }
+
+    @Override
+    public void request(long n) {
+        if (debug.on()) {
+            debug.log("request %s", n);
+        }
+        if (demand.increase(n)) {
+            receiveScheduler.runOrSchedule();
+        }
+    }
+
+    @Override
+    public void acknowledgeReception() {
+        boolean decremented = demand.tryDecrement();
+        if (!decremented) {
+            throw new InternalError();
+        }
+    }
+
+    @Override
+    public void closeOutput() throws IOException {
+        if (debug.on()) {
+            debug.log("closeOutput");
+        }
+        synchronized (closeLock) {
+            if (!outputClosed) {
+                outputClosed = true;
+                try {
+                    channel.shutdownOutput();
+                } finally {
+                    if (inputClosed) {
+                        channel.close();
+                    }
+                }
+            }
+        }
+        writeState.set(CLOSED);
+        sendScheduler.runOrSchedule();
+    }
+
+    /*
+     * Permanently stops reading from the channel and delivering messages
+     * regardless of the current demand and data availability.
+     */
+    @Override
+    public void closeInput() throws IOException {
+        if (debug.on()) {
+            debug.log("closeInput");
+        }
+        synchronized (closeLock) {
+            if (!inputClosed) {
+                inputClosed = true;
+                try {
+                    receiveScheduler.stop();
+                    channel.shutdownInput();
+                } finally {
+                    if (outputClosed) {
+                        channel.close();
+                    }
+                }
+            }
+        }
+    }
+
+    /* Common states for send and receive tasks */
+    enum ChannelState {
+        UNREGISTERED,
+        AVAILABLE,
+        WAITING,
+        CLOSED,
+    }
+
+    @SuppressWarnings({"rawtypes"})
+    private class SendTask extends CompleteRestartableTask {
+
+        private final MessageQueue.QueueCallback<Boolean, IOException>
+                encodingCallback = new MessageQueue.QueueCallback<>() {
+
+            @Override
+            public <T> Boolean onText(CharBuffer message,
+                                      boolean isLast,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future) throws IOException
+            {
+                return encoder.encodeText(message, isLast, dst);
+            }
+
+            @Override
+            public <T> Boolean onBinary(ByteBuffer message,
+                                        boolean isLast,
+                                        T attachment,
+                                        BiConsumer<? super T, ? super Throwable> action,
+                                        CompletableFuture<? super T> future) throws IOException
+            {
+                return encoder.encodeBinary(message, isLast, dst);
+            }
+
+            @Override
+            public <T> Boolean onPing(ByteBuffer message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future) throws IOException
+            {
+                return encoder.encodePing(message, dst);
+            }
+
+            @Override
+            public <T> Boolean onPong(ByteBuffer message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future) throws IOException
+            {
+                return encoder.encodePong(message, dst);
+            }
+
+            @Override
+            public <T> Boolean onPong(Supplier<? extends ByteBuffer> message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future) throws IOException {
+                return encoder.encodePong(message.get(), dst);
+            }
+
+            @Override
+            public <T> Boolean onClose(int statusCode,
+                                       CharBuffer reason,
+                                       T attachment,
+                                       BiConsumer<? super T, ? super Throwable> action,
+                                       CompletableFuture<? super T> future) throws IOException
+            {
+                return encoder.encodeClose(statusCode, reason, dst);
+            }
+
+            @Override
+            public Boolean onEmpty() {
+                return false;
+            }
+        };
+
+        /* Whether the task sees the current head message for first time */
+        private boolean firstPass = true;
+        /* Whether the message has been fully encoded */
+        private boolean encoded;
+
+        // -- Current message completion communication fields --
+
+        private Object attachment;
+        private BiConsumer action;
+        private CompletableFuture future;
+        private final MessageQueue.QueueCallback<Boolean, RuntimeException>
+                /* If there is a message, loads its completion communication fields */
+                loadCallback = new MessageQueue.QueueCallback<Boolean, RuntimeException>() {
+
+            @Override
+            public <T> Boolean onText(CharBuffer message,
+                                      boolean isLast,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future)
+            {
+                SendTask.this.attachment = attachment;
+                SendTask.this.action = action;
+                SendTask.this.future = future;
+                return true;
+            }
+
+            @Override
+            public <T> Boolean onBinary(ByteBuffer message,
+                                        boolean isLast,
+                                        T attachment,
+                                        BiConsumer<? super T, ? super Throwable> action,
+                                        CompletableFuture<? super T> future)
+            {
+                SendTask.this.attachment = attachment;
+                SendTask.this.action = action;
+                SendTask.this.future = future;
+                return true;
+            }
+
+            @Override
+            public <T> Boolean onPing(ByteBuffer message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future)
+            {
+                SendTask.this.attachment = attachment;
+                SendTask.this.action = action;
+                SendTask.this.future = future;
+                return true;
+            }
+
+            @Override
+            public <T> Boolean onPong(ByteBuffer message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future)
+            {
+                SendTask.this.attachment = attachment;
+                SendTask.this.action = action;
+                SendTask.this.future = future;
+                return true;
+            }
+
+            @Override
+            public <T> Boolean onPong(Supplier<? extends ByteBuffer> message,
+                                      T attachment,
+                                      BiConsumer<? super T, ? super Throwable> action,
+                                      CompletableFuture<? super T> future)
+            {
+                SendTask.this.attachment = attachment;
+                SendTask.this.action = action;
+                SendTask.this.future = future;
+                return true;
+            }
+
+            @Override
+            public <T> Boolean onClose(int statusCode,
+                                       CharBuffer reason,
+                                       T attachment,
+                                       BiConsumer<? super T, ? super Throwable> action,
+                                       CompletableFuture<? super T> future)
+            {
+                SendTask.this.attachment = attachment;
+                SendTask.this.action = action;
+                SendTask.this.future = future;
+                return true;
+            }
+
+            @Override
+            public Boolean onEmpty() {
+                return false;
+            }
+        };
+
+        @Override
+        public void run() {
+            // Could have been only called in one of the following cases:
+            //   (a) A message has been added to the queue
+            //   (b) The channel is ready for writing
+            if (debug.on()) {
+                debug.log("enter send task");
+            }
+            while (!queue.isEmpty()) {
+                try {
+                    if (dst.hasRemaining()) {
+                        if (debug.on()) {
+                            debug.log("%s bytes remaining in buffer %s",
+                                      dst.remaining(), dst);
+                        }
+                        // The previous part of the binary representation of the
+                        // message hasn't been fully written
+                        if (!tryCompleteWrite()) {
+                            break;
+                        }
+                    } else if (!encoded) {
+                        if (firstPass) {
+                            firstPass = false;
+                            queue.peek(loadCallback);
+                            if (debug.on()) {
+                                debug.log("load message");
+                            }
+                        }
+                        dst.clear();
+                        encoded = queue.peek(encodingCallback);
+                        dst.flip();
+                        if (!tryCompleteWrite()) {
+                            break;
+                        }
+                    } else {
+                        // All done, remove and complete
+                        encoder.reset();
+                        removeAndComplete(null);
+                    }
+                } catch (Throwable t) {
+                    if (debug.on()) {
+                        debug.log("send task exception %s", (Object) t);
+                    }
+                    // buffer cleanup: if there is an exception, the buffer
+                    // should appear empty for the next write as there is
+                    // nothing to write
+                    dst.position(dst.limit());
+                    encoder.reset();
+                    removeAndComplete(t);
+                }
+            }
+            if (debug.on()) {
+                debug.log("exit send task");
+            }
+        }
+
+        private boolean tryCompleteWrite() throws IOException {
+            if (debug.on()) {
+                debug.log("enter writing");
+            }
+            boolean finished = false;
+            loop:
+            while (true) {
+                final ChannelState ws = writeState.get();
+                if (debug.on()) {
+                    debug.log("write state: %s", ws);
+                }
+                switch (ws) {
+                    case WAITING:
+                        break loop;
+                    case UNREGISTERED:
+                        if (debug.on()) {
+                            debug.log("registering write event");
+                        }
+                        channel.registerEvent(writeEvent);
+                        writeState.compareAndSet(UNREGISTERED, WAITING);
+                        if (debug.on()) {
+                            debug.log("registered write event");
+                        }
+                        break loop;
+                    case AVAILABLE:
+                        boolean written = write();
+                        if (written) {
+                            if (debug.on()) {
+                                debug.log("finished writing to the channel");
+                            }
+                            finished = true;
+                            break loop;   // All done
+                        } else {
+                            writeState.compareAndSet(AVAILABLE, UNREGISTERED);
+                            continue loop; //  Effectively "goto UNREGISTERED"
+                        }
+                    case CLOSED:
+                        throw new IOException("Output closed");
+                    default:
+                        throw new InternalError(String.valueOf(ws));
+                }
+            }
+            if (debug.on()) {
+                debug.log("exit writing");
+            }
+            return finished;
+        }
+
+        @SuppressWarnings("unchecked")
+        private void removeAndComplete(Throwable error) {
+            if (debug.on()) {
+                debug.log("removeAndComplete error=%s", (Object) error);
+            }
+            queue.remove();
+            if (error != null) {
+                try {
+                    action.accept(null, error);
+                } finally {
+                    future.completeExceptionally(error);
+                }
+            } else {
+                try {
+                    action.accept(attachment, null);
+                } finally {
+                    future.complete(attachment);
+                }
+            }
+            encoded = false;
+            firstPass = true;
+            attachment = null;
+            action = null;
+            future = null;
+        }
+    }
+
+    private class ReceiveTask extends CompleteRestartableTask {
+
+        @Override
+        public void run() {
+            if (debug.on()) {
+                debug.log("enter receive task");
+            }
+            loop:
+            while (!receiveScheduler.isStopped()) {
+                ChannelState rs = readState;
+                if (data.hasRemaining()) {
+                    if (debug.on()) {
+                        debug.log("remaining bytes received %s",
+                                  data.remaining());
+                    }
+                    if (!demand.isFulfilled()) {
+                        try {
+                            int oldPos = data.position();
+                            reader.readFrame(data, decoder);
+                            int newPos = data.position();
+                            // Reader always consumes bytes:
+                            assert oldPos != newPos : data;
+                        } catch (Throwable e) {
+                            receiveScheduler.stop();
+                            messageConsumer.onError(e);
+                        }
+                        if (!data.hasRemaining()) {
+                            rs = readState = UNREGISTERED;
+                        }
+                        continue;
+                    }
+                    break loop;
+                }
+                if (debug.on()) {
+                    debug.log("receive state: %s", rs);
+                }
+                switch (rs) {
+                    case WAITING:
+                        break loop;
+                    case UNREGISTERED:
+                        try {
+                            rs = readState = WAITING;
+                            channel.registerEvent(readEvent);
+                        } catch (Throwable e) {
+                            receiveScheduler.stop();
+                            messageConsumer.onError(e);
+                        }
+                        break loop;
+                    case AVAILABLE:
+                        try {
+                            data = channel.read();
+                        } catch (Throwable e) {
+                            receiveScheduler.stop();
+                            messageConsumer.onError(e);
+                            break loop;
+                        }
+                        if (data == null) { // EOF
+                            receiveScheduler.stop();
+                            messageConsumer.onComplete();
+                            break loop;
+                        } else if (!data.hasRemaining()) {
+                            // No data at the moment. Pretty much a "goto",
+                            // reusing the existing code path for registration
+                            rs = readState = UNREGISTERED;
+                        }
+                        continue loop;
+                    default:
+                        throw new InternalError(String.valueOf(rs));
+                }
+            }
+            if (debug.on()) {
+                debug.log("exit receive task");
+            }
+        }
+    }
+
+    private class WriteEvent implements RawChannel.RawEvent {
+
+        @Override
+        public int interestOps() {
+            return SelectionKey.OP_WRITE;
+        }
+
+        @Override
+        public void handle() {
+            if (debug.on()) {
+                debug.log("write event");
+            }
+            ChannelState s;
+            do {
+                s = writeState.get();
+                if (s == CLOSED) {
+                    if (debug.on()) {
+                        debug.log("write state %s", s);
+                    }
+                    break;
+                }
+            } while (!writeState.compareAndSet(s, AVAILABLE));
+            sendScheduler.runOrSchedule();
+        }
+    }
+
+    private class ReadEvent implements RawChannel.RawEvent {
+
+        @Override
+        public int interestOps() {
+            return SelectionKey.OP_READ;
+        }
+
+        @Override
+        public void handle() {
+            if (debug.on()) {
+                debug.log("read event");
+            }
+            readState = AVAILABLE;
+            receiveScheduler.runOrSchedule();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/UTF8AccumulatingDecoder.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,872 @@
+/*
+ * 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.Demand;
+import jdk.internal.net.http.common.Log;
+import jdk.internal.net.http.common.Logger;
+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.System.Logger.Level;
+import java.lang.ref.Reference;
+import java.net.ProtocolException;
+import java.net.URI;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+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 {
+
+    private static final Logger debug =
+            Utils.getWebSocketLogger("[WebSocket]"::toString, Utils.DEBUG_WS);
+    private final AtomicLong sendCounter = new AtomicLong();
+    private final AtomicLong receiveCounter = new AtomicLong();
+
+    enum State {
+        OPEN,
+        IDLE,
+        WAITING,
+        TEXT,
+        BINARY,
+        PING,
+        PONG,
+        CLOSE,
+        ERROR;
+    }
+
+    private final AtomicReference<ByteBuffer> lastAutomaticPong = new AtomicReference<>();
+    private final MinimalFuture<WebSocket> DONE = MinimalFuture.completedFuture(this);
+    private volatile boolean inputClosed;
+    private final AtomicBoolean outputClosed = new AtomicBoolean();
+
+    private final AtomicReference<State> state = new AtomicReference<>(OPEN);
+
+    /* Components of calls to Listener's methods */
+    private boolean last;
+    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 pendingTextOrBinary = new AtomicBoolean();
+    private final AtomicBoolean pendingPingOrPong = new AtomicBoolean();
+    private final Transport 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);
+        // Why 6? 1 sendPing/sendPong + 1 sendText/sendBinary + 1 Close +
+        // 2 automatic Ping replies + 1 automatic Close = 6 messages
+        // Why 2 automatic Pong replies? One is being sent, but the byte buffer
+        // has been set to null, another just has been added.
+        this.transport = transportFactory.createTransport(new MessageQueue(6),
+                new SignallingMessageConsumer());
+    }
+
+    // FIXME: add to action handling of errors -> signalError()
+
+    @Override
+    public CompletableFuture<WebSocket> sendText(CharSequence message,
+                                                 boolean last) {
+        Objects.requireNonNull(message);
+        long id = 0;
+        if (debug.on()) {
+            id = sendCounter.incrementAndGet();
+            debug.log("enter send text %s payload length=%s last=%s",
+                      id, message.length(), last);
+        }
+        CompletableFuture<WebSocket> result;
+        if (!setPendingTextOrBinary()) {
+            result = failedFuture(new IllegalStateException("Send pending"));
+        } else {
+            result = transport.sendText(message, last, this,
+                                        (r, e) -> clearPendingTextOrBinary());
+        }
+        if (debug.on()) {
+            debug.log("exit send text %s returned %s", id, result);
+        }
+
+        return replaceNull(result);
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendBinary(ByteBuffer message,
+                                                   boolean last) {
+        Objects.requireNonNull(message);
+        long id = 0;
+        if (debug.on()) {
+            id = sendCounter.incrementAndGet();
+            debug.log("enter send binary %s payload=%s last=%s",
+                      id, message, last);
+        }
+        CompletableFuture<WebSocket> result;
+        if (!setPendingTextOrBinary()) {
+            result = failedFuture(new IllegalStateException("Send pending"));
+        } else {
+            result = transport.sendBinary(message, last, this,
+                                          (r, e) -> clearPendingTextOrBinary());
+        }
+        if (debug.on()) {
+            debug.log("exit send binary %s returned %s", id, result);
+        }
+        return replaceNull(result);
+    }
+
+    private void clearPendingTextOrBinary() {
+        pendingTextOrBinary.set(false);
+    }
+
+    private boolean setPendingTextOrBinary() {
+        return pendingTextOrBinary.compareAndSet(false, true);
+    }
+
+    private CompletableFuture<WebSocket> replaceNull(
+            CompletableFuture<WebSocket> cf)
+    {
+        if (cf == null) {
+            return DONE;
+        } else {
+            return cf;
+        }
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendPing(ByteBuffer message) {
+        Objects.requireNonNull(message);
+        long id = 0;
+        if (debug.on()) {
+            id = sendCounter.incrementAndGet();
+            debug.log("enter send ping %s payload=%s", id, message);
+        }
+        CompletableFuture<WebSocket> result;
+        if (!setPendingPingOrPong()) {
+            result = failedFuture(new IllegalStateException("Send pending"));
+        } else {
+            result = transport.sendPing(message, this,
+                                        (r, e) -> clearPendingPingOrPong());
+        }
+        if (debug.on()) {
+            debug.log("exit send ping %s returned %s", id, result);
+        }
+        return replaceNull(result);
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendPong(ByteBuffer message) {
+        Objects.requireNonNull(message);
+        long id = 0;
+        if (debug.on()) {
+            id = sendCounter.incrementAndGet();
+            debug.log("enter send pong %s payload=%s", id, message);
+        }
+        CompletableFuture<WebSocket> result;
+        if (!setPendingPingOrPong()) {
+            result = failedFuture(new IllegalStateException("Send pending"));
+        } else {
+            result =  transport.sendPong(message, this,
+                                         (r, e) -> clearPendingPingOrPong());
+        }
+        if (debug.on()) {
+            debug.log("exit send pong %s returned %s", id, result);
+        }
+        return replaceNull(result);
+    }
+
+    private boolean setPendingPingOrPong() {
+        return pendingPingOrPong.compareAndSet(false, true);
+    }
+
+    private void clearPendingPingOrPong() {
+        pendingPingOrPong.set(false);
+    }
+
+    @Override
+    public CompletableFuture<WebSocket> sendClose(int statusCode,
+                                                  String reason) {
+        Objects.requireNonNull(reason);
+        long id = 0;
+        if (debug.on()) {
+            id = sendCounter.incrementAndGet();
+            debug.log("enter send close %s statusCode=%s reason.length=%s",
+                      id, statusCode, reason.length());
+        }
+        CompletableFuture<WebSocket> result;
+        // Close message is the only type of message whose validity is checked
+        // in the corresponding send method. This is made in order to close the
+        // output in place. Otherwise the number of Close messages in queue
+        // would not be bounded.
+        if (!isLegalToSendFromClient(statusCode)) {
+            result = failedFuture(new IllegalArgumentException("statusCode"));
+        } else if (!isLegalReason(reason)) {
+            result = failedFuture(new IllegalArgumentException("reason"));
+        } else if (!outputClosed.compareAndSet(false, true)){
+            result = failedFuture(new IOException("Output closed"));
+        } else {
+            result = sendClose0(statusCode, reason);
+        }
+        if (debug.on()) {
+            debug.log("exit send close %s returned %s", id, result);
+        }
+        return replaceNull(result);
+    }
+
+    private static boolean isLegalReason(String reason) {
+        if (reason.length() > 123) { // quick check
+            return false;
+        }
+        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder()
+                        .onMalformedInput(CodingErrorAction.REPORT)
+                        .onUnmappableCharacter(CodingErrorAction.REPORT);
+        ByteBuffer bytes;
+        try {
+            bytes = encoder.encode(CharBuffer.wrap(reason));
+        } catch (CharacterCodingException ignored) {
+            return false;
+        }
+        return bytes.remaining() <= 123;
+    }
+
+    /*
+     * The implementation uses this method internally to send Close messages
+     * with codes that are not allowed to be sent through the API.
+     */
+    private CompletableFuture<WebSocket> sendClose0(int statusCode,
+                                                    String reason) {
+        return transport.sendClose(statusCode, reason, this,
+                                   (r, e) -> processCloseError(e));
+    }
+
+    private void processCloseError(Throwable e) {
+        if (e == null) {
+            debug.log("send close completed successfully");
+        } else {
+            debug.log("send close completed with error", e);
+        }
+        outputClosed.set(true);
+        try {
+            transport.closeOutput();
+        } catch (IOException ignored) { }
+    }
+
+    @Override
+    public void request(long n) {
+        if (debug.on()) {
+            debug.log("request %s", n);
+        }
+        if (demand.increase(n)) {
+            receiveScheduler.runOrSchedule();
+        }
+    }
+
+    @Override
+    public String getSubprotocol() {
+        return subprotocol;
+    }
+
+    @Override
+    public boolean isOutputClosed() {
+        return outputClosed.get();
+    }
+
+    @Override
+    public boolean isInputClosed() {
+        return inputClosed;
+    }
+
+    @Override
+    public void abort() {
+        if (debug.on()) {
+            debug.log("abort");
+        }
+        inputClosed = true;
+        outputClosed.set(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() {
+            if (debug.on()) {
+                debug.log("enter receive task");
+            }
+            loop:
+            while (!receiveScheduler.isStopped()) {
+                State s = state.get();
+                if (debug.on()) {
+                    debug.log("receive state: %s", s);
+                }
+                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();
+                            break loop;
+                        case ERROR:
+                            processError();
+                            break loop;
+                        case IDLE:
+                            if (demand.tryDecrement()
+                                    && tryChangeState(IDLE, WAITING)) {
+                                transport.request(1);
+                            }
+                            break loop;
+                        case WAITING:
+                            // For debugging spurious signalling: when there was
+                            // a signal, but apparently nothing has changed
+                            break loop;
+                        default:
+                            throw new InternalError(String.valueOf(s));
+                    }
+                } catch (Throwable t) {
+                    signalError(t);
+                }
+            }
+            if (debug.on()) {
+                debug.log("exit receive task");
+            }
+        }
+
+        private void processError() throws IOException {
+            if (debug.on()) {
+                debug.log("processError");
+            }
+            transport.closeInput();
+            receiveScheduler.stop();
+            Throwable err = error.get();
+            if (err instanceof FailWebSocketException) {
+                int code1 = ((FailWebSocketException) err).getStatusCode();
+                err = new ProtocolException().initCause(err);
+                if (debug.on()) {
+                    debug.log("failing %s with error=%s statusCode=%s",
+                              WebSocketImpl.this, err, code1);
+                }
+                sendCloseSilently(code1);
+            }
+            long id = 0;
+            if (debug.on()) {
+                id = receiveCounter.incrementAndGet();
+                debug.log("enter onError %s error=%s", id, err);
+            }
+            try {
+                listener.onError(WebSocketImpl.this, err);
+            } finally {
+                if (debug.on()) {
+                    debug.log("exit onError %s", id);
+                }
+            }
+        }
+
+        private void processClose() throws IOException {
+            debug.log("processClose");
+            transport.closeInput();
+            receiveScheduler.stop();
+            CompletionStage<?> cs = null; // when the listener is ready to close
+            long id = 0;
+            if (debug.on()) {
+                id = receiveCounter.incrementAndGet();
+                debug.log("enter onClose %s statusCode=%s reason.length=%s",
+                          id, statusCode, reason.length());
+            }
+            try {
+                cs = listener.onClose(WebSocketImpl.this, statusCode, reason);
+            } finally {
+                debug.log("exit onClose %s returned %s", id, cs);
+            }
+            if (cs == null) {
+                cs = DONE;
+            }
+            int code;
+            if (statusCode == NO_STATUS_CODE || statusCode == CLOSED_ABNORMALLY) {
+                code = NORMAL_CLOSURE;
+                debug.log("using statusCode %s instead of %s",
+                          statusCode, code);
+
+            } else {
+                code = statusCode;
+            }
+            cs.whenComplete((r, e) -> {
+                if (debug.on()) {
+                    debug.log("CompletionStage returned by onClose completed result=%s error=%s",
+                              r, e);
+                }
+                sendCloseSilently(code);
+            });
+        }
+
+        private void processPong() {
+            long id = 0;
+            if (debug.on()) {
+                id = receiveCounter.incrementAndGet();
+                debug.log("enter onPong %s payload=%s",
+                          id, binaryData);
+            }
+            CompletionStage<?> cs = null;
+            try {
+                cs = listener.onPong(WebSocketImpl.this, binaryData);
+            } finally {
+                if (debug.on()) {
+                    debug.log("exit onPong %s returned %s", id, cs);
+                }
+            }
+        }
+
+        private void processPing() {
+            if (debug.on()) {
+                debug.log("processPing");
+            }
+            // A full copy of this (small) data is made. This way sending a
+            // replying Pong could be done in parallel with the listener
+            // handling this Ping.
+            ByteBuffer slice = binaryData.slice();
+            if (!outputClosed.get()) {
+                ByteBuffer copy = ByteBuffer.allocate(binaryData.remaining())
+                        .put(binaryData)
+                        .flip();
+                if (!trySwapAutomaticPong(copy)) {
+                    // Non-exclusive send;
+                    BiConsumer<WebSocketImpl, Throwable> reporter = (r, e) -> {
+                        if (e != null) { // TODO: better error handing. What if already closed?
+                            signalError(Utils.getCompletionCause(e));
+                        }
+                    };
+                    transport.sendPong(WebSocketImpl.this::clearAutomaticPong,
+                                       WebSocketImpl.this,
+                                       reporter);
+                }
+            }
+            long id = 0;
+            if (debug.on()) {
+                id = receiveCounter.incrementAndGet();
+                debug.log("enter onPing %s payload=%s", id, slice);
+            }
+            CompletionStage<?> cs = null;
+            try {
+                cs = listener.onPing(WebSocketImpl.this, slice);
+            } finally {
+                if (debug.on()) {
+                    debug.log("exit onPing %s returned %s", id, cs);
+                }
+            }
+        }
+
+        private void processBinary() {
+            long id = 0;
+            if (debug.on()) {
+                id = receiveCounter.incrementAndGet();
+                debug.log("enter onBinary %s payload=%s last=%s",
+                          id, binaryData, last);
+            }
+            CompletionStage<?> cs = null;
+            try {
+                cs = listener.onBinary(WebSocketImpl.this, binaryData, last);
+            } finally {
+                if (debug.on()) {
+                    debug.log("exit onBinary %s returned %s", id, cs);
+                }
+            }
+        }
+
+        private void processText() {
+            long id = 0;
+            if (debug.on()) {
+                id = receiveCounter.incrementAndGet();
+                debug.log("enter onText %s payload.length=%s last=%s",
+                          id, text.length(), last);
+            }
+            CompletionStage<?> cs = null;
+            try {
+                cs = listener.onText(WebSocketImpl.this, text, last);
+            } finally {
+                if (debug.on()) {
+                    debug.log("exit onText %s returned %s", id, cs);
+                }
+            }
+        }
+
+        private void processOpen() {
+            long id = 0;
+            if (debug.on()) {
+                id = receiveCounter.incrementAndGet();
+                debug.log("enter onOpen %s", id);
+            }
+            try {
+                listener.onOpen(WebSocketImpl.this);
+            } finally {
+                if (debug.on()) {
+                    debug.log("exit onOpen %s", id);
+                }
+            }
+        }
+    }
+
+    private void sendCloseSilently(int statusCode) {
+        sendClose0(statusCode, "").whenComplete((r, e) -> {
+            if (e != null) {
+                if (debug.on()) {
+                    debug.log("automatic closure completed with error",
+                              (Object) e);
+                }
+            }
+        });
+    }
+
+    private ByteBuffer clearAutomaticPong() {
+        ByteBuffer data;
+        do {
+            data = lastAutomaticPong.get();
+            if (data == null) {
+                // This method must never be called unless a message that is
+                // using it has been added previously
+                throw new InternalError();
+            }
+        } while (!lastAutomaticPong.compareAndSet(data, null));
+        return data;
+    }
+
+    // bound pings
+    private boolean trySwapAutomaticPong(ByteBuffer copy) {
+        ByteBuffer message;
+        boolean swapped;
+        while (true) {
+            message = lastAutomaticPong.get();
+            if (message == null) {
+                if (!lastAutomaticPong.compareAndSet(null, copy)) {
+                    // It's only this method that can change null to ByteBuffer,
+                    // and this method is invoked at most by one thread at a
+                    // time. Thus no failure in the atomic operation above is
+                    // expected.
+                    throw new InternalError();
+                }
+                swapped = false;
+                break;
+            } else if (lastAutomaticPong.compareAndSet(message, copy)) {
+                swapped = true;
+                break;
+            }
+        }
+        if (debug.on()) {
+            debug.log("swapped automatic pong from %s to %s",
+                      message, copy);
+        }
+        return swapped;
+    }
+
+    private void signalOpen() {
+        debug.log("signalOpen");
+        receiveScheduler.runOrSchedule();
+    }
+
+    private void signalError(Throwable error) {
+        if (debug.on()) {
+            debug.log("signalError %s", (Object) error);
+        }
+        inputClosed = true;
+        outputClosed.set(true);
+        if (!this.error.compareAndSet(null, error) || !trySetState(ERROR)) {
+            if (debug.on()) {
+                debug.log("signalError", error);
+            }
+            Log.logError(error);
+        } else {
+            close();
+        }
+    }
+
+    private void close() {
+        if (debug.on()) {
+            debug.log("close");
+        }
+        Throwable first = null;
+        try {
+            transport.closeInput();
+        } catch (Throwable t1) {
+            first = t1;
+        } finally {
+            Throwable second = null;
+            try {
+                transport.closeOutput();
+            } catch (Throwable t2) {
+                second = t2;
+            } finally {
+                Throwable e = null;
+                if (first != null && second != null) {
+                    first.addSuppressed(second);
+                    e = first;
+                } else if (first != null) {
+                    e = first;
+                } else if (second != null) {
+                    e = second;
+                }
+                if (e != null) {
+                    if (debug.on()) {
+                        debug.log("exception in close", e);
+                    }
+                }
+            }
+        }
+    }
+
+    private void signalClose(int statusCode, String reason) {
+        // FIXME: make sure no race reason & close are not intermixed
+        inputClosed = true;
+        this.statusCode = statusCode;
+        this.reason = reason;
+        boolean managed = trySetState(CLOSE);
+        if (debug.on()) {
+            debug.log("signalClose statusCode=%s reason.length=%s: %s",
+                      statusCode, reason.length(), managed);
+        }
+        if (managed) {
+            try {
+                transport.closeInput();
+            } catch (Throwable t) {
+                if (debug.on()) {
+                    debug.log("exception closing input", (Object) t);
+                }
+            }
+        }
+    }
+
+    private class SignallingMessageConsumer implements MessageStreamConsumer {
+
+        @Override
+        public void onText(CharSequence data, boolean last) {
+            transport.acknowledgeReception();
+            text = data;
+            WebSocketImpl.this.last = last;
+            tryChangeState(WAITING, TEXT);
+        }
+
+        @Override
+        public void onBinary(ByteBuffer data, boolean last) {
+            transport.acknowledgeReception();
+            binaryData = data;
+            WebSocketImpl.this.last = last;
+            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) {
+        State currentState;
+        boolean success = false;
+        while (true) {
+            currentState = state.get();
+            if (currentState == ERROR || currentState == CLOSE) {
+                break;
+            } else if (state.compareAndSet(currentState, newState)) {
+                receiveScheduler.runOrSchedule();
+                success = true;
+                break;
+            }
+        }
+        if (debug.on()) {
+            debug.log("set state %s (previous %s) %s",
+                      newState, currentState, success);
+        }
+        return success;
+    }
+
+    private boolean tryChangeState(State expectedState, State newState) {
+        State witness = state.compareAndExchange(expectedState, newState);
+        boolean success = false;
+        if (witness == expectedState) {
+            receiveScheduler.runOrSchedule();
+            success = true;
+        } else if (witness != ERROR && witness != CLOSE) {
+            // This should be the only reason for inability to change the state
+            // from IDLE to WAITING: the state has changed to terminal
+            throw new InternalError();
+        }
+        if (debug.on()) {
+            debug.log("change state from %s to %s %s",
+                      expectedState, newState, success);
+        }
+        return success;
+    }
+
+    /* Exposed for testing purposes */
+    protected Transport 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	Tue Apr 17 08:54:17 2018 -0700
@@ -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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/module-info.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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.
+ */
+
+/**
+ * Defines the HTTP Client and WebSocket APIs.
+ *
+ * @moduleGraph
+ * @since 11
+ */
+module java.net.http {
+    exports java.net.http;
+}
--- a/src/java.se/share/classes/module-info.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/src/java.se/share/classes/module-info.java	Tue Apr 17 08:54:17 2018 -0700
@@ -48,6 +48,7 @@
     requires transitive java.management;
     requires transitive java.management.rmi;
     requires transitive java.naming;
+    requires transitive java.net.http;
     requires transitive java.prefs;
     requires transitive java.rmi;
     requires transitive java.scripting;
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java	Tue Apr 17 08:54:17 2018 -0700
@@ -528,7 +528,7 @@
                     if (https) {
                         if (sslContext == null) {
                             logger.log (Level.WARNING,
-                                "SSL connection received. No https contxt created");
+                                "SSL connection received. No https context created");
                             throw new HttpError ("No SSL context established");
                         }
                         sslStreams = new SSLStreams (ServerImpl.this, sslContext, chan);
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractAsyncSSLConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import javax.net.ssl.SNIHostName;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLParameters;
-
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-
-
-/**
- * Asynchronous version of SSLConnection.
- *
- * There are two concrete implementations of this class: AsyncSSLConnection
- * and AsyncSSLTunnelConnection.
- * This abstraction is useful when downgrading from HTTP/2 to HTTP/1.1 over
- * an SSL connection. See ExchangeImpl::get in the case where an ALPNException
- * is thrown.
- *
- * Note: An AsyncSSLConnection wraps a PlainHttpConnection, while an
- *       AsyncSSLTunnelConnection wraps a PlainTunnelingConnection.
- *       If both these wrapped classes where made to inherit from a
- *       common abstraction then it might be possible to merge
- *       AsyncSSLConnection and AsyncSSLTunnelConnection back into
- *       a single class - and simply use different factory methods to
- *       create different wrappees, but this is left up for further cleanup.
- *
- */
-abstract class AbstractAsyncSSLConnection extends HttpConnection
-{
-    protected final SSLEngine engine;
-    protected final String serverName;
-    protected final SSLParameters sslParameters;
-
-    AbstractAsyncSSLConnection(InetSocketAddress addr,
-                               HttpClientImpl client,
-                               String serverName,
-                               String[] alpn) {
-        super(addr, client);
-        this.serverName = serverName;
-        SSLContext context = client.theSSLContext();
-        sslParameters = createSSLParameters(client, serverName, alpn);
-        Log.logParams(sslParameters);
-        engine = createEngine(context, sslParameters);
-    }
-
-    abstract HttpConnection plainConnection();
-    abstract SSLTube getConnectionFlow();
-
-    final CompletableFuture<String> getALPN() {
-        assert connected();
-        return getConnectionFlow().getALPN();
-    }
-
-    final SSLEngine getEngine() { return engine; }
-
-    private static SSLParameters createSSLParameters(HttpClientImpl client,
-                                                     String serverName,
-                                                     String[] alpn) {
-        SSLParameters sslp = client.sslParameters();
-        SSLParameters sslParameters = Utils.copySSLParameters(sslp);
-        if (alpn != null) {
-            Log.logSSL("AbstractAsyncSSLConnection: Setting application protocols: {0}",
-                       Arrays.toString(alpn));
-            sslParameters.setApplicationProtocols(alpn);
-        } else {
-            Log.logSSL("AbstractAsyncSSLConnection: no applications set!");
-        }
-        if (serverName != null) {
-            sslParameters.setServerNames(List.of(new SNIHostName(serverName)));
-        }
-        return sslParameters;
-    }
-
-    private static SSLEngine createEngine(SSLContext context,
-                                          SSLParameters sslParameters) {
-        SSLEngine engine = context.createSSLEngine();
-        engine.setUseClientMode(true);
-        engine.setSSLParameters(sslParameters);
-        return engine;
-    }
-
-    @Override
-    final boolean isSecure() {
-        return true;
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    static final class SSLConnectionChannel extends DetachedConnectionChannel {
-        final DetachedConnectionChannel delegate;
-        final SSLDelegate sslDelegate;
-        SSLConnectionChannel(DetachedConnectionChannel delegate, SSLDelegate sslDelegate) {
-            this.delegate = delegate;
-            this.sslDelegate = sslDelegate;
-        }
-
-        SocketChannel channel() {
-            return delegate.channel();
-        }
-
-        @Override
-        ByteBuffer read() throws IOException {
-            SSLDelegate.WrapperResult r = sslDelegate.recvData(ByteBuffer.allocate(8192));
-            // TODO: check for closure
-            int n = r.result.bytesProduced();
-            if (n > 0) {
-                return r.buf;
-            } else if (n == 0) {
-                return Utils.EMPTY_BYTEBUFFER;
-            } else {
-                return null;
-            }
-        }
-        @Override
-        long write(ByteBuffer[] buffers, int start, int number) throws IOException {
-            long l = SSLDelegate.countBytes(buffers, start, number);
-            SSLDelegate.WrapperResult r = sslDelegate.sendData(buffers, start, number);
-            if (r.result.getStatus() == SSLEngineResult.Status.CLOSED) {
-                if (l > 0) {
-                    throw new IOException("SSLHttpConnection closed");
-                }
-            }
-            return l;
-        }
-        @Override
-        public void shutdownInput() throws IOException {
-            delegate.shutdownInput();
-        }
-        @Override
-        public void shutdownOutput() throws IOException {
-            delegate.shutdownOutput();
-        }
-        @Override
-        public void close() {
-            delegate.close();
-        }
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    @Override
-    DetachedConnectionChannel detachChannel() {
-        assert client() != null;
-        DetachedConnectionChannel detachedChannel = plainConnection().detachChannel();
-        SSLDelegate sslDelegate = new SSLDelegate(engine,
-                                                  detachedChannel.channel());
-        return new SSLConnectionChannel(detachedChannel, sslDelegate);
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractSubscription.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.concurrent.Flow;
-import jdk.incubator.http.internal.common.Demand;
-
-/**
- * A {@link Flow.Subscription} wrapping a {@link Demand} instance.
- *
- */
-abstract class AbstractSubscription implements Flow.Subscription {
-
-    private final Demand demand = new Demand();
-
-    /**
-     * Returns the subscription's demand.
-     * @return the subscription's demand.
-     */
-    protected Demand demand() { return demand; }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncEvent.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.nio.channels.SelectableChannel;
-
-/**
- * Event handling interface from HttpClientImpl's selector.
- *
- * If REPEATING is set then the event is not cancelled after being posted.
- */
-abstract class AsyncEvent {
-
-    public static final int REPEATING = 0x2; // one off event if not set
-
-    protected final int flags;
-
-    AsyncEvent() {
-        this(0);
-    }
-
-    AsyncEvent(int flags) {
-        this.flags = flags;
-    }
-
-    /** Returns the channel */
-    public abstract SelectableChannel channel();
-
-    /** Returns the selector interest op flags OR'd */
-    public abstract int interestOps();
-
-    /** Called when event occurs */
-    public abstract void handle();
-
-    /**
-     * Called when an error occurs during registration, or when the selector has
-     * been shut down. Aborts all exchanges.
-     *
-     * @param ioe  the IOException, or null
-     */
-    public abstract void abort(IOException ioe);
-
-    public boolean repeating() {
-        return (flags & REPEATING) != 0;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.http.internal.common.Utils;
-
-
-/**
- * Asynchronous version of SSLConnection.
- */
-class AsyncSSLConnection extends AbstractAsyncSSLConnection {
-
-    final PlainHttpConnection plainConnection;
-    final PlainHttpPublisher writePublisher;
-    private volatile SSLTube flow;
-
-    AsyncSSLConnection(InetSocketAddress addr,
-                       HttpClientImpl client,
-                       String[] alpn) {
-        super(addr, client, Utils.getServerName(addr), alpn);
-        plainConnection = new PlainHttpConnection(addr, client);
-        writePublisher = new PlainHttpPublisher();
-    }
-
-    @Override
-    PlainHttpConnection plainConnection() {
-        return plainConnection;
-    }
-
-    @Override
-    public CompletableFuture<Void> connectAsync() {
-        return plainConnection
-                .connectAsync()
-                .thenApply( unused -> {
-                    // create the SSLTube wrapping the SocketTube, with the given engine
-                    flow = new SSLTube(engine,
-                                       client().theExecutor(),
-                                       plainConnection.getConnectionFlow());
-                    return null; } );
-    }
-
-    @Override
-    boolean connected() {
-        return plainConnection.connected();
-    }
-
-    @Override
-    HttpPublisher publisher() { return writePublisher; }
-
-    @Override
-    boolean isProxied() {
-        return false;
-    }
-
-    @Override
-    SocketChannel channel() {
-        return plainConnection.channel();
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return ConnectionPool.cacheKey(address, null);
-    }
-
-    @Override
-    public void close() {
-        plainConnection.close();
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        debug.log(Level.DEBUG, "plainConnection.channel().shutdownInput()");
-        plainConnection.channel().shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        debug.log(Level.DEBUG, "plainConnection.channel().shutdownOutput()");
-        plainConnection.channel().shutdownOutput();
-    }
-
-   @Override
-   SSLTube getConnectionFlow() {
-       return flow;
-   }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLTunnelConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * 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)
-    {
-        super(addr, client, Utils.getServerName(addr), alpn);
-        this.plainConnection = new PlainTunnelingConnection(addr, proxy, client);
-        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 connected() {
-        return plainConnection.connected(); // && sslDelegate.connected();
-    }
-
-    @Override
-    HttpPublisher publisher() { return writePublisher; }
-
-    @Override
-    public String toString() {
-        return "AsyncSSLTunnelConnection: " + super.toString();
-    }
-
-    @Override
-    PlainTunnelingConnection plainConnection() {
-        return plainConnection;
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return ConnectionPool.cacheKey(address, plainConnection.proxyAddr);
-    }
-
-    @Override
-    public void close() {
-        plainConnection.close();
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        plainConnection.channel().shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        plainConnection.channel().shutdownOutput();
-    }
-
-    @Override
-    SocketChannel channel() {
-        return plainConnection.channel();
-    }
-
-    @Override
-    boolean isProxied() {
-        return true;
-    }
-
-    @Override
-    SSLTube getConnectionFlow() {
-       return flow;
-   }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncTriggerEvent.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.nio.channels.SelectableChannel;
-import java.util.Objects;
-import java.util.function.Consumer;
-
-/**
- * An asynchronous event which is triggered only once from the selector manager
- * thread as soon as event registration are handled.
- */
-final class AsyncTriggerEvent extends AsyncEvent{
-
-    private final Runnable trigger;
-    private final Consumer<? super IOException> errorHandler;
-    AsyncTriggerEvent(Consumer<? super IOException> errorHandler,
-                      Runnable trigger) {
-        super(0);
-        this.trigger = Objects.requireNonNull(trigger);
-        this.errorHandler = Objects.requireNonNull(errorHandler);
-    }
-    /** Returns null */
-    @Override
-    public SelectableChannel channel() { return null; }
-    /** Returns 0 */
-    @Override
-    public int interestOps() { return 0; }
-    @Override
-    public void handle() { trigger.run(); }
-    @Override
-    public void abort(IOException ioe) { errorHandler.accept(ioe); }
-    @Override
-    public boolean repeating() { return false; }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AuthenticationFilter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,340 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.PasswordAuthentication;
-import java.net.URI;
-import java.net.InetSocketAddress;
-import java.net.URISyntaxException;
-import java.util.Base64;
-import java.util.LinkedList;
-import java.util.Objects;
-import java.util.WeakHashMap;
-import jdk.incubator.http.internal.common.Utils;
-import static java.net.Authenticator.RequestorType.PROXY;
-import static java.net.Authenticator.RequestorType.SERVER;
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
-
-/**
- * Implementation of Http Basic authentication.
- */
-class AuthenticationFilter implements HeaderFilter {
-    volatile MultiExchange<?,?> exchange;
-    private static final Base64.Encoder encoder = Base64.getEncoder();
-
-    static final int DEFAULT_RETRY_LIMIT = 3;
-
-    static final int retry_limit = Utils.getIntegerNetProperty(
-            "jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT);
-
-    static final int UNAUTHORIZED = 401;
-    static final int PROXY_UNAUTHORIZED = 407;
-
-    // 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;
-
-        // needs to be instance method in Authenticator
-        return auth.requestPasswordAuthenticationInstance(uri.getHost(),
-                                                          null,
-                                                          uri.getPort(),
-                                                          uri.getScheme(),
-                                                          realm,
-                                                          authscheme,
-                                                          uri.toURL(),
-                                                          rtype
-        );
-    }
-
-    private URI getProxyURI(HttpRequestImpl r) {
-        InetSocketAddress proxy = r.proxy();
-        if (proxy == null) {
-            return null;
-        }
-
-        // our own private scheme for proxy URLs
-        // eg. proxy.http://host:port/
-        String scheme = "proxy." + r.uri().getScheme();
-        try {
-            return new URI(scheme,
-                           null,
-                           proxy.getHostString(),
-                           proxy.getPort(),
-                           null,
-                           null,
-                           null);
-        } catch (URISyntaxException e) {
-            throw new InternalError(e);
-        }
-    }
-
-    @Override
-    public void request(HttpRequestImpl r, 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;
-        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
-        }
-
-        AuthInfo au = proxy ? exchange.proxyauth : exchange.serverauth;
-        if (au == null) {
-            PasswordAuthentication pw = getCredentials(authval, proxy, req);
-            if (pw == null) {
-                throw new IOException("No credentials provided");
-            }
-            // No authentication in request. Get credentials from user
-            au = new AuthInfo(false, "Basic", pw);
-            if (proxy) {
-                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);
-            }
-            // try again
-            PasswordAuthentication pw = getCredentials(authval, proxy, req);
-            if (pw == null) {
-                throw new IOException("No credentials provided");
-            }
-            au = au.retryWithCredentials(pw);
-            if (proxy) {
-                exchange.proxyauth = au;
-            } else {
-                exchange.serverauth = au;
-            }
-            addBasicCredentials(req, proxy, au.credentials);
-            au.retries++;
-            return req;
-        }
-    }
-
-    // Use a WeakHashMap to make it possible for the HttpClient to
-    // be garbaged collected when no longer referenced.
-    static final WeakHashMap<HttpClientImpl,Cache> caches = new WeakHashMap<>();
-
-    static synchronized Cache getCache(MultiExchange<?,?> exchange) {
-        HttpClientImpl client = exchange.client();
-        Cache c = caches.get(client);
-        if (c == null) {
-            c = new Cache();
-            caches.put(client, c);
-        }
-        return c;
-    }
-
-    // Note: Make sure that Cache and CacheEntry do not keep any strong
-    //       reference to the HttpClient: it would prevent the client being
-    //       GC'ed when no longer referenced.
-    static class Cache {
-        final LinkedList<CacheEntry> entries = new LinkedList<>();
-
-        synchronized CacheEntry get(URI uri, boolean proxy) {
-            for (CacheEntry entry : entries) {
-                if (entry.equalsKey(uri, proxy)) {
-                    return entry;
-                }
-            }
-            return null;
-        }
-
-        synchronized void remove(String authscheme, URI domain, boolean proxy) {
-            for (CacheEntry entry : entries) {
-                if (entry.equalsKey(domain, proxy)) {
-                    entries.remove(entry);
-                }
-            }
-        }
-
-        synchronized void remove(CacheEntry entry) {
-            entries.remove(entry);
-        }
-
-        synchronized void store(String authscheme,
-                                URI domain,
-                                boolean proxy,
-                                PasswordAuthentication value) {
-            remove(authscheme, domain, proxy);
-            entries.add(new CacheEntry(authscheme, domain, proxy, value));
-        }
-    }
-
-    static class CacheEntry {
-        final String root;
-        final String scheme;
-        final boolean proxy;
-        final PasswordAuthentication value;
-
-        CacheEntry(String authscheme,
-                   URI uri,
-                   boolean proxy,
-                   PasswordAuthentication value) {
-            this.scheme = authscheme;
-            this.root = uri.resolve(".").toString(); // remove extraneous components
-            this.proxy = proxy;
-            this.value = value;
-        }
-
-        public PasswordAuthentication value() {
-            return value;
-        }
-
-        public boolean equalsKey(URI uri, boolean proxy) {
-            if (this.proxy != proxy) {
-                return false;
-            }
-            String other = uri.toString();
-            return other.startsWith(root);
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/BufferingSubscriber.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.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 jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.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.
- */
-class BufferingSubscriber<T> implements HttpResponse.BodySubscriber<T>
-{
-    /** The downstream consumer of the data. */
-    private final HttpResponse.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;
-
-    BufferingSubscriber(HttpResponse.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) {
-            accumulatedBytes += Utils.accumulateBuffers(internalBuffers, 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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ConnectionPool.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,490 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.Flow;
-import java.util.stream.Collectors;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Http 1.1 connection pool.
- */
-final class ConnectionPool {
-
-    static final long KEEP_ALIVE = Utils.getIntegerNetProperty(
-            "jdk.httpclient.keepalive.timeout", 1200); // seconds
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    // Pools of idle connections
-
-    private final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool;
-    private final HashMap<CacheKey,LinkedList<HttpConnection>> sslPool;
-    private final ExpiryList expiryList;
-    private final String dbgTag; // used for debug
-    boolean stopped;
-
-    /**
-     * Entries in connection pool are keyed by destination address and/or
-     * proxy address:
-     * case 1: plain TCP not via proxy (destination only)
-     * case 2: plain TCP via proxy (proxy only)
-     * case 3: SSL not via proxy (destination only)
-     * case 4: SSL over tunnel (destination and proxy)
-     */
-    static class CacheKey {
-        final InetSocketAddress proxy;
-        final InetSocketAddress destination;
-
-        CacheKey(InetSocketAddress destination, InetSocketAddress proxy) {
-            this.proxy = proxy;
-            this.destination = destination;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == null) {
-                return false;
-            }
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final CacheKey other = (CacheKey) obj;
-            if (!Objects.equals(this.proxy, other.proxy)) {
-                return false;
-            }
-            if (!Objects.equals(this.destination, other.destination)) {
-                return false;
-            }
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(proxy, destination);
-        }
-    }
-
-    ConnectionPool(long clientId) {
-        this("ConnectionPool("+clientId+")");
-    }
-
-    /**
-     * There should be one of these per HttpClient.
-     */
-    private ConnectionPool(String tag) {
-        dbgTag = tag;
-        plainPool = new HashMap<>();
-        sslPool = new HashMap<>();
-        expiryList = new ExpiryList();
-    }
-
-    final String dbgString() {
-        return dbgTag;
-    }
-
-    void start() {
-        assert !stopped : "Already stopped";
-    }
-
-    static CacheKey cacheKey(InetSocketAddress destination,
-                             InetSocketAddress proxy)
-    {
-        return new CacheKey(destination, proxy);
-    }
-
-    synchronized HttpConnection getConnection(boolean secure,
-                                              InetSocketAddress addr,
-                                              InetSocketAddress proxy) {
-        if (stopped) return null;
-        CacheKey key = new CacheKey(addr, proxy);
-        HttpConnection c = secure ? findConnection(key, sslPool)
-                                  : findConnection(key, plainPool);
-        //System.out.println ("getConnection returning: " + c);
-        return c;
-    }
-
-    /**
-     * Returns the connection to the pool.
-     */
-    void returnToPool(HttpConnection conn) {
-        returnToPool(conn, Instant.now(), KEEP_ALIVE);
-    }
-
-    // Called also by whitebox tests
-    void returnToPool(HttpConnection conn, Instant now, long keepAlive) {
-
-        // Don't call registerCleanupTrigger while holding a lock,
-        // but register it before the connection is added to the pool,
-        // since we don't want to trigger the cleanup if the connection
-        // is not in the pool.
-        CleanupTrigger cleanup = registerCleanupTrigger(conn);
-
-        // it's possible that cleanup may have been called.
-        synchronized(this) {
-            if (cleanup.isDone()) {
-                return;
-            } else if (stopped) {
-                conn.close();
-                return;
-            }
-            if (conn instanceof PlainHttpConnection) {
-                putConnection(conn, plainPool);
-            } else {
-                assert conn.isSecure();
-                putConnection(conn, sslPool);
-            }
-            expiryList.add(conn, now, keepAlive);
-        }
-        //System.out.println("Return to pool: " + conn);
-    }
-
-    private CleanupTrigger registerCleanupTrigger(HttpConnection conn) {
-        // Connect the connection flow to a pub/sub pair that will take the
-        // connection out of the pool and close it if anything happens
-        // while the connection is sitting in the pool.
-        CleanupTrigger cleanup = new CleanupTrigger(conn);
-        FlowTube flow = conn.getConnectionFlow();
-        debug.log(Level.DEBUG, "registering %s", cleanup);
-        flow.connectFlows(cleanup, cleanup);
-        return cleanup;
-    }
-
-    private HttpConnection
-    findConnection(CacheKey key,
-                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
-        LinkedList<HttpConnection> l = pool.get(key);
-        if (l == null || l.isEmpty()) {
-            return null;
-        } else {
-            HttpConnection c = l.removeFirst();
-            expiryList.remove(c);
-            return c;
-        }
-    }
-
-    /* called from cache cleaner only  */
-    private boolean
-    removeFromPool(HttpConnection c,
-                   HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
-        //System.out.println("cacheCleaner removing: " + c);
-        assert Thread.holdsLock(this);
-        CacheKey k = c.cacheKey();
-        List<HttpConnection> l = pool.get(k);
-        if (l == null || l.isEmpty()) {
-            pool.remove(k);
-            return false;
-        }
-        return l.remove(c);
-    }
-
-    private void
-    putConnection(HttpConnection c,
-                  HashMap<CacheKey,LinkedList<HttpConnection>> pool) {
-        CacheKey key = c.cacheKey();
-        LinkedList<HttpConnection> l = pool.get(key);
-        if (l == null) {
-            l = new LinkedList<>();
-            pool.put(key, l);
-        }
-        l.add(c);
-    }
-
-    /**
-     * Purge expired connection and return the number of milliseconds
-     * in which the next connection is scheduled to expire.
-     * If no connections are scheduled to be purged return 0.
-     * @return the delay in milliseconds in which the next connection will
-     *         expire.
-     */
-    long purgeExpiredConnectionsAndReturnNextDeadline() {
-        if (!expiryList.purgeMaybeRequired()) return 0;
-        return purgeExpiredConnectionsAndReturnNextDeadline(Instant.now());
-    }
-
-    // Used for whitebox testing
-    long purgeExpiredConnectionsAndReturnNextDeadline(Instant now) {
-        long nextPurge = 0;
-
-        // We may be in the process of adding new elements
-        // to the expiry list - but those elements will not
-        // have outlast their keep alive timer yet since we're
-        // just adding them.
-        if (!expiryList.purgeMaybeRequired()) return nextPurge;
-
-        List<HttpConnection> closelist;
-        synchronized (this) {
-            closelist = expiryList.purgeUntil(now);
-            for (HttpConnection c : closelist) {
-                if (c instanceof PlainHttpConnection) {
-                    boolean wasPresent = removeFromPool(c, plainPool);
-                    assert wasPresent;
-                } else {
-                    boolean wasPresent = removeFromPool(c, sslPool);
-                    assert wasPresent;
-                }
-            }
-            nextPurge = now.until(
-                    expiryList.nextExpiryDeadline().orElse(now),
-                    ChronoUnit.MILLIS);
-        }
-        closelist.forEach(this::close);
-        return nextPurge;
-    }
-
-    private void close(HttpConnection c) {
-        try {
-            c.close();
-        } catch (Throwable e) {} // ignore
-    }
-
-    void stop() {
-        List<HttpConnection> closelist = Collections.emptyList();
-        try {
-            synchronized (this) {
-                stopped = true;
-                closelist = expiryList.stream()
-                    .map(e -> e.connection)
-                    .collect(Collectors.toList());
-                expiryList.clear();
-                plainPool.clear();
-                sslPool.clear();
-            }
-        } finally {
-            closelist.forEach(this::close);
-        }
-    }
-
-    static final class ExpiryEntry {
-        final HttpConnection connection;
-        final Instant expiry; // absolute time in seconds of expiry time
-        ExpiryEntry(HttpConnection connection, Instant expiry) {
-            this.connection = connection;
-            this.expiry = expiry;
-        }
-    }
-
-    /**
-     * Manages a LinkedList of sorted ExpiryEntry. The entry with the closer
-     * deadline is at the tail of the list, and the entry with the farther
-     * deadline is at the head. In the most common situation, new elements
-     * will need to be added at the head (or close to it), and expired elements
-     * will need to be purged from the tail.
-     */
-    private static final class ExpiryList {
-        private final LinkedList<ExpiryEntry> list = new LinkedList<>();
-        private volatile boolean mayContainEntries;
-
-        // A loosely accurate boolean whose value is computed
-        // at the end of each operation performed on ExpiryList;
-        // Does not require synchronizing on the ConnectionPool.
-        boolean purgeMaybeRequired() {
-            return mayContainEntries;
-        }
-
-        // Returns the next expiry deadline
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        Optional<Instant> nextExpiryDeadline() {
-            if (list.isEmpty()) return Optional.empty();
-            else return Optional.of(list.getLast().expiry);
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        void add(HttpConnection conn) {
-            add(conn, Instant.now(), KEEP_ALIVE);
-        }
-
-        // Used by whitebox test.
-        void add(HttpConnection conn, Instant now, long keepAlive) {
-            Instant then = now.truncatedTo(ChronoUnit.SECONDS)
-                    .plus(keepAlive, ChronoUnit.SECONDS);
-
-            // Elements with the farther deadline are at the head of
-            // the list. It's more likely that the new element will
-            // have the farthest deadline, and will need to be inserted
-            // at the head of the list, so we're using an ascending
-            // list iterator to find the right insertion point.
-            ListIterator<ExpiryEntry> li = list.listIterator();
-            while (li.hasNext()) {
-                ExpiryEntry entry = li.next();
-
-                if (then.isAfter(entry.expiry)) {
-                    li.previous();
-                    // insert here
-                    li.add(new ExpiryEntry(conn, then));
-                    mayContainEntries = true;
-                    return;
-                }
-            }
-            // last (or first) element of list (the last element is
-            // the first when the list is empty)
-            list.add(new ExpiryEntry(conn, then));
-            mayContainEntries = true;
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        void remove(HttpConnection c) {
-            if (c == null || list.isEmpty()) return;
-            ListIterator<ExpiryEntry> li = list.listIterator();
-            while (li.hasNext()) {
-                ExpiryEntry e = li.next();
-                if (e.connection.equals(c)) {
-                    li.remove();
-                    mayContainEntries = !list.isEmpty();
-                    return;
-                }
-            }
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool.
-        // Purge all elements whose deadline is before now (now included).
-        List<HttpConnection> purgeUntil(Instant now) {
-            if (list.isEmpty()) return Collections.emptyList();
-
-            List<HttpConnection> closelist = new ArrayList<>();
-
-            // elements with the closest deadlines are at the tail
-            // of the queue, so we're going to use a descending iterator
-            // to remove them, and stop when we find the first element
-            // that has not expired yet.
-            Iterator<ExpiryEntry> li = list.descendingIterator();
-            while (li.hasNext()) {
-                ExpiryEntry entry = li.next();
-                // use !isAfter instead of isBefore in order to
-                // remove the entry if its expiry == now
-                if (!entry.expiry.isAfter(now)) {
-                    li.remove();
-                    HttpConnection c = entry.connection;
-                    closelist.add(c);
-                } else break; // the list is sorted
-            }
-            mayContainEntries = !list.isEmpty();
-            return closelist;
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        java.util.stream.Stream<ExpiryEntry> stream() {
-            return list.stream();
-        }
-
-        // should only be called while holding a synchronization
-        // lock on the ConnectionPool
-        void clear() {
-            list.clear();
-            mayContainEntries = false;
-        }
-    }
-
-    void cleanup(HttpConnection c, Throwable error) {
-        debug.log(Level.DEBUG,
-                  "%s : ConnectionPool.cleanup(%s)",
-                  String.valueOf(c.getConnectionFlow()),
-                  error);
-        synchronized(this) {
-            if (c instanceof PlainHttpConnection) {
-                removeFromPool(c, plainPool);
-            } else {
-                assert c.isSecure();
-                removeFromPool(c, sslPool);
-            }
-            expiryList.remove(c);
-        }
-        c.close();
-    }
-
-    /**
-     * An object that subscribes to the flow while the connection is in
-     * the pool. Anything that comes in will cause the connection to be closed
-     * and removed from the pool.
-     */
-    private final class CleanupTrigger implements
-            FlowTube.TubeSubscriber, FlowTube.TubePublisher,
-            Flow.Subscription {
-
-        private final HttpConnection connection;
-        private volatile boolean done;
-
-        public CleanupTrigger(HttpConnection connection) {
-            this.connection = connection;
-        }
-
-        public boolean isDone() { return done;}
-
-        private void triggerCleanup(Throwable error) {
-            done = true;
-            cleanup(connection, error);
-        }
-
-        @Override public void request(long n) {}
-        @Override public void cancel() {}
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            subscription.request(1);
-        }
-        @Override
-        public void onError(Throwable error) { triggerCleanup(error); }
-        @Override
-        public void onComplete() { triggerCleanup(null); }
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            triggerCleanup(new IOException("Data received while in pool"));
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            subscriber.onSubscribe(this);
-        }
-
-        @Override
-        public String toString() {
-            return "CleanupTrigger(" + connection.getConnectionFlow() + ")";
-        }
-
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/CookieFilter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.CookieHandler;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.common.Log;
-
-class CookieFilter implements HeaderFilter {
-
-    public CookieFilter() {
-    }
-
-    @Override
-    public void request(HttpRequestImpl r, MultiExchange<?,?> e) throws IOException {
-        HttpClientImpl client = e.client();
-        Optional<CookieHandler> cookieHandlerOpt = client.cookieHandler();
-        if (cookieHandlerOpt.isPresent()) {
-            CookieHandler cookieHandler = cookieHandlerOpt.get();
-            Map<String,List<String>> userheaders = r.getUserHeaders().map();
-            Map<String,List<String>> cookies = cookieHandler.get(r.uri(), userheaders);
-
-            // add the returned cookies
-            HttpHeadersImpl systemHeaders = r.getSystemHeaders();
-            if (cookies.isEmpty()) {
-                Log.logTrace("Request: no cookie to add for {0}",
-                             r.uri());
-            } else {
-                Log.logTrace("Request: adding cookies for {0}",
-                             r.uri());
-            }
-            for (String hdrname : cookies.keySet()) {
-                List<String> vals = cookies.get(hdrname);
-                for (String val : vals) {
-                    systemHeaders.addHeader(hdrname, val);
-                }
-            }
-        } else {
-            Log.logTrace("Request: No cookie manager found for {0}",
-                         r.uri());
-        }
-    }
-
-    @Override
-    public HttpRequestImpl response(Response r) throws IOException {
-        HttpHeaders hdrs = r.headers();
-        HttpRequestImpl request = r.request();
-        Exchange<?> e = r.exchange;
-        Log.logTrace("Response: processing cookies for {0}", request.uri());
-        Optional<CookieHandler> cookieHandlerOpt = e.client().cookieHandler();
-        if (cookieHandlerOpt.isPresent()) {
-            CookieHandler cookieHandler = cookieHandlerOpt.get();
-            Log.logTrace("Response: parsing cookies from {0}", hdrs.map());
-            cookieHandler.put(request.uri(), hdrs.map());
-        } else {
-            Log.logTrace("Response: No cookie manager found for {0}",
-                         request.uri());
-        }
-        return null;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,509 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.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 jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.common.Log;
-
-import static jdk.incubator.http.internal.common.Utils.permissionForProxy;
-
-/**
- * One request/response exchange (handles 100/101 intermediate response also).
- * depth field used to track number of times a new request is being sent
- * for a given API request. If limit exceeded exception is thrown.
- *
- * Security check is performed here:
- * - uses AccessControlContext captured at API level
- * - checks for appropriate URLPermission for request
- * - if permission allowed, grants equivalent SocketPermission to call
- * - in case of direct HTTP proxy, checks additionally for access to proxy
- *    (CONNECT proxying uses its own Exchange, so check done there)
- *
- */
-final class Exchange<T> {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    final HttpRequestImpl request;
-    final HttpClientImpl client;
-    volatile ExchangeImpl<T> exchImpl;
-    volatile CompletableFuture<? extends ExchangeImpl<T>> exchangeCF;
-    // 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) {
-        // 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() {
-        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);
-        }
-    }
-
-    CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) {
-        if (request.expectContinue()) {
-            request.addSystemHeader("Expect", "100-Continue");
-            Log.logTrace("Sending Expect: 100-Continue");
-            return establishExchange(connection)
-                    .thenCompose((ex) -> ex.sendHeadersAsync())
-                    .thenCompose(v -> exchImpl.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);
-                        }
-                    });
-        } else {
-            CompletableFuture<Response> cf = establishExchange(connection)
-                    .thenCompose((ex) -> ex.sendHeadersAsync())
-                    .thenCompose(ExchangeImpl::sendBodyAsync)
-                    .thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
-            cf = wrapForUpgrade(cf);
-            cf = wrapForLog(cf);
-            return cf;
-        }
-    }
-
-    private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> cf) {
-        if (upgrading) {
-            return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
-        }
-        return cf;
-    }
-
-    private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
-        if (Log.requests()) {
-            return cf.thenApply(response -> {
-                Log.logResponse(response::toString);
-                return response;
-            });
-        }
-        return cf;
-    }
-
-    HttpResponse.BodySubscriber<T> ignoreBody(int status, HttpHeaders hdrs) {
-        return HttpResponse.BodySubscriber.discard((T)null);
-    }
-
-    // if this response was received in reply to an upgrade
-    // then create the Http2Connection from the HttpConnection
-    // initialize it and wait for the real response on a newly created Stream
-
-    private CompletableFuture<Response>
-    checkForUpgradeAsync(Response resp,
-                         ExchangeImpl<T> ex) {
-
-        int rcode = resp.statusCode();
-        if (upgrading && (rcode == 101)) {
-            Http1Exchange<T> e = (Http1Exchange<T>)ex;
-            // check for 101 switching protocols
-            // 101 responses are not supposed to contain a body.
-            //    => should we fail if there is one?
-            debug.log(Level.DEBUG, "Upgrading async %s", e.connection());
-            return e.readBodyAsync(this::ignoreBody, false, parentExecutor)
-                .thenCompose((T v) -> {// v is null
-                    debug.log(Level.DEBUG, "Ignored body");
-                    // we pass e::getBuffer to allow the ByteBuffers to accumulate
-                    // while we build the Http2Connection
-                    return Http2Connection.createAsync(e.connection(),
-                                                 client.client2(),
-                                                 this, e::drainLeftOverBytes)
-                        .thenCompose((Http2Connection c) -> {
-                            boolean cached = c.offerConnection();
-                            Stream<T> s = c.getStream(1);
-
-                            if (s == null) {
-                                // s can be null if an exception occurred
-                                // asynchronously while sending the preface.
-                                Throwable t = c.getRecordedCause();
-                                if (t != null) {
-                                    if (!cached)
-                                        c.close();
-                                    return MinimalFuture.failedFuture(
-                                            new IOException("Can't get stream 1: " + t, t));
-                                }
-                            }
-                            exchImpl.released();
-                            Throwable t;
-                            // There's a race condition window where an external
-                            // thread (SelectorManager) might complete the
-                            // exchange in timeout at the same time where we're
-                            // trying to switch the exchange impl.
-                            // 'failed' will be reset to null after
-                            // exchImpl.cancel() has completed, so either we
-                            // will observe failed != null here, or we will
-                            // observe e.getCancelCause() != null, or the
-                            // timeout exception will be routed to 's'.
-                            // Either way, we need to relay it to s.
-                            synchronized (this) {
-                                exchImpl = s;
-                                t = failed;
-                            }
-                            // Check whether the HTTP/1.1 was cancelled.
-                            if (t == null) t = e.getCancelCause();
-                            // if HTTP/1.1 exchange was timed out, don't
-                            // try to go further.
-                            if (t instanceof HttpTimeoutException) {
-                                 s.cancelImpl(t);
-                                 return MinimalFuture.failedFuture(t);
-                            }
-                            debug.log(Level.DEBUG, "Getting response async %s", s);
-                            return s.getResponseAsync(null);
-                        });}
-                );
-        }
-        return MinimalFuture.completedFuture(resp);
-    }
-
-    private URI getURIForSecurityCheck() {
-        URI u;
-        String method = request.method();
-        InetSocketAddress authority = request.authority();
-        URI uri = request.uri();
-
-        // CONNECT should be restricted at API level
-        if (method.equalsIgnoreCase("CONNECT")) {
-            try {
-                u = new URI("socket",
-                             null,
-                             authority.getHostString(),
-                             authority.getPort(),
-                             null,
-                             null,
-                             null);
-            } catch (URISyntaxException e) {
-                throw new InternalError(e); // shouldn't happen
-            }
-        } else {
-            u = uri;
-        }
-        return u;
-    }
-
-    /**
-     * Returns the security permission required for the given details.
-     * If method is CONNECT, then uri must be of form "scheme://host:port"
-     */
-    private static URLPermission permissionForServer(URI uri,
-                                                     String method,
-                                                     Map<String, List<String>> headers) {
-        if (method.equals("CONNECT")) {
-            return new URLPermission(uri.toString(), "CONNECT");
-        } else {
-            return Utils.permissionForServer(uri, method, headers.keySet().stream());
-        }
-    }
-
-    /**
-     * Performs the necessary security permission checks required to retrieve
-     * the response. Returns a security exception representing the denied
-     * permission, or null if all checks pass or there is no security manager.
-     */
-    private SecurityException checkPermissions() {
-        String method = request.method();
-        SecurityManager sm = System.getSecurityManager();
-        if (sm == null || method.equals("CONNECT")) {
-            // tunneling will have a null acc, which is fine. The proxy
-            // permission check will have already been preformed.
-            return null;
-        }
-
-        HttpHeaders userHeaders = request.getUserHeaders();
-        URI u = getURIForSecurityCheck();
-        URLPermission p = permissionForServer(u, method, userHeaders.map());
-
-        try {
-            assert acc != null;
-            sm.checkPermission(p, acc);
-        } catch (SecurityException e) {
-            return e;
-        }
-        ProxySelector ps = client.proxySelector();
-        if (ps != null) {
-            if (!method.equals("CONNECT")) {
-                // a non-tunneling HTTP proxy. Need to check access
-                URLPermission proxyPerm = permissionForProxy(request.proxy());
-                if (proxyPerm != null) {
-                    try {
-                        sm.checkPermission(proxyPerm, acc);
-                    } catch (SecurityException e) {
-                        return e;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    HttpClient.Version version() {
-        return multi.version();
-    }
-
-    String dbgString() {
-        return dbgTag;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.function.Function;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Splits request so that headers and body can be sent separately with optional
- * (multiple) responses in between (e.g. 100 Continue). Also request and
- * response always sent/received in different calls.
- *
- * Synchronous and asynchronous versions of each method are provided.
- *
- * Separate implementations of this class exist for HTTP/1.1 and HTTP/2
- *      Http1Exchange   (HTTP/1.1)
- *      Stream          (HTTP/2)
- *
- * These implementation classes are where work is allocated to threads.
- */
-abstract class ExchangeImpl<T> {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    private static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("ExchangeImpl"::toString, DEBUG);
-
-    final Exchange<T> exchange;
-
-    ExchangeImpl(Exchange<T> e) {
-        // e == null means a http/2 pushed stream
-        this.exchange = e;
-    }
-
-    final Exchange<T> getExchange() {
-        return exchange;
-    }
-
-
-    /**
-     * Returns the {@link HttpConnection} instance to which this exchange is
-     * assigned.
-     */
-    abstract HttpConnection connection();
-
-    /**
-     * Initiates a new exchange and assigns it to a connection if one exists
-     * already. connection usually null.
-     */
-    static <U> CompletableFuture<? extends ExchangeImpl<U>>
-    get(Exchange<U> exchange, HttpConnection connection)
-    {
-        if (exchange.version() == HTTP_1_1) {
-            DEBUG_LOGGER.log(Level.DEBUG, "get: HTTP/1.1: new Http1Exchange");
-            return createHttp1Exchange(exchange, connection);
-        } else {
-            Http2ClientImpl c2 = exchange.client().client2(); // TODO: improve
-            HttpRequestImpl request = exchange.request();
-            CompletableFuture<Http2Connection> c2f = c2.getConnectionFor(request);
-            DEBUG_LOGGER.log(Level.DEBUG, "get: Trying to get HTTP/2 connection");
-            return c2f.handle((h2c, t) -> createExchangeImpl(h2c, t, exchange, connection))
-                    .thenCompose(Function.identity());
-        }
-    }
-
-    private static <U> CompletableFuture<? extends ExchangeImpl<U>>
-    createExchangeImpl(Http2Connection c,
-                       Throwable t,
-                       Exchange<U> exchange,
-                       HttpConnection connection)
-    {
-        DEBUG_LOGGER.log(Level.DEBUG, "handling HTTP/2 connection creation result");
-        boolean secure = exchange.request().secure();
-        if (t != null) {
-            DEBUG_LOGGER.log(Level.DEBUG,
-                             "handling HTTP/2 connection creation failed: %s",
-                             (Object)t);
-            t = Utils.getCompletionCause(t);
-            if (t instanceof Http2Connection.ALPNException) {
-                Http2Connection.ALPNException ee = (Http2Connection.ALPNException)t;
-                AbstractAsyncSSLConnection as = ee.getConnection();
-                DEBUG_LOGGER.log(Level.DEBUG, "downgrading to HTTP/1.1 with: %s", as);
-                CompletableFuture<? extends ExchangeImpl<U>> ex =
-                        createHttp1Exchange(exchange, as);
-                return ex;
-            } else {
-                DEBUG_LOGGER.log(Level.DEBUG, "HTTP/2 connection creation failed "
-                                  + "with unexpected exception: %s", (Object)t);
-                return CompletableFuture.failedFuture(t);
-            }
-        }
-        if (secure && c== null) {
-            DEBUG_LOGGER.log(Level.DEBUG, "downgrading to HTTP/1.1 ");
-            CompletableFuture<? extends ExchangeImpl<U>> ex =
-                    createHttp1Exchange(exchange, null);
-            return ex;
-        }
-        if (c == null) {
-            // no existing connection. Send request with HTTP 1 and then
-            // upgrade if successful
-            DEBUG_LOGGER.log(Level.DEBUG, "new Http1Exchange, try to upgrade");
-            return createHttp1Exchange(exchange, connection)
-                    .thenApply((e) -> {
-                        exchange.h2Upgrade();
-                        return e;
-                    });
-        } else {
-            DEBUG_LOGGER.log(Level.DEBUG, "creating HTTP/2 streams");
-            Stream<U> s = c.createStream(exchange);
-            CompletableFuture<? extends ExchangeImpl<U>> ex = MinimalFuture.completedFuture(s);
-            return ex;
-        }
-    }
-
-    private static <T> CompletableFuture<Http1Exchange<T>>
-    createHttp1Exchange(Exchange<T> ex, HttpConnection as)
-    {
-        try {
-            return MinimalFuture.completedFuture(new Http1Exchange<>(ex, as));
-        } catch (Throwable e) {
-            return MinimalFuture.failedFuture(e);
-        }
-    }
-
-    /* The following methods have separate HTTP/1.1 and HTTP/2 implementations */
-
-    abstract CompletableFuture<ExchangeImpl<T>> sendHeadersAsync();
-
-    /** Sends a request body, after request headers have been sent. */
-    abstract CompletableFuture<ExchangeImpl<T>> sendBodyAsync();
-
-    abstract CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler,
-                                                boolean returnConnectionToPool,
-                                                Executor executor);
-
-    /**
-     * Ignore/consume the body.
-     */
-    abstract CompletableFuture<Void> ignoreBody();
-
-    /** Gets the response headers. Completes before body is read. */
-    abstract CompletableFuture<Response> getResponseAsync(Executor executor);
-
-
-    /** Cancels a request.  Not currently exposed through API. */
-    abstract void cancel();
-
-    /**
-     * Cancels a request with a cause.  Not currently exposed through API.
-     */
-    abstract void cancel(IOException cause);
-
-    /**
-     * Called when the exchange is released, so that cleanup actions may be
-     * performed - such as deregistering callbacks.
-     * Typically released is called during upgrade, when an HTTP/2 stream
-     * takes over from an Http1Exchange, or when a new exchange is created
-     * during a multi exchange before the final response body was received.
-     */
-    abstract void released();
-
-    /**
-     * Called when the exchange is completed, so that cleanup actions may be
-     * performed - such as deregistering callbacks.
-     * Typically, completed is called at the end of the exchange, when the
-     * final response body has been received (or an error has caused the
-     * completion of the exchange).
-     */
-    abstract void completed();
-
-    /**
-     * Returns true if this exchange was canceled.
-     * @return true if this exchange was canceled.
-     */
-    abstract boolean isCanceled();
-
-    /**
-     * Returns the cause for which this exchange was canceled, if available.
-     * @return the cause for which this exchange was canceled, if available.
-     */
-    abstract Throwable getCancelCause();
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/FilterFactory.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.LinkedList;
-import java.util.List;
-
-class FilterFactory {
-
-    final LinkedList<Class<? extends HeaderFilter>> filterClasses = new LinkedList<>();
-
-    public void addFilter(Class<? extends HeaderFilter> type) {
-        filterClasses.add(type);
-    }
-
-    List<HeaderFilter> getFilterChain() {
-        List<HeaderFilter> l = new LinkedList<>();
-        for (Class<? extends HeaderFilter> clazz : filterClasses) {
-            try {
-                // Requires a public no arg constructor.
-                HeaderFilter headerFilter = clazz.getConstructor().newInstance();
-                l.add(headerFilter);
-            } catch (ReflectiveOperationException e) {
-                throw new InternalError(e);
-            }
-        }
-        return l;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderFilter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-
-/**
- * A header filter that can examine or modify, typically system headers for
- * requests before they are sent, and responses before they are returned to the
- * user. Some ability to resend requests is provided.
- */
-interface HeaderFilter {
-
-    void request(HttpRequestImpl r, MultiExchange<?,?> e) throws IOException;
-
-    /**
-     * Returns null if response ok to be given to user.  Non null is a request
-     * that must be resent and its response given to user. If impl throws an
-     * exception that is returned to user instead.
-     */
-    HttpRequestImpl response(Response r) throws IOException;
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HeaderParser.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.NoSuchElementException;
-
-/* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
- * sensibly:
- * From a String like: 'timeout=15, max=5'
- * create an array of Strings:
- * { {"timeout", "15"},
- *   {"max", "5"}
- * }
- * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
- * create one like (no quotes in literal):
- * { {"basic", null},
- *   {"realm", "FuzzFace"}
- *   {"foo", "Biz Bar Baz"}
- * }
- * keys are converted to lower case, vals are left as is....
- */
-class HeaderParser {
-
-    /* table of key/val pairs */
-    String raw;
-    String[][] tab;
-    int nkeys;
-    int asize = 10; // initial size of array is 10
-
-    public HeaderParser(String raw) {
-        this.raw = raw;
-        tab = new String[asize][2];
-        parse();
-    }
-
-//    private HeaderParser () { }
-
-//    /**
-//     * Creates a new HeaderParser from this, whose keys (and corresponding
-//     * values) range from "start" to "end-1"
-//     */
-//    public HeaderParser subsequence(int start, int end) {
-//        if (start == 0 && end == nkeys) {
-//            return this;
-//        }
-//        if (start < 0 || start >= end || end > nkeys) {
-//            throw new IllegalArgumentException("invalid start or end");
-//        }
-//        HeaderParser n = new HeaderParser();
-//        n.tab = new String [asize][2];
-//        n.asize = asize;
-//        System.arraycopy (tab, start, n.tab, 0, (end-start));
-//        n.nkeys= (end-start);
-//        return n;
-//    }
-
-    private void parse() {
-
-        if (raw != null) {
-            raw = raw.trim();
-            char[] ca = raw.toCharArray();
-            int beg = 0, end = 0, i = 0;
-            boolean inKey = true;
-            boolean inQuote = false;
-            int len = ca.length;
-            while (end < len) {
-                char c = ca[end];
-                if ((c == '=') && !inQuote) { // end of a key
-                    tab[i][0] = new String(ca, beg, end-beg).toLowerCase(Locale.US);
-                    inKey = false;
-                    end++;
-                    beg = end;
-                } else if (c == '\"') {
-                    if (inQuote) {
-                        tab[i++][1]= new String(ca, beg, end-beg);
-                        inQuote=false;
-                        do {
-                            end++;
-                        } while (end < len && (ca[end] == ' ' || ca[end] == ','));
-                        inKey=true;
-                        beg=end;
-                    } else {
-                        inQuote=true;
-                        end++;
-                        beg=end;
-                    }
-                } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
-                    if (inQuote) {
-                        end++;
-                        continue;
-                    } else if (inKey) {
-                        tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase(Locale.US);
-                    } else {
-                        tab[i++][1] = (new String(ca, beg, end-beg));
-                    }
-                    while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
-                        end++;
-                    }
-                    inKey = true;
-                    beg = end;
-                } else {
-                    end++;
-                }
-                if (i == asize) {
-                    asize = asize * 2;
-                    String[][] ntab = new String[asize][2];
-                    System.arraycopy (tab, 0, ntab, 0, tab.length);
-                    tab = ntab;
-                }
-            }
-            // get last key/val, if any
-            if (--end > beg) {
-                if (!inKey) {
-                    if (ca[end] == '\"') {
-                        tab[i++][1] = (new String(ca, beg, end-beg));
-                    } else {
-                        tab[i++][1] = (new String(ca, beg, end-beg+1));
-                    }
-                } else {
-                    tab[i++][0] = (new String(ca, beg, end-beg+1)).toLowerCase();
-                }
-            } else if (end == beg) {
-                if (!inKey) {
-                    if (ca[end] == '\"') {
-                        tab[i++][1] = String.valueOf(ca[end-1]);
-                    } else {
-                        tab[i++][1] = String.valueOf(ca[end]);
-                    }
-                } else {
-                    tab[i++][0] = String.valueOf(ca[end]).toLowerCase();
-                }
-            }
-            nkeys=i;
-        }
-    }
-
-    public String findKey(int i) {
-        if (i < 0 || i > asize) {
-            return null;
-        }
-        return tab[i][0];
-    }
-
-    public String findValue(int i) {
-        if (i < 0 || i > asize) {
-            return null;
-        }
-        return tab[i][1];
-    }
-
-    public String findValue(String key) {
-        return findValue(key, null);
-    }
-
-    public String findValue(String k, String Default) {
-        if (k == null) {
-            return Default;
-        }
-        k = k.toLowerCase(Locale.US);
-        for (int i = 0; i < asize; ++i) {
-            if (tab[i][0] == null) {
-                return Default;
-            } else if (k.equals(tab[i][0])) {
-                return tab[i][1];
-            }
-        }
-        return Default;
-    }
-
-    class ParserIterator implements Iterator<String> {
-        int index;
-        boolean returnsValue; // or key
-
-        ParserIterator (boolean returnValue) {
-            returnsValue = returnValue;
-        }
-        @Override
-        public boolean hasNext () {
-            return index<nkeys;
-        }
-        @Override
-        public String next () {
-            if (index >= nkeys) {
-                throw new NoSuchElementException();
-            }
-            return tab[index++][returnsValue?1:0];
-        }
-    }
-
-    public Iterator<String> keys () {
-        return new ParserIterator (false);
-    }
-
-//    public Iterator<String> values () {
-//        return new ParserIterator (true);
-//    }
-
-    @Override
-    public String toString () {
-        Iterator<String> k = keys();
-        StringBuilder sb = new StringBuilder();
-        sb.append("{size=").append(asize).append(" nkeys=").append(nkeys)
-                .append(' ');
-        for (int i=0; k.hasNext(); i++) {
-            String key = k.next();
-            String val = findValue (i);
-            if (val != null && "".equals (val)) {
-                val = null;
-            }
-            sb.append(" {").append(key).append(val == null ? "" : "," + val)
-                    .append('}');
-            if (k.hasNext()) {
-                sb.append (',');
-            }
-        }
-        sb.append (" }");
-        return sb.toString();
-    }
-
-//    public int findInt(String k, int Default) {
-//        try {
-//            return Integer.parseInt(findValue(k, String.valueOf(Default)));
-//        } catch (Throwable t) {
-//            return Default;
-//        }
-//    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1AsyncReceiver.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,651 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.FlowTube.TubeSubscriber;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.ConnectionExpiredException;
-import jdk.incubator.http.internal.common.Utils;
-
-
-/**
- * A helper class that will queue up incoming data until the receiving
- * side is ready to handle it.
- */
-class Http1AsyncReceiver {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    /**
-     * A delegate that can asynchronously receive data from an upstream flow,
-     * parse, it, then possibly transform it and either store it (response
-     * headers) or possibly pass it to a downstream subscriber (response body).
-     * Usually, there will be one Http1AsyncDelegate in charge of receiving
-     * and parsing headers, and another one in charge of receiving, parsing,
-     * and forwarding body. Each will sequentially subscribe with the
-     * Http1AsyncReceiver in turn. There may be additional delegates which
-     * subscribe to the Http1AsyncReceiver, mainly for the purpose of handling
-     * errors while the connection is busy transmitting the request body and the
-     * Http1Exchange::readBody method hasn't been called yet, and response
-     * delegates haven't subscribed yet.
-     */
-    static interface Http1AsyncDelegate {
-        /**
-         * Receives and handles a byte buffer reference.
-         * @param ref A byte buffer reference coming from upstream.
-         * @return false, if the byte buffer reference should be kept in the queue.
-         *         Usually, this means that either the byte buffer reference
-         *         was handled and parsing is finished, or that the receiver
-         *         didn't handle the byte reference at all.
-         *         There may or may not be any remaining data in the
-         *         byte buffer, and the byte buffer reference must not have
-         *         been cleared.
-         *         true, if the byte buffer reference was fully read and
-         *         more data can be received.
-         */
-        public boolean tryAsyncReceive(ByteBuffer ref);
-
-        /**
-         * Called when an exception is raised.
-         * @param ex The raised Throwable.
-         */
-        public void onReadError(Throwable ex);
-
-        /**
-         * Must be called before any other method on the delegate.
-         * The subscription can be either used directly by the delegate
-         * to request more data (e.g. if the delegate is a header parser),
-         * or can be forwarded to a downstream subscriber (if the delegate
-         * is a body parser that wraps a response BodySubscriber).
-         * In all cases, it is the responsibility of the delegate to ensure
-         * that request(n) and demand.tryDecrement() are called appropriately.
-         * No data will be sent to {@code tryAsyncReceive} unless
-         * the subscription has some demand.
-         *
-         * @param s A subscription that allows the delegate to control the
-         *          data flow.
-         */
-        public void onSubscribe(AbstractSubscription s);
-
-        /**
-         * Returns the subscription that was passed to {@code onSubscribe}
-         * @return the subscription that was passed to {@code onSubscribe}..
-         */
-        public AbstractSubscription subscription();
-
-    }
-
-    /**
-     * A simple subclass of AbstractSubscription that ensures the
-     * SequentialScheduler will be run when request() is called and demand
-     * becomes positive again.
-     */
-    private static final class Http1AsyncDelegateSubscription
-            extends AbstractSubscription
-    {
-        private final Runnable onCancel;
-        private final SequentialScheduler scheduler;
-        Http1AsyncDelegateSubscription(SequentialScheduler scheduler,
-                                       Runnable onCancel) {
-            this.scheduler = scheduler;
-            this.onCancel = onCancel;
-        }
-        @Override
-        public void request(long n) {
-            final Demand demand = demand();
-            if (demand.increase(n)) {
-                scheduler.runOrSchedule();
-            }
-        }
-        @Override
-        public void cancel() { onCancel.run();}
-    }
-
-    private final ConcurrentLinkedDeque<ByteBuffer> queue
-            = new ConcurrentLinkedDeque<>();
-    private final SequentialScheduler scheduler =
-            SequentialScheduler.synchronizedScheduler(this::flush);
-    private final Executor executor;
-    private final Http1TubeSubscriber subscriber = new Http1TubeSubscriber();
-    private final AtomicReference<Http1AsyncDelegate> pendingDelegateRef;
-    private final AtomicLong received = new AtomicLong();
-    final AtomicBoolean canRequestMore = new AtomicBoolean();
-
-    private volatile Throwable error;
-    private volatile Http1AsyncDelegate delegate;
-    // This reference is only used to prevent early GC of the exchange.
-    private volatile Http1Exchange<?>  owner;
-    // Only used for checking whether we run on the selector manager thread.
-    private final HttpClientImpl client;
-    private boolean retry;
-
-    public Http1AsyncReceiver(Executor executor, Http1Exchange<?> owner) {
-        this.pendingDelegateRef = new AtomicReference<>();
-        this.executor = executor;
-        this.owner = owner;
-        this.client = owner.client;
-    }
-
-    // This is the main loop called by the SequentialScheduler.
-    // It attempts to empty the queue until the scheduler is stopped,
-    // or the delegate is unregistered, or the delegate is unable to
-    // process the data (because it's not ready or already done), which
-    // it signals by returning 'true';
-    private void flush() {
-        ByteBuffer buf;
-        try {
-            assert !client.isSelectorThread() :
-                    "Http1AsyncReceiver::flush should not run in the selector: "
-                    + Thread.currentThread().getName();
-
-            // First check whether we have a pending delegate that has
-            // just subscribed, and if so, create a Subscription for it
-            // and call onSubscribe.
-            handlePendingDelegate();
-
-            // Then start emptying the queue, if possible.
-            while ((buf = queue.peek()) != null) {
-                Http1AsyncDelegate delegate = this.delegate;
-                debug.log(Level.DEBUG, "Got %s bytes for delegate %s",
-                                       buf.remaining(), delegate);
-                if (!hasDemand(delegate)) {
-                    // The scheduler will be invoked again later when the demand
-                    // becomes positive.
-                    return;
-                }
-
-                assert delegate != null;
-                debug.log(Level.DEBUG, "Forwarding %s bytes to delegate %s",
-                          buf.remaining(), delegate);
-                // The delegate has demand: feed it the next buffer.
-                if (!delegate.tryAsyncReceive(buf)) {
-                    final long remaining = buf.remaining();
-                    debug.log(Level.DEBUG, () -> {
-                        // If the scheduler is stopped, the queue may already
-                        // be empty and the reference may already be released.
-                        String remstr = scheduler.isStopped() ? "" :
-                                " remaining in ref: "
-                                + remaining;
-                        remstr =  remstr
-                                + " total remaining: " + remaining();
-                        return "Delegate done: " + remaining;
-                    });
-                    canRequestMore.set(false);
-                    // The last buffer parsed may have remaining unparsed bytes.
-                    // Don't take it out of the queue.
-                    return; // done.
-                }
-
-                // removed parsed buffer from queue, and continue with next
-                // if available
-                ByteBuffer parsed = queue.remove();
-                canRequestMore.set(queue.isEmpty());
-                assert parsed == buf;
-            }
-
-            // queue is empty: let's see if we should request more
-            checkRequestMore();
-
-        } catch (Throwable t) {
-            Throwable x = error;
-            if (x == null) error = t; // will be handled in the finally block
-            debug.log(Level.DEBUG, "Unexpected error caught in flush()", t);
-        } finally {
-            // Handles any pending error.
-            // The most recently subscribed delegate will get the error.
-            checkForErrors();
-        }
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Handles any pending errors by calling delegate.onReadError().
-     * If the error can be forwarded to the delegate, stops the scheduler.
-     */
-    private void checkForErrors() {
-        // Handles any pending error.
-        // The most recently subscribed delegate will get the error.
-        // If the delegate is null, the error will be handled by the next
-        // delegate that subscribes.
-        // If the queue is not empty, wait until it it is empty before
-        // handling the error.
-        Http1AsyncDelegate delegate = pendingDelegateRef.get();
-        if (delegate == null) delegate = this.delegate;
-        Throwable x = error;
-        if (delegate != null && x != null && queue.isEmpty()) {
-            // forward error only after emptying the queue.
-            final Object captured = delegate;
-            debug.log(Level.DEBUG, () -> "flushing " + x
-                    + "\n\t delegate: " + captured
-                    + "\t\t queue.isEmpty: " + queue.isEmpty());
-            scheduler.stop();
-            delegate.onReadError(x);
-        }
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Figure out whether more data should be requested from the
-     * Http1TubeSubscriber.
-     */
-    private void checkRequestMore() {
-        Http1AsyncDelegate delegate = this.delegate;
-        boolean more = this.canRequestMore.get();
-        boolean hasDemand = hasDemand(delegate);
-        debug.log(Level.DEBUG, () -> "checkRequestMore: "
-                  + "canRequestMore=" + more + ", hasDemand=" + hasDemand
-                  + (delegate == null ? ", delegate=null" : ""));
-        if (hasDemand) {
-            subscriber.requestMore();
-        }
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Return true if the delegate is not null and has some demand.
-     * @param delegate The Http1AsyncDelegate delegate
-     * @return true if the delegate is not null and has some demand
-     */
-    private boolean hasDemand(Http1AsyncDelegate delegate) {
-        if (delegate == null) return false;
-        AbstractSubscription subscription = delegate.subscription();
-        long demand = subscription.demand().get();
-        debug.log(Level.DEBUG, "downstream subscription demand is %s", demand);
-        return demand > 0;
-    }
-
-    /**
-     * Must be called from within the scheduler main loop.
-     * Handles pending delegate subscription.
-     * Return true if there was some pending delegate subscription and a new
-     * delegate was subscribed, false otherwise.
-     *
-     * @return true if there was some pending delegate subscription and a new
-     *         delegate was subscribed, false otherwise.
-     */
-    private boolean handlePendingDelegate() {
-        Http1AsyncDelegate pending = pendingDelegateRef.get();
-        if (pending != null && pendingDelegateRef.compareAndSet(pending, null)) {
-            Http1AsyncDelegate delegate = this.delegate;
-            if (delegate != null) unsubscribe(delegate);
-            Runnable cancel = () -> {
-                debug.log(Level.DEBUG, "Downstream subscription cancelled by %s", pending);
-                // The connection should be closed, as some data may
-                // be left over in the stream.
-                try {
-                    setRetryOnError(false);
-                    onReadError(new IOException("subscription cancelled"));
-                    unsubscribe(pending);
-                } finally {
-                    Http1Exchange<?> exchg = owner;
-                    stop();
-                    if (exchg != null) exchg.connection().close();
-                }
-            };
-            // The subscription created by a delegate is only loosely
-            // coupled with the upstream subscription. This is partly because
-            // the header/body parser work with a flow of ByteBuffer, whereas
-            // we have a flow List<ByteBuffer> upstream.
-            Http1AsyncDelegateSubscription subscription =
-                    new Http1AsyncDelegateSubscription(scheduler, cancel);
-            pending.onSubscribe(subscription);
-            this.delegate = delegate = pending;
-            final Object captured = delegate;
-            debug.log(Level.DEBUG, () -> "delegate is now " + captured
-                  + ", demand=" + subscription.demand().get()
-                  + ", canRequestMore=" + canRequestMore.get()
-                  + ", queue.isEmpty=" + queue.isEmpty());
-            return true;
-        }
-        return false;
-    }
-
-    synchronized void setRetryOnError(boolean retry) {
-        this.retry = retry;
-    }
-
-    void clear() {
-        debug.log(Level.DEBUG, "cleared");
-        this.pendingDelegateRef.set(null);
-        this.delegate = null;
-        this.owner = null;
-    }
-
-    void subscribe(Http1AsyncDelegate delegate) {
-        synchronized(this) {
-            pendingDelegateRef.set(delegate);
-        }
-        if (queue.isEmpty()) {
-            canRequestMore.set(true);
-        }
-        debug.log(Level.DEBUG, () ->
-                "Subscribed pending " + delegate + " queue.isEmpty: "
-                + queue.isEmpty());
-        // Everything may have been received already. Make sure
-        // we parse it.
-        if (client.isSelectorThread()) {
-            scheduler.deferOrSchedule(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.deferOrSchedule(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.deferOrSchedule(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.deferOrSchedule(executor);
-            }
-        }
-
-        void requestMore() {
-            Flow.Subscription s = subscription;
-            if (s == null) return;
-            if (canRequestMore.compareAndSet(true, false)) {
-                if (!completed && !dropped) {
-                    debug.log(Level.DEBUG,
-                        "Http1TubeSubscriber: requesting one more from upstream");
-                    s.request(1);
-                    return;
-                }
-            }
-            debug.log(Level.DEBUG, "Http1TubeSubscriber: no need to request more");
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> item) {
-            canRequestMore.set(item.isEmpty());
-            for (ByteBuffer buffer : item) {
-                asyncReceive(buffer);
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            onReadError(throwable);
-            completed = true;
-        }
-
-        @Override
-        public void onComplete() {
-            onReadError(new EOFException("EOF reached while reading"));
-            completed = true;
-        }
-
-        public void dropSubscription() {
-            debug.log(Level.DEBUG, "Http1TubeSubscriber: dropSubscription");
-            // we could probably set subscription to null here...
-            // then we might not need the 'dropped' boolean?
-            dropped = true;
-        }
-
-    }
-
-    // Drains the content of the queue into a single ByteBuffer.
-    // The scheduler must be permanently stopped before calling drain().
-    ByteBuffer drain(ByteBuffer initial) {
-        // Revisit: need to clean that up.
-        //
-        ByteBuffer b = initial = (initial == null ? Utils.EMPTY_BYTEBUFFER : initial);
-        assert scheduler.isStopped();
-
-        if (queue.isEmpty()) return b;
-
-        // sanity check: we shouldn't have queued the same
-        // buffer twice.
-        ByteBuffer[] qbb = queue.toArray(new ByteBuffer[queue.size()]);
-        assert java.util.stream.Stream.of(qbb)
-                .collect(Collectors.toSet())
-                .size() == qbb.length : debugQBB(qbb);
-
-        // compute the number of bytes in the queue, the number of bytes
-        // in the initial buffer
-        // TODO: will need revisiting - as it is not guaranteed that all
-        // data will fit in single BB!
-        int size = Utils.remaining(qbb, Integer.MAX_VALUE);
-        int remaining = b.remaining();
-        int free = b.capacity() - b.position() - remaining;
-        debug.log(Level.DEBUG,
-            "Flushing %s bytes from queue into initial buffer (remaining=%s, free=%s)",
-            size, remaining, free);
-
-        // check whether the initial buffer has enough space
-        if (size > free) {
-            debug.log(Level.DEBUG,
-                    "Allocating new buffer for initial: %s", (size + remaining));
-            // allocates a new buffer and copy initial to it
-            b = ByteBuffer.allocate(size + remaining);
-            Utils.copy(initial, b);
-            assert b.position() == remaining;
-            b.flip();
-            assert b.position() == 0;
-            assert b.limit() == remaining;
-            assert b.remaining() == remaining;
-        }
-
-        // store position and limit
-        int pos = b.position();
-        int limit = b.limit();
-        assert limit - pos == remaining;
-        assert b.capacity() >= remaining + size
-                : "capacity: " + b.capacity()
-                + ", remaining: " + b.remaining()
-                + ", size: " + size;
-
-        // prepare to copy the content of the queue
-        b.position(limit);
-        b.limit(pos + remaining + size);
-        assert b.remaining() >= size :
-                "remaining: " + b.remaining() + ", size: " + size;
-
-        // copy the content of the queue
-        int count = 0;
-        for (int i=0; i<qbb.length; i++) {
-            ByteBuffer b2 = qbb[i];
-            int r = b2.remaining();
-            assert b.remaining() >= r : "need at least " + r + " only "
-                    + b.remaining() + " available";
-            int copied = Utils.copy(b2, b);
-            assert copied == r : "copied="+copied+" available="+r;
-            assert b2.remaining() == 0;
-            count += copied;
-        }
-        assert count == size;
-        assert b.position() == pos + remaining + size :
-                "b.position="+b.position()+" != "+pos+"+"+remaining+"+"+size;
-
-        // reset limit and position
-        b.limit(limit+size);
-        b.position(pos);
-
-        // we can clear the refs
-        queue.clear();
-        final ByteBuffer bb = b;
-        debug.log(Level.DEBUG, () -> "Initial buffer now has " + bb.remaining()
-                + " pos=" + bb.position() + " limit=" + bb.limit());
-
-        return b;
-    }
-
-    private String debugQBB(ByteBuffer[] qbb) {
-        StringBuilder msg = new StringBuilder();
-        List<ByteBuffer> lbb = Arrays.asList(qbb);
-        Set<ByteBuffer> sbb = new HashSet<>(Arrays.asList(qbb));
-
-        int uniquebb = sbb.size();
-        msg.append("qbb: ").append(lbb.size())
-           .append(" (unique: ").append(uniquebb).append("), ")
-           .append("duplicates: ");
-        String sep = "";
-        for (ByteBuffer b : lbb) {
-            if (!sbb.remove(b)) {
-                msg.append(sep)
-                   .append(String.valueOf(b))
-                   .append("[remaining=")
-                   .append(b.remaining())
-                   .append(", position=")
-                   .append(b.position())
-                   .append(", capacity=")
-                   .append(b.capacity())
-                   .append("]");
-                sep = ", ";
-            }
-        }
-        return msg.toString();
-    }
-
-    volatile String dbgTag;
-    String dbgString() {
-        String tag = dbgTag;
-        if (tag == null) {
-            String flowTag = null;
-            Http1Exchange<?> exchg = owner;
-            Object flow = (exchg != null)
-                    ? exchg.connection().getConnectionFlow()
-                    : null;
-            flowTag = tag = flow == null ? null: (String.valueOf(flow));
-            if (flowTag != null) {
-                dbgTag = tag = flowTag + " Http1AsyncReceiver";
-            } else {
-                tag = "Http1AsyncReceiver";
-            }
-        }
-        return tag;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Exchange.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,616 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
-import java.nio.ByteBuffer;
-import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-
-/**
- * Encapsulates one HTTP/1.1 request/response exchange.
- */
-class Http1Exchange<T> extends ExchangeImpl<T> {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    private static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("Http1Exchange"::toString, DEBUG);
-
-    final HttpRequestImpl request; // main request
-    final Http1Request requestAction;
-    private volatile Http1Response<T> response;
-    final HttpConnection connection;
-    final HttpClientImpl client;
-    final Executor executor;
-    private final Http1AsyncReceiver asyncReceiver;
-
-    /** Records a possible cancellation raised before any operation
-     * has been initiated, or an error received while sending the request. */
-    private Throwable failed;
-    private final List<CompletableFuture<?>> operations; // used for cancel
-
-    /** Must be held when operating on any internal state or data. */
-    private final Object lock = new Object();
-
-    /** Holds the outgoing data, either the headers or a request body part. Or
-     * an error from the request body publisher. At most there can be ~2 pieces
-     * of outgoing data ( onComplete|onError can be invoked without demand ).*/
-    final ConcurrentLinkedDeque<DataPair> outgoing = new ConcurrentLinkedDeque<>();
-
-    /** The write publisher, responsible for writing the complete request ( both
-     * headers and body ( if any ). */
-    private final Http1Publisher writePublisher = new Http1Publisher();
-
-    /** Completed when the header have been published, or there is an error */
-    private volatile CompletableFuture<ExchangeImpl<T>> headersSentCF  = new MinimalFuture<>();
-     /** Completed when the body has been published, or there is an error */
-    private volatile 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.deferOrSchedule(client.theExecutor());
-            }
-
-            @Override
-            public void cancel() {
-                debug.log(Level.DEBUG, "subscription cancelled");
-                if (cancelled)
-                    return;  //no-op
-                cancelled = true;
-                writeScheduler.stop();
-            }
-        }
-    }
-
-    String dbgString() {
-        return "Http1Exchange";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1HeaderParser.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-
-class Http1HeaderParser {
-
-    private static final char CR = '\r';
-    private static final char LF = '\n';
-    private static final char HT = '\t';
-    private static final char SP = ' ';
-
-    private StringBuilder sb = new StringBuilder();
-    private String statusLine;
-    private int responseCode;
-    private HttpHeaders headers;
-    private Map<String,List<String>> privateMap = new HashMap<>();
-
-    enum State { STATUS_LINE,
-                 STATUS_LINE_FOUND_CR,
-                 STATUS_LINE_END,
-                 STATUS_LINE_END_CR,
-                 HEADER,
-                 HEADER_FOUND_CR,
-                 HEADER_FOUND_LF,
-                 HEADER_FOUND_CR_LF,
-                 HEADER_FOUND_CR_LF_CR,
-                 FINISHED }
-
-    private State state = State.STATUS_LINE;
-
-    /** Returns the status-line. */
-    String statusLine() { return statusLine; }
-
-    /** Returns the response code. */
-    int responseCode() { return responseCode; }
-
-    /** Returns the headers, possibly empty. */
-    HttpHeaders headers() { assert state == State.FINISHED; return headers; }
-
-    /**
-     * Parses HTTP/1.X status-line and headers from the given bytes. Must be
-     * called successive times, with additional data, until returns true.
-     *
-     * All given ByteBuffers will be consumed, until ( possibly ) the last one
-     * ( when true is returned ), which may not be fully consumed.
-     *
-     * @param input the ( partial ) header data
-     * @return true iff the end of the headers block has been reached
-     */
-    boolean parse(ByteBuffer input) throws ProtocolException {
-        requireNonNull(input, "null input");
-
-        while (input.hasRemaining() && state != State.FINISHED) {
-            switch (state) {
-                case STATUS_LINE:
-                    readResumeStatusLine(input);
-                    break;
-                case STATUS_LINE_FOUND_CR:
-                    readStatusLineFeed(input);
-                    break;
-                case STATUS_LINE_END:
-                    maybeStartHeaders(input);
-                    break;
-                case STATUS_LINE_END_CR:
-                    maybeEndHeaders(input);
-                    break;
-                case HEADER:
-                    readResumeHeader(input);
-                    break;
-                // fallthrough
-                case HEADER_FOUND_CR:
-                case HEADER_FOUND_LF:
-                    resumeOrLF(input);
-                    break;
-                case HEADER_FOUND_CR_LF:
-                    resumeOrSecondCR(input);
-                    break;
-                case HEADER_FOUND_CR_LF_CR:
-                    resumeOrEndHeaders(input);
-                    break;
-                default:
-                    throw new InternalError(
-                            "Unexpected state: " + String.valueOf(state));
-            }
-        }
-
-        return state == State.FINISHED;
-    }
-
-    private void readResumeStatusLine(ByteBuffer input) {
-        char c = 0;
-        while (input.hasRemaining() && (c =(char)input.get()) != CR) {
-            sb.append(c);
-        }
-
-        if (c == CR) {
-            state = State.STATUS_LINE_FOUND_CR;
-        }
-    }
-
-    private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
-        char c = (char)input.get();
-        if (c != LF) {
-            throw protocolException("Bad trailing char, \"%s\", when parsing status-line, \"%s\"",
-                                    c, sb.toString());
-        }
-
-        statusLine = sb.toString();
-        sb = new StringBuilder();
-        if (!statusLine.startsWith("HTTP/1.")) {
-            throw protocolException("Invalid status line: \"%s\"", statusLine);
-        }
-        if (statusLine.length() < 12) {
-            throw protocolException("Invalid status line: \"%s\"", statusLine);
-        }
-        responseCode = Integer.parseInt(statusLine.substring(9, 12));
-
-        state = State.STATUS_LINE_END;
-    }
-
-    private void maybeStartHeaders(ByteBuffer input) {
-        assert state == State.STATUS_LINE_END;
-        assert sb.length() == 0;
-        char c = (char)input.get();
-        if (c == CR) {
-            state = State.STATUS_LINE_END_CR;
-        } else {
-            sb.append(c);
-            state = State.HEADER;
-        }
-    }
-
-    private void maybeEndHeaders(ByteBuffer input) throws ProtocolException {
-        assert state == State.STATUS_LINE_END_CR;
-        assert sb.length() == 0;
-        char c = (char)input.get();
-        if (c == LF) {
-            headers = ImmutableHeaders.of(privateMap);
-            privateMap = null;
-            state = State.FINISHED;  // no headers
-        } else {
-            throw protocolException("Unexpected \"%s\", after status-line CR", c);
-        }
-    }
-
-    private void readResumeHeader(ByteBuffer input) {
-        assert state == State.HEADER;
-        assert input.hasRemaining();
-        while (input.hasRemaining()) {
-            char c = (char)input.get();
-            if (c == CR) {
-                state = State.HEADER_FOUND_CR;
-                break;
-            } else if (c == LF) {
-                state = State.HEADER_FOUND_LF;
-                break;
-            }
-
-            if (c == HT)
-                c = SP;
-            sb.append(c);
-        }
-    }
-
-    private void addHeaderFromString(String headerString) {
-        assert sb.length() == 0;
-        int idx = headerString.indexOf(':');
-        if (idx == -1)
-            return;
-        String name = headerString.substring(0, idx).trim();
-        if (name.isEmpty())
-            return;
-        String value = headerString.substring(idx + 1, headerString.length()).trim();
-
-        privateMap.computeIfAbsent(name.toLowerCase(Locale.US),
-                                   k -> new ArrayList<>()).add(value);
-    }
-
-    private void resumeOrLF(ByteBuffer input) {
-        assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF;
-        char c = (char)input.get();
-        if (c == LF && state == State.HEADER_FOUND_CR) {
-            // header value will be flushed by
-            // resumeOrSecondCR if next line does not
-            // begin by SP or HT
-            state = State.HEADER_FOUND_CR_LF;
-        } else if (c == SP || c == HT) {
-            sb.append(SP); // parity with MessageHeaders
-            state = State.HEADER;
-        } else {
-            sb = new StringBuilder();
-            sb.append(c);
-            state = State.HEADER;
-        }
-    }
-
-    private void resumeOrSecondCR(ByteBuffer input) {
-        assert state == State.HEADER_FOUND_CR_LF;
-        char c = (char)input.get();
-        if (c == CR) {
-            if (sb.length() > 0) {
-                // no continuation line - flush
-                // previous header value.
-                String headerString = sb.toString();
-                sb = new StringBuilder();
-                addHeaderFromString(headerString);
-            }
-            state = State.HEADER_FOUND_CR_LF_CR;
-        } else if (c == SP || c == HT) {
-            assert sb.length() != 0;
-            sb.append(SP); // continuation line
-            state = State.HEADER;
-        } else {
-            if (sb.length() > 0) {
-                // no continuation line - flush
-                // previous header value.
-                String headerString = sb.toString();
-                sb = new StringBuilder();
-                addHeaderFromString(headerString);
-            }
-            sb.append(c);
-            state = State.HEADER;
-        }
-    }
-
-    private void resumeOrEndHeaders(ByteBuffer input) throws ProtocolException {
-        assert state == State.HEADER_FOUND_CR_LF_CR;
-        char c = (char)input.get();
-        if (c == LF) {
-            state = State.FINISHED;
-            headers = ImmutableHeaders.of(privateMap);
-            privateMap = null;
-        } else {
-            throw protocolException("Unexpected \"%s\", after CR LF CR", c);
-        }
-    }
-
-    private ProtocolException protocolException(String format, Object... args) {
-        return new ProtocolException(format(format, args));
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Request.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,374 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.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 jdk.incubator.http.Http1Exchange.Http1BodySubscriber;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-/**
- *  An HTTP/1.1 request.
- */
-class Http1Request {
-    private final HttpRequestImpl request;
-    private final Http1Exchange<?> http1Exchange;
-    private final HttpConnection connection;
-    private final HttpRequest.BodyPublisher requestPublisher;
-    private final HttpHeaders userHeaders;
-    private final HttpHeadersImpl systemHeaders;
-    private volatile boolean streaming;
-    private volatile long contentLength;
-
-    Http1Request(HttpRequestImpl request,
-                 Http1Exchange<?> http1Exchange)
-        throws IOException
-    {
-        this.request = request;
-        this.http1Exchange = http1Exchange;
-        this.connection = http1Exchange.connection();
-        this.requestPublisher = request.requestPublisher;  // may be null
-        this.userHeaders = request.getUserHeaders();
-        this.systemHeaders = request.getSystemHeaders();
-    }
-
-    private void logHeaders(String completeHeaders) {
-        if (Log.headers()) {
-            //StringBuilder sb = new StringBuilder(256);
-            //sb.append("REQUEST HEADERS:\n");
-            //Log.dumpHeaders(sb, "    ", systemHeaders);
-            //Log.dumpHeaders(sb, "    ", userHeaders);
-            //Log.logHeaders(sb.toString());
-
-            String s = completeHeaders.replaceAll("\r\n", "\n");
-            Log.logHeaders("REQUEST HEADERS:\n" + s);
-        }
-    }
-
-    private void collectHeaders0(StringBuilder sb) {
-        collectHeaders1(sb, systemHeaders);
-        collectHeaders1(sb, userHeaders);
-        sb.append("\r\n");
-    }
-
-    private void collectHeaders1(StringBuilder sb, HttpHeaders headers) {
-        for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
-            String key = entry.getKey();
-            List<String> values = entry.getValue();
-            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 (request == null || requestPublisher == null) {
-            // Not a user request, or maybe a method, e.g. GET, with no body.
-            contentLength = 0;
-        } else {
-            contentLength = requestPublisher.contentLength();
-        }
-
-        if (contentLength == 0) {
-            systemHeaders.setHeader("Content-Length", "0");
-        } else if (contentLength > 0) {
-            systemHeaders.setHeader("Content-Length", Long.toString(contentLength));
-            streaming = false;
-        } else {
-            streaming = true;
-            systemHeaders.setHeader("Transfer-encoding", "chunked");
-        }
-        collectHeaders0(sb);
-        String hs = sb.toString();
-        logHeaders(hs);
-        ByteBuffer b = ByteBuffer.wrap(hs.getBytes(US_ASCII));
-        return List.of(b);
-    }
-
-    Http1BodySubscriber continueRequest()  {
-        Http1BodySubscriber subscriber;
-        if (streaming) {
-            subscriber = new StreamSubscriber();
-            requestPublisher.subscribe(subscriber);
-        } else {
-            if (contentLength == 0)
-                return null;
-
-            subscriber = new FixedContentSubscriber();
-            requestPublisher.subscribe(subscriber);
-        }
-        return subscriber;
-    }
-
-    class StreamSubscriber extends Http1BodySubscriber {
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (this.subscription != null) {
-                Throwable t = new IllegalStateException("already subscribed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                this.subscription = subscription;
-            }
-        }
-
-        @Override
-        public void onNext(ByteBuffer item) {
-            Objects.requireNonNull(item);
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                int chunklen = item.remaining();
-                ArrayList<ByteBuffer> l = new ArrayList<>(3);
-                l.add(getHeader(chunklen));
-                l.add(item);
-                l.add(ByteBuffer.wrap(CRLF));
-                http1Exchange.appendToOutgoing(l);
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            if (complete)
-                return;
-
-            subscription.cancel();
-            http1Exchange.appendToOutgoing(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                ArrayList<ByteBuffer> l = new ArrayList<>(2);
-                l.add(ByteBuffer.wrap(EMPTY_CHUNK_BYTES));
-                l.add(ByteBuffer.wrap(CRLF));
-                complete = true;
-                //setFinished();
-                http1Exchange.appendToOutgoing(l);
-                http1Exchange.appendToOutgoing(COMPLETED);
-                setFinished();  // TODO: before or after,? does it matter?
-
-            }
-        }
-    }
-
-    class FixedContentSubscriber extends Http1BodySubscriber {
-
-        private volatile long contentWritten;
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            if (this.subscription != null) {
-                Throwable t = new IllegalStateException("already subscribed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                this.subscription = subscription;
-            }
-        }
-
-        @Override
-        public void onNext(ByteBuffer item) {
-            debug.log(Level.DEBUG, "onNext");
-            Objects.requireNonNull(item);
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                long writing = item.remaining();
-                long written = (contentWritten += writing);
-
-                if (written > contentLength) {
-                    subscription.cancel();
-                    String msg = connection.getConnectionFlow()
-                                  + " [" + Thread.currentThread().getName() +"] "
-                                  + "Too many bytes in request body. Expected: "
-                                  + contentLength + ", got: " + written;
-                    http1Exchange.appendToOutgoing(new IOException(msg));
-                } else {
-                    http1Exchange.appendToOutgoing(List.of(item));
-                }
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            debug.log(Level.DEBUG, "onError");
-            if (complete)  // TODO: error?
-                return;
-
-            subscription.cancel();
-            http1Exchange.appendToOutgoing(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            debug.log(Level.DEBUG, "onComplete");
-            if (complete) {
-                Throwable t = new IllegalStateException("subscription already completed");
-                http1Exchange.appendToOutgoing(t);
-            } else {
-                complete = true;
-                long written = contentWritten;
-                if (contentLength > written) {
-                    subscription.cancel();
-                    Throwable t = new IOException(connection.getConnectionFlow()
-                                         + " [" + Thread.currentThread().getName() +"] "
-                                         + "Too few bytes returned by the publisher ("
-                                                  + written + "/"
-                                                  + contentLength + ")");
-                    http1Exchange.appendToOutgoing(t);
-                } else {
-                    http1Exchange.appendToOutgoing(COMPLETED);
-                }
-            }
-        }
-    }
-
-    private static final byte[] CRLF = {'\r', '\n'};
-    private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};
-
-    /** Returns a header for a particular chunk size */
-    private static ByteBuffer getHeader(int size) {
-        String hexStr = Integer.toHexString(size);
-        byte[] hexBytes = hexStr.getBytes(US_ASCII);
-        byte[] header = new byte[hexStr.length()+2];
-        System.arraycopy(hexBytes, 0, header, 0, hexBytes.length);
-        header[hexBytes.length] = CRLF[0];
-        header[hexBytes.length+1] = CRLF[1];
-        return ByteBuffer.wrap(header);
-    }
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::toString, DEBUG);
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http1Response.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,516 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import jdk.incubator.http.ResponseContent.BodyParser;
-import jdk.incubator.http.internal.common.Log;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Handles a HTTP/1.1 response (headers + body).
- * There can be more than one of these per Http exchange.
- */
-class Http1Response<T> {
-
-    private volatile ResponseContent content;
-    private final HttpRequestImpl request;
-    private Response response;
-    private final HttpConnection connection;
-    private HttpHeaders headers;
-    private int responseCode;
-    private final Http1Exchange<T> exchange;
-    private boolean return2Cache; // return connection to cache when finished
-    private final HeadersReader headersReader; // used to read the headers
-    private final BodyReader bodyReader; // used to read the body
-    private final Http1AsyncReceiver asyncReceiver;
-    private volatile EOFException eof;
-    // max number of bytes of (fixed length) body to ignore on redirect
-    private final static int MAX_IGNORE = 1024;
-
-    // Revisit: can we get rid of this?
-    static enum State {INITIAL, READING_HEADERS, READING_BODY, DONE}
-    private volatile State readProgress = State.INITIAL;
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this.getClass()::getSimpleName, DEBUG);
-
-
-    Http1Response(HttpConnection conn,
-                  Http1Exchange<T> exchange,
-                  Http1AsyncReceiver asyncReceiver) {
-        this.readProgress = State.INITIAL;
-        this.request = exchange.request();
-        this.exchange = exchange;
-        this.connection = conn;
-        this.asyncReceiver = asyncReceiver;
-        headersReader = new HeadersReader(this::advance);
-        bodyReader = new BodyReader(this::advance);
-    }
-
-   public CompletableFuture<Response> readHeadersAsync(Executor executor) {
-        debug.log(Level.DEBUG, () -> "Reading Headers: (remaining: "
-                + asyncReceiver.remaining() +") "  + readProgress);
-        // with expect continue we will resume reading headers + body.
-        asyncReceiver.unsubscribe(bodyReader);
-        bodyReader.reset();
-        Http1HeaderParser hd = new Http1HeaderParser();
-        readProgress = State.READING_HEADERS;
-        headersReader.start(hd);
-        asyncReceiver.subscribe(headersReader);
-        CompletableFuture<State> cf = headersReader.completion();
-        assert cf != null : "parsing not started";
-
-        Function<State, Response> lambda = (State completed) -> {
-                assert completed == State.READING_HEADERS;
-                debug.log(Level.DEBUG, () ->
-                            "Reading Headers: creating Response object;"
-                            + " state is now " + readProgress);
-                asyncReceiver.unsubscribe(headersReader);
-                responseCode = hd.responseCode();
-                headers = hd.headers();
-
-                response = new Response(request,
-                                        exchange.getExchange(),
-                                        headers,
-                                        responseCode,
-                                        HTTP_1_1);
-                return response;
-            };
-
-        if (executor != null) {
-            return cf.thenApplyAsync(lambda, executor);
-        } else {
-            return cf.thenApply(lambda);
-        }
-    }
-
-    private boolean finished;
-
-    synchronized void completed() {
-        finished = true;
-    }
-
-    synchronized boolean finished() {
-        return finished;
-    }
-
-    int fixupContentLen(int clen) {
-        if (request.method().equalsIgnoreCase("HEAD")) {
-            return 0;
-        }
-        if (clen == -1) {
-            if (headers.firstValue("Transfer-encoding").orElse("")
-                       .equalsIgnoreCase("chunked")) {
-                return -1;
-            }
-            return 0;
-        }
-        return clen;
-    }
-
-    /**
-     * Read up to MAX_IGNORE bytes discarding
-     */
-    public CompletableFuture<Void> ignoreBody(Executor executor) {
-        int clen = (int)headers.firstValueAsLong("Content-Length").orElse(-1);
-        if (clen == -1 || clen > MAX_IGNORE) {
-            connection.close();
-            return MinimalFuture.completedFuture(null); // not treating as error
-        } else {
-            return readBody(HttpResponse.BodySubscriber.discard((Void)null), true, executor);
-        }
-    }
-
-    public <U> CompletableFuture<U> readBody(HttpResponse.BodySubscriber<U> p,
-                                         boolean return2Cache,
-                                         Executor executor) {
-        this.return2Cache = return2Cache;
-        final HttpResponse.BodySubscriber<U> pusher = p;
-        final CompletionStage<U> bodyCF = p.getBody();
-        final CompletableFuture<U> cf = MinimalFuture.of(bodyCF);
-
-        int clen0 = (int)headers.firstValueAsLong("Content-Length").orElse(-1);
-
-        final int clen = fixupContentLen(clen0);
-
-        // expect-continue reads headers and body twice.
-        // if we reach here, we must reset the headersReader state.
-        asyncReceiver.unsubscribe(headersReader);
-        headersReader.reset();
-
-        executor.execute(() -> {
-            try {
-                HttpClientImpl client = connection.client();
-                content = new ResponseContent(
-                        connection, clen, headers, pusher,
-                        this::onFinished
-                );
-                if (cf.isCompletedExceptionally()) {
-                    // if an error occurs during subscription
-                    connection.close();
-                    return;
-                }
-                // increment the reference count on the HttpClientImpl
-                // to prevent the SelectorManager thread from exiting until
-                // the body is fully read.
-                client.reference();
-                bodyReader.start(content.getBodyParser(
-                    (t) -> {
-                        try {
-                            if (t != null) {
-                                pusher.onError(t);
-                                connection.close();
-                                if (!cf.isDone())
-                                    cf.completeExceptionally(t);
-                            }
-                        } finally {
-                            // decrement the reference count on the HttpClientImpl
-                            // to allow the SelectorManager thread to exit if no
-                            // other operation is pending and the facade is no
-                            // longer referenced.
-                            client.unreference();
-                            bodyReader.onComplete(t);
-                        }
-                    }));
-                CompletableFuture<State> bodyReaderCF = bodyReader.completion();
-                asyncReceiver.subscribe(bodyReader);
-                assert bodyReaderCF != null : "parsing not started";
-                // Make sure to keep a reference to asyncReceiver from
-                // within this
-                CompletableFuture<?> trailingOp = bodyReaderCF.whenComplete((s,t) ->  {
-                    t = Utils.getCompletionCause(t);
-                    try {
-                        if (t != null) {
-                            debug.log(Level.DEBUG, () ->
-                                    "Finished reading body: " + s);
-                            assert s == State.READING_BODY;
-                        }
-                        if (t != null && !cf.isDone()) {
-                            pusher.onError(t);
-                            cf.completeExceptionally(t);
-                        }
-                    } catch (Throwable x) {
-                        // not supposed to happen
-                        asyncReceiver.onReadError(x);
-                    }
-                });
-                connection.addTrailingOperation(trailingOp);
-            } catch (Throwable t) {
-               debug.log(Level.DEBUG, () -> "Failed reading body: " + t);
-                try {
-                    if (!cf.isDone()) {
-                        pusher.onError(t);
-                        cf.completeExceptionally(t);
-                    }
-                } finally {
-                    asyncReceiver.onReadError(t);
-                }
-            }
-        });
-        return cf;
-    }
-
-
-    private void onFinished() {
-        asyncReceiver.clear();
-        if (return2Cache) {
-            Log.logTrace("Attempting to return connection to the pool: {0}", connection);
-            // TODO: need to do something here?
-            // connection.setAsyncCallbacks(null, null, null);
-
-            // don't return the connection to the cache if EOF happened.
-            debug.log(Level.DEBUG, () -> connection.getConnectionFlow()
-                                   + ": return to HTTP/1.1 pool");
-            connection.closeOrReturnToCache(eof == null ? headers : null);
-        }
-    }
-
-    HttpHeaders responseHeaders() {
-        return headers;
-    }
-
-    int responseCode() {
-        return responseCode;
-    }
-
-// ================ Support for plugging into Http1Receiver   =================
-// ============================================================================
-
-    // Callback: Error receiver: Consumer of Throwable.
-    void onReadError(Throwable t) {
-        Log.logError(t);
-        Receiver<?> receiver = receiver(readProgress);
-        if (t instanceof EOFException) {
-            debug.log(Level.DEBUG, "onReadError: received EOF");
-            eof = (EOFException) t;
-        }
-        CompletableFuture<?> cf = receiver == null ? null : receiver.completion();
-        debug.log(Level.DEBUG, () -> "onReadError: cf is "
-                + (cf == null  ? "null"
-                : (cf.isDone() ? "already completed"
-                               : "not yet completed")));
-        if (cf != null && !cf.isDone()) cf.completeExceptionally(t);
-        else { debug.log(Level.DEBUG, "onReadError", t); }
-        debug.log(Level.DEBUG, () -> "closing connection: cause is " + t);
-        connection.close();
-    }
-
-    // ========================================================================
-
-    private State advance(State previous) {
-        assert readProgress == previous;
-        switch(previous) {
-            case READING_HEADERS:
-                asyncReceiver.unsubscribe(headersReader);
-                return readProgress = State.READING_BODY;
-            case READING_BODY:
-                asyncReceiver.unsubscribe(bodyReader);
-                return readProgress = State.DONE;
-            default:
-                throw new InternalError("can't advance from " + previous);
-        }
-    }
-
-    Receiver<?> receiver(State state) {
-        switch(state) {
-            case READING_HEADERS: return headersReader;
-            case READING_BODY: return bodyReader;
-            default: return null;
-        }
-
-    }
-
-    static abstract class Receiver<T>
-            implements Http1AsyncReceiver.Http1AsyncDelegate {
-        abstract void start(T parser);
-        abstract CompletableFuture<State> completion();
-        // accepts a buffer from upstream.
-        // this should be implemented as a simple call to
-        // accept(ref, parser, cf)
-        public abstract boolean tryAsyncReceive(ByteBuffer buffer);
-        public abstract void onReadError(Throwable t);
-        // handle a byte buffer received from upstream.
-        // this method should set the value of Http1Response.buffer
-        // to ref.get() before beginning parsing.
-        abstract void handle(ByteBuffer buf, T parser,
-                             CompletableFuture<State> cf);
-        // resets this objects state so that it can be reused later on
-        // typically puts the reference to parser and completion to null
-        abstract void reset();
-
-        // accepts a byte buffer received from upstream
-        // returns true if the buffer is fully parsed and more data can
-        // be accepted, false otherwise.
-        final boolean accept(ByteBuffer buf, T parser,
-                CompletableFuture<State> cf) {
-            if (cf == null || parser == null || cf.isDone()) return false;
-            handle(buf, parser, cf);
-            return !cf.isDone();
-        }
-        public abstract void onSubscribe(AbstractSubscription s);
-        public abstract AbstractSubscription subscription();
-
-    }
-
-    // Invoked with each new ByteBuffer when reading headers...
-    final class HeadersReader extends Receiver<Http1HeaderParser> {
-        final Consumer<State> onComplete;
-        volatile Http1HeaderParser parser;
-        volatile CompletableFuture<State> cf;
-        volatile long count; // bytes parsed (for debug)
-        volatile AbstractSubscription subscription;
-
-        HeadersReader(Consumer<State> onComplete) {
-            this.onComplete = onComplete;
-        }
-
-        @Override
-        public AbstractSubscription subscription() {
-            return subscription;
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription s) {
-            this.subscription = s;
-            s.request(1);
-        }
-
-        @Override
-        void reset() {
-            cf = null;
-            parser = null;
-            count = 0;
-            subscription = null;
-        }
-
-        // Revisit: do we need to support restarting?
-        @Override
-        final void start(Http1HeaderParser hp) {
-            count = 0;
-            cf = new MinimalFuture<>();
-            parser = hp;
-        }
-
-        @Override
-        CompletableFuture<State> completion() {
-            return cf;
-        }
-
-        @Override
-        public final boolean tryAsyncReceive(ByteBuffer ref) {
-            boolean hasDemand = subscription.demand().tryDecrement();
-            assert hasDemand;
-            boolean needsMore = accept(ref, parser, cf);
-            if (needsMore) subscription.request(1);
-            return needsMore;
-        }
-
-        @Override
-        public final void onReadError(Throwable t) {
-            Http1Response.this.onReadError(t);
-        }
-
-        @Override
-        final void handle(ByteBuffer b,
-                          Http1HeaderParser parser,
-                          CompletableFuture<State> cf) {
-            assert cf != null : "parsing not started";
-            assert parser != null : "no parser";
-            try {
-                count += b.remaining();
-                debug.log(Level.DEBUG, () -> "Sending " + b.remaining()
-                        + "/" + b.capacity() + " bytes to header parser");
-                if (parser.parse(b)) {
-                    count -= b.remaining();
-                    debug.log(Level.DEBUG, () ->
-                            "Parsing headers completed. bytes=" + count);
-                    onComplete.accept(State.READING_HEADERS);
-                    cf.complete(State.READING_HEADERS);
-                }
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG,
-                        () -> "Header parser failed to handle buffer: " + t);
-                cf.completeExceptionally(t);
-            }
-        }
-    }
-
-    // Invoked with each new ByteBuffer when reading bodies...
-    final class BodyReader extends Receiver<BodyParser> {
-        final Consumer<State> onComplete;
-        volatile BodyParser parser;
-        volatile CompletableFuture<State> cf;
-        volatile AbstractSubscription subscription;
-        BodyReader(Consumer<State> onComplete) {
-            this.onComplete = onComplete;
-        }
-
-        @Override
-        void reset() {
-            parser = null;
-            cf = null;
-            subscription = null;
-        }
-
-        // Revisit: do we need to support restarting?
-        @Override
-        final void start(BodyParser parser) {
-            cf = new MinimalFuture<>();
-            this.parser = parser;
-        }
-
-        @Override
-        CompletableFuture<State> completion() {
-            return cf;
-        }
-
-        @Override
-        public final boolean tryAsyncReceive(ByteBuffer b) {
-            return accept(b, parser, cf);
-        }
-
-        @Override
-        public final void onReadError(Throwable t) {
-            Http1Response.this.onReadError(t);
-        }
-
-        @Override
-        public AbstractSubscription subscription() {
-            return subscription;
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription s) {
-            this.subscription = s;
-            parser.onSubscribe(s);
-        }
-
-        @Override
-        final void handle(ByteBuffer b,
-                          BodyParser parser,
-                          CompletableFuture<State> cf) {
-            assert cf != null : "parsing not started";
-            assert parser != null : "no parser";
-            try {
-                debug.log(Level.DEBUG, () -> "Sending " + b.remaining()
-                        + "/" + b.capacity() + " bytes to body parser");
-                parser.accept(b);
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG,
-                        () -> "Body parser failed to handle buffer: " + t);
-                if (!cf.isDone()) {
-                    cf.completeExceptionally(t);
-                }
-            }
-        }
-
-        final void onComplete(Throwable closedExceptionally) {
-            if (cf.isDone()) return;
-            if (closedExceptionally != null) {
-                cf.completeExceptionally(closedExceptionally);
-            } else {
-                onComplete.accept(State.READING_BODY);
-                cf.complete(State.READING_BODY);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return super.toString() + "/parser=" + String.valueOf(parser);
-        }
-
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CompletableFuture;
-
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.frame.SettingsFrame;
-import static jdk.incubator.http.internal.frame.SettingsFrame.INITIAL_WINDOW_SIZE;
-import static jdk.incubator.http.internal.frame.SettingsFrame.ENABLE_PUSH;
-import static jdk.incubator.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE;
-import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_CONCURRENT_STREAMS;
-import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_FRAME_SIZE;
-
-/**
- *  Http2 specific aspects of HttpClientImpl
- */
-class Http2ClientImpl {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final static System.Logger debug =
-            Utils.getDebugLogger("Http2ClientImpl"::toString, DEBUG);
-
-    private final HttpClientImpl client;
-
-    Http2ClientImpl(HttpClientImpl client) {
-        this.client = client;
-    }
-
-    /* Map key is "scheme:host:port" */
-    private final Map<String,Http2Connection> connections = new ConcurrentHashMap<>();
-
-    private final Set<String> failures = Collections.synchronizedSet(new HashSet<>());
-
-    /**
-     * When HTTP/2 requested only. The following describes the aggregate behavior including the
-     * calling code. In all cases, the HTTP2 connection cache
-     * is checked first for a suitable connection and that is returned if available.
-     * If not, a new connection is opened, except in https case when a previous negotiate failed.
-     * In that case, we want to continue using http/1.1. When a connection is to be opened and
-     * if multiple requests are sent in parallel then each will open a new connection.
-     *
-     * If negotiation/upgrade succeeds then
-     * one connection will be put in the cache and the others will be closed
-     * after the initial request completes (not strictly necessary for h2, only for h2c)
-     *
-     * If negotiate/upgrade fails, then any opened connections remain open (as http/1.1)
-     * and will be used and cached in the http/1 cache. Note, this method handles the
-     * https failure case only (by completing the CF with an ALPN exception, handled externally)
-     * The h2c upgrade is handled externally also.
-     *
-     * Specific CF behavior of this method.
-     * 1. completes with ALPN exception: h2 negotiate failed for first time. failure recorded.
-     * 2. completes with other exception: failure not recorded. Caller must handle
-     * 3. completes normally with null: no connection in cache for h2c or h2 failed previously
-     * 4. completes normally with connection: h2 or h2c connection in cache. Use it.
-     */
-    CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req) {
-        URI uri = req.uri();
-        InetSocketAddress proxy = req.proxy();
-        String key = Http2Connection.keyFor(uri, proxy);
-
-        synchronized (this) {
-            Http2Connection connection = connections.get(key);
-            if (connection != null) { // fast path if connection already exists
-                return CompletableFuture.completedFuture(connection);
-            }
-
-            if (!req.secure() || failures.contains(key)) {
-                // secure: negotiate failed before. Use http/1.1
-                // !secure: no connection available in cache. Attempt upgrade
-                return MinimalFuture.completedFuture(null);
-            }
-        }
-        return Http2Connection
-                .createAsync(req, this)
-                .whenComplete((conn, t) -> {
-                    synchronized (Http2ClientImpl.this) {
-                        if (conn != null) {
-                            offerConnection(conn);
-                        } else {
-                            Throwable cause = Utils.getCompletionCause(t);
-                            if (cause instanceof Http2Connection.ALPNException)
-                                failures.add(key);
-                        }
-                    }
-                });
-    }
-
-    /*
-     * Cache the given connection, if no connection to the same
-     * destination exists. If one exists, then we let the initial stream
-     * complete but allow it to close itself upon completion.
-     * This situation should not arise with https because the request
-     * has not been sent as part of the initial alpn negotiation
-     */
-    boolean offerConnection(Http2Connection c) {
-        String key = c.key();
-        Http2Connection c1 = connections.putIfAbsent(key, c);
-        if (c1 != null) {
-            c.setSingleStream(true);
-            return false;
-        }
-        return true;
-    }
-
-    void deleteConnection(Http2Connection c) {
-        connections.remove(c.key());
-    }
-
-    void stop() {
-        debug.log(Level.DEBUG, "stopping");
-        connections.values().forEach(this::close);
-        connections.clear();
-    }
-
-    private void close(Http2Connection h2c) {
-        try { h2c.close(); } catch (Throwable t) {}
-    }
-
-    HttpClientImpl client() {
-        return client;
-    }
-
-    /** Returns the client settings as a base64 (url) encoded string */
-    String getSettingsString() {
-        SettingsFrame sf = getClientSettings();
-        byte[] settings = sf.toByteArray(); // without the header
-        Base64.Encoder encoder = Base64.getUrlEncoder()
-                                       .withoutPadding();
-        return encoder.encodeToString(settings);
-    }
-
-    private static final int K = 1024;
-
-    private static int getParameter(String property, int min, int max, int defaultValue) {
-        int value =  Utils.getIntegerNetProperty(property, defaultValue);
-        // use default value if misconfigured
-        if (value < min || value > max) {
-            Log.logError("Property value for {0}={1} not in [{2}..{3}]: " +
-                    "using default={4}", property, value, min, max, defaultValue);
-            value = defaultValue;
-        }
-        return value;
-    }
-
-    // used for the connection window, to have a connection window size
-    // bigger than the initial stream window size.
-    int getConnectionWindowSize(SettingsFrame clientSettings) {
-        // Maximum size is 2^31-1. Don't allow window size to be less
-        // than the stream window size. HTTP/2 specify a default of 64 * K -1,
-        // but we use 2^26 by default for better performance.
-        int streamWindow = clientSettings.getParameter(INITIAL_WINDOW_SIZE);
-
-        // The default is the max between the stream window size
-        // and the connection window size.
-        int defaultValue = Math.min(Integer.MAX_VALUE,
-                Math.max(streamWindow, K*K*32));
-
-        return getParameter(
-                "jdk.httpclient.connectionWindowSize",
-                streamWindow, Integer.MAX_VALUE, defaultValue);
-    }
-
-    SettingsFrame getClientSettings() {
-        SettingsFrame frame = new SettingsFrame();
-        // default defined for HTTP/2 is 4 K, we use 16 K.
-        frame.setParameter(HEADER_TABLE_SIZE, getParameter(
-                "jdk.httpclient.hpack.maxheadertablesize",
-                0, Integer.MAX_VALUE, 16 * K));
-        // O: does not accept push streams. 1: accepts push streams.
-        frame.setParameter(ENABLE_PUSH, getParameter(
-                "jdk.httpclient.enablepush",
-                0, 1, 1));
-        // HTTP/2 recommends to set the number of concurrent streams
-        // no lower than 100. We use 100. 0 means no stream would be
-        // accepted. That would render the client to be non functional,
-        // so we won't let 0 be configured for our Http2ClientImpl.
-        frame.setParameter(MAX_CONCURRENT_STREAMS, getParameter(
-                "jdk.httpclient.maxstreams",
-                1, Integer.MAX_VALUE, 100));
-        // Maximum size is 2^31-1. Don't allow window size to be less
-        // than the minimum frame size as this is likely to be a
-        // configuration error. HTTP/2 specify a default of 64 * K -1,
-        // but we use 16 M  for better performance.
-        frame.setParameter(INITIAL_WINDOW_SIZE, getParameter(
-                "jdk.httpclient.windowsize",
-                16 * K, Integer.MAX_VALUE, 16*K*K));
-        // HTTP/2 specify a minimum size of 16 K, a maximum size of 2^24-1,
-        // and a default of 16 K. We use 16 K as default.
-        frame.setParameter(MAX_FRAME_SIZE, getParameter(
-                "jdk.httpclient.maxframesize",
-                16 * K, 16 * K * K -1, 16 * K));
-        return frame;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1276 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Flow;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import javax.net.ssl.SSLEngine;
-import jdk.incubator.http.HttpConnection.HttpPublisher;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.FlowTube.TubeSubscriber;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.frame.ContinuationFrame;
-import jdk.incubator.http.internal.frame.DataFrame;
-import jdk.incubator.http.internal.frame.ErrorFrame;
-import jdk.incubator.http.internal.frame.FramesDecoder;
-import jdk.incubator.http.internal.frame.FramesEncoder;
-import jdk.incubator.http.internal.frame.GoAwayFrame;
-import jdk.incubator.http.internal.frame.HeaderFrame;
-import jdk.incubator.http.internal.frame.HeadersFrame;
-import jdk.incubator.http.internal.frame.Http2Frame;
-import jdk.incubator.http.internal.frame.MalformedFrame;
-import jdk.incubator.http.internal.frame.OutgoingHeaders;
-import jdk.incubator.http.internal.frame.PingFrame;
-import jdk.incubator.http.internal.frame.PushPromiseFrame;
-import jdk.incubator.http.internal.frame.ResetFrame;
-import jdk.incubator.http.internal.frame.SettingsFrame;
-import jdk.incubator.http.internal.frame.WindowUpdateFrame;
-import jdk.incubator.http.internal.hpack.Encoder;
-import jdk.incubator.http.internal.hpack.Decoder;
-import jdk.incubator.http.internal.hpack.DecodingCallback;
-
-import static jdk.incubator.http.internal.frame.SettingsFrame.*;
-
-
-/**
- * An Http2Connection. Encapsulates the socket(channel) and any SSLEngine used
- * over it. Contains an HttpConnection which hides the SocketChannel SSL stuff.
- *
- * Http2Connections belong to a Http2ClientImpl, (one of) which belongs
- * to a HttpClientImpl.
- *
- * Creation cases:
- * 1) upgraded HTTP/1.1 plain tcp connection
- * 2) prior knowledge directly created plain tcp connection
- * 3) directly created HTTP/2 SSL connection which uses ALPN.
- *
- * Sending is done by writing directly to underlying HttpConnection object which
- * is operating in async mode. No flow control applies on output at this level
- * and all writes are just executed as puts to an output Q belonging to HttpConnection
- * Flow control is implemented by HTTP/2 protocol itself.
- *
- * Hpack header compression
- * and outgoing stream creation is also done here, because these operations
- * must be synchronized at the socket level. Stream objects send frames simply
- * by placing them on the connection's output Queue. sendFrame() is called
- * from a higher level (Stream) thread.
- *
- * asyncReceive(ByteBuffer) is always called from the selector thread. It assembles
- * incoming Http2Frames, and directs them to the appropriate Stream.incoming()
- * or handles them directly itself. This thread performs hpack decompression
- * and incoming stream creation (Server push). Incoming frames destined for a
- * stream are provided by calling Stream.incoming().
- */
-class Http2Connection  {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    static final boolean DEBUG_HPACK = Utils.DEBUG_HPACK; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    final static System.Logger  DEBUG_LOGGER =
-            Utils.getDebugLogger("Http2Connection"::toString, DEBUG);
-    private final System.Logger debugHpack =
-                  Utils.getHpackLogger(this::dbgString, DEBUG_HPACK);
-    static final ByteBuffer EMPTY_TRIGGER = ByteBuffer.allocate(0);
-
-    private boolean singleStream; // used only for stream 1, then closed
-
-    /*
-     *  ByteBuffer pooling strategy for HTTP/2 protocol:
-     *
-     * In general there are 4 points where ByteBuffers are used:
-     *  - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing encrypted data
-     *    in case of SSL connection.
-     *
-     * 1. Outgoing frames encoded to ByteBuffers.
-     *    Outgoing ByteBuffers are created with requited size and frequently small (except DataFrames, etc)
-     *    At this place no pools at all. All outgoing buffers should be collected by GC.
-     *
-     * 2. Incoming ByteBuffers (decoded to frames).
-     *    Here, total elimination of BB pool is not a good idea.
-     *    We don't know how many bytes we will receive through network.
-     * So here we allocate buffer of reasonable size. The following life of the BB:
-     * - If all frames decoded from the BB are other than DataFrame and HeaderFrame (and HeaderFrame subclasses)
-     *     BB is returned to pool,
-     * - If we decoded DataFrame from the BB. In that case DataFrame refers to subbuffer obtained by slice() method.
-     *     Such BB is never returned to pool and will be GCed.
-     * - If we decoded HeadersFrame from the BB. Then header decoding is performed inside processFrame method and
-     *     the buffer could be release to pool.
-     *
-     * 3. SLL encrypted buffers. Here another pool was introduced and all net buffers are to/from the pool,
-     *    because of we can't predict size encrypted packets.
-     *
-     */
-
-
-    // A small class that allows to control frames with respect to the state of
-    // the connection preface. Any data received before the connection
-    // preface is sent will be buffered.
-    private final class FramesController {
-        volatile boolean prefaceSent;
-        volatile List<ByteBuffer> pending;
-
-        boolean processReceivedData(FramesDecoder decoder, ByteBuffer buf)
-                throws IOException
-        {
-            // if preface is not sent, buffers data in the pending list
-            if (!prefaceSent) {
-                debug.log(Level.DEBUG, "Preface is not sent: buffering %d",
-                          buf.remaining());
-                synchronized (this) {
-                    if (!prefaceSent) {
-                        if (pending == null) pending = new ArrayList<>();
-                        pending.add(buf);
-                        debug.log(Level.DEBUG, () -> "there are now "
-                              + Utils.remaining(pending)
-                              + " bytes buffered waiting for preface to be sent");
-                        return false;
-                    }
-                }
-            }
-
-            // Preface is sent. Checks for pending data and flush it.
-            // We rely on this method being called from within the Http2TubeSubscriber
-            // scheduler, so we know that no other thread could execute this method
-            // concurrently while we're here.
-            // This ensures that later incoming buffers will not
-            // be processed before we have flushed the pending queue.
-            // No additional synchronization is therefore necessary here.
-            List<ByteBuffer> pending = this.pending;
-            this.pending = null;
-            if (pending != null) {
-                // flush pending data
-                debug.log(Level.DEBUG, () -> "Processing buffered data: "
-                      + Utils.remaining(pending));
-                for (ByteBuffer b : pending) {
-                    decoder.decode(b);
-                }
-            }
-            // push the received buffer to the frames decoder.
-            if (buf != EMPTY_TRIGGER) {
-                debug.log(Level.DEBUG, "Processing %d", buf.remaining());
-                decoder.decode(buf);
-            }
-            return true;
-        }
-
-        // Mark that the connection preface is sent
-        void markPrefaceSent() {
-            assert !prefaceSent;
-            synchronized (this) {
-                prefaceSent = true;
-            }
-        }
-    }
-
-    volatile boolean closed;
-
-    //-------------------------------------
-    final HttpConnection connection;
-    private final Http2ClientImpl client2;
-    private final Map<Integer,Stream<?>> streams = new ConcurrentHashMap<>();
-    private int nextstreamid;
-    private int nextPushStream = 2;
-    private final Encoder hpackOut;
-    private final Decoder hpackIn;
-    final SettingsFrame clientSettings;
-    private volatile SettingsFrame serverSettings;
-    private final String key; // for HttpClientImpl.connections map
-    private final FramesDecoder framesDecoder;
-    private final FramesEncoder framesEncoder = new FramesEncoder();
-
-    /**
-     * Send Window controller for both connection and stream windows.
-     * Each of this connection's Streams MUST use this controller.
-     */
-    private final WindowController windowController = new WindowController();
-    private final FramesController framesController = new FramesController();
-    private final Http2TubeSubscriber subscriber = new Http2TubeSubscriber();
-    final ConnectionWindowUpdateSender windowUpdater;
-    private volatile Throwable cause;
-    private volatile Supplier<ByteBuffer> initial;
-
-    static final int DEFAULT_FRAME_SIZE = 16 * 1024;
-
-
-    // TODO: need list of control frames from other threads
-    // that need to be sent
-
-    private Http2Connection(HttpConnection connection,
-                            Http2ClientImpl client2,
-                            int nextstreamid,
-                            String key) {
-        this.connection = connection;
-        this.client2 = client2;
-        this.nextstreamid = nextstreamid;
-        this.key = key;
-        this.clientSettings = this.client2.getClientSettings();
-        this.framesDecoder = new FramesDecoder(this::processFrame,
-                clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE));
-        // serverSettings will be updated by server
-        this.serverSettings = SettingsFrame.getDefaultSettings();
-        this.hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE));
-        this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
-        debugHpack.log(Level.DEBUG, () -> "For the record:" + super.toString());
-        debugHpack.log(Level.DEBUG, "Decoder created: %s", hpackIn);
-        debugHpack.log(Level.DEBUG, "Encoder created: %s", hpackOut);
-        this.windowUpdater = new ConnectionWindowUpdateSender(this,
-                client2.getConnectionWindowSize(clientSettings));
-    }
-
-    /**
-     * Case 1) Create from upgraded HTTP/1.1 connection.
-     * Is ready to use. Can be SSL. exchange is the Exchange
-     * that initiated the connection, whose response will be delivered
-     * on a Stream.
-     */
-    private Http2Connection(HttpConnection connection,
-                    Http2ClientImpl client2,
-                    Exchange<?> exchange,
-                    Supplier<ByteBuffer> initial)
-        throws IOException, InterruptedException
-    {
-        this(connection,
-                client2,
-                3, // stream 1 is registered during the upgrade
-                keyFor(connection));
-        Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
-
-        Stream<?> initialStream = createStream(exchange);
-        initialStream.registerStream(1);
-        windowController.registerStream(1, getInitialSendWindowSize());
-        initialStream.requestSent();
-        // Upgrading:
-        //    set callbacks before sending preface - makes sure anything that
-        //    might be sent by the server will come our way.
-        this.initial = initial;
-        connectFlows(connection);
-        sendConnectionPreface();
-    }
-
-    // Used when upgrading an HTTP/1.1 connection to HTTP/2 after receiving
-    // agreement from the server. Async style but completes immediately, because
-    // the connection is already connected.
-    static CompletableFuture<Http2Connection> createAsync(HttpConnection connection,
-                                                          Http2ClientImpl client2,
-                                                          Exchange<?> exchange,
-                                                          Supplier<ByteBuffer> initial)
-    {
-        return MinimalFuture.supply(() -> new Http2Connection(connection, client2, exchange, initial));
-    }
-
-    // Requires TLS handshake. So, is really async
-    static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,
-                                                          Http2ClientImpl h2client) {
-        assert request.secure();
-        AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)
-        HttpConnection.getConnection(request.getAddress(),
-                                     h2client.client(),
-                                     request,
-                                     HttpClient.Version.HTTP_2);
-
-        return connection.connectAsync()
-                  .thenCompose(unused -> checkSSLConfig(connection))
-                  .thenCompose(notused-> {
-                      CompletableFuture<Http2Connection> cf = new MinimalFuture<>();
-                      try {
-                          Http2Connection hc = new Http2Connection(request, h2client, connection);
-                          cf.complete(hc);
-                      } catch (IOException e) {
-                          cf.completeExceptionally(e);
-                      }
-                      return cf; } );
-    }
-
-    /**
-     * Cases 2) 3)
-     *
-     * request is request to be sent.
-     */
-    private Http2Connection(HttpRequestImpl request,
-                            Http2ClientImpl h2client,
-                            HttpConnection connection)
-        throws IOException
-    {
-        this(connection,
-             h2client,
-             1,
-             keyFor(request.uri(), request.proxy()));
-
-        Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
-
-        // safe to resume async reading now.
-        connectFlows(connection);
-        sendConnectionPreface();
-    }
-
-    private void connectFlows(HttpConnection connection) {
-        FlowTube tube =  connection.getConnectionFlow();
-        // Connect the flow to our Http2TubeSubscriber:
-        tube.connectFlows(connection.publisher(), subscriber);
-    }
-
-    final HttpClientImpl client() {
-        return client2.client();
-    }
-
-    /**
-     * Throws an IOException if h2 was not negotiated
-     */
-    private static CompletableFuture<?> checkSSLConfig(AbstractAsyncSSLConnection aconn) {
-        assert aconn.isSecure();
-
-        Function<String, CompletableFuture<Void>> checkAlpnCF = (alpn) -> {
-            CompletableFuture<Void> cf = new MinimalFuture<>();
-            SSLEngine engine = aconn.getEngine();
-            assert Objects.equals(alpn, engine.getApplicationProtocol());
-
-            DEBUG_LOGGER.log(Level.DEBUG, "checkSSLConfig: alpn: %s", alpn );
-
-            if (alpn == null || !alpn.equals("h2")) {
-                String msg;
-                if (alpn == null) {
-                    Log.logSSL("ALPN not supported");
-                    msg = "ALPN not supported";
-                } else {
-                    switch (alpn) {
-                        case "":
-                            Log.logSSL(msg = "No ALPN negotiated");
-                            break;
-                        case "http/1.1":
-                            Log.logSSL( msg = "HTTP/1.1 ALPN returned");
-                            break;
-                        default:
-                            Log.logSSL(msg = "Unexpected ALPN: " + alpn);
-                            cf.completeExceptionally(new IOException(msg));
-                    }
-                }
-                cf.completeExceptionally(new ALPNException(msg, aconn));
-                return cf;
-            }
-            cf.complete(null);
-            return cf;
-        };
-
-        return aconn.getALPN().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());
-        // 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);
-                }
-            }
-        }
-
-
-        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.deferOrSchedule(client().theExecutor());
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            debug.log(Level.DEBUG, () -> "onError: " + throwable);
-            error = throwable;
-            completed = true;
-            scheduler.deferOrSchedule(client().theExecutor());
-        }
-
-        @Override
-        public void onComplete() {
-            debug.log(Level.DEBUG, "EOF");
-            error = new EOFException("EOF reached while reading");
-            completed = true;
-            scheduler.deferOrSchedule(client().theExecutor());
-        }
-
-        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 = 23138275393635783L;
-        final AbstractAsyncSSLConnection connection;
-
-        ALPNException(String msg, AbstractAsyncSSLConnection connection) {
-            super(msg);
-            this.connection = connection;
-        }
-
-        AbstractAsyncSSLConnection getConnection() {
-            return connection;
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,524 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-
-/**
- * A container for configuration information common to multiple {@link
- * HttpRequest}s. All requests are sent through a {@code HttpClient}.
- * {@Incubating}
- *
- * <p> {@code HttpClient}s are immutable and created from a builder returned
- * from {@link HttpClient#newBuilder()}. Request builders are created by calling
- * {@link HttpRequest#newBuilder() }.
- * <p>
- * See {@link HttpRequest} for examples of usage of this API.
- *
- * @since 9
- */
-public abstract class HttpClient {
-
-    /**
-     * Creates an HttpClient.
-     */
-    protected HttpClient() {}
-
-    /**
-     * Returns a new HttpClient with default settings.
-     *
-     * <p> Equivalent to {@code newBuilder().build()}.
-     *
-     * <p> The default settings include: the "GET" request method, a preference
-     * of {@linkplain HttpClient.Version#HTTP_2 HTTP/2}, a redirection policy of
-     * {@linkplain Redirect#NEVER NEVER}, the {@linkplain
-     * ProxySelector#getDefault() default proxy selector}, and the {@linkplain
-     * SSLContext#getDefault() default SSL context}.
-     *
-     * @implNote The system-wide default values are retrieved at the time the
-     * {@code HttpClient} instance is constructed. Changing the system-wide
-     * values after an {@code HttpClient} instance has been built, for
-     * instance, by calling {@link ProxySelector#setDefault(ProxySelector)}
-     * or {@link SSLContext#setDefault(SSLContext)}, has no effect on already
-     * built instances.
-     *
-     * @return a new HttpClient
-     */
-    public static HttpClient newHttpClient() {
-        return newBuilder().build();
-    }
-
-    /**
-     * Creates a new {@code HttpClient} builder.
-     *
-     * @return a {@code HttpClient.Builder}
-     */
-    public static Builder newBuilder() {
-        return new HttpClientBuilderImpl();
-    }
-
-    /**
-     * A builder of immutable {@link HttpClient}s.
-     * {@Incubating}
-     *
-     * <p> Builders are created by invoking {@linkplain HttpClient#newBuilder()
-     * newBuilder}. Each of the setter methods modifies the state of the builder
-     * and returns the same instance. Builders are not thread-safe and should not be
-     * used concurrently from multiple threads without external synchronization.
-     *
-     * @since 9
-     */
-    public abstract static class Builder {
-
-        /**
-         * A proxy selector that always return {@link Proxy#NO_PROXY} implying
-         * a direct connection.
-         * This is a convenience object that can be passed to {@link #proxy(ProxySelector)}
-         * in order to build an instance of {@link HttpClient} that uses no
-         * proxy.
-         */
-        public static final ProxySelector NO_PROXY = ProxySelector.of(null);
-
-        /**
-         * Creates a Builder.
-         */
-        protected Builder() {}
-
-        /**
-         * Sets a cookie handler.
-         *
-         * @param cookieHandler the cookie handler
-         * @return this builder
-         */
-        public abstract Builder cookieHandler(CookieHandler cookieHandler);
-
-        /**
-         * Sets an {@code SSLContext}.
-         *
-         * <p> If this method is not invoked prior to {@linkplain #build()
-         * building}, then newly built clients will use the {@linkplain
-         * SSLContext#getDefault() default context}, which is normally adequate
-         * for client applications that do not need to specify protocols, or
-         * require client authentication.
-         *
-         * @param sslContext the SSLContext
-         * @return this builder
-         */
-        public abstract Builder sslContext(SSLContext sslContext);
-
-        /**
-         * Sets an {@code SSLParameters}.
-         *
-         * <p> If this method is not invoked prior to {@linkplain #build()
-         * building}, then newly built clients will use a default,
-         * implementation specific, set of parameters.
-         *
-         * <p> Some parameters which are used internally by the HTTP Client
-         * implementation (such as the application protocol list) should not be
-         * set by callers, as they may be ignored. The contents of the given
-         * object are copied.
-         *
-         * @param sslParameters the SSLParameters
-         * @return this builder
-         */
-        public abstract Builder sslParameters(SSLParameters sslParameters);
-
-        /**
-         * Sets the executor to be used for asynchronous and dependent tasks.
-         *
-         * <p> If this method is not invoked prior to {@linkplain #build()
-         * building}, a default executor is created for each newly built {@code
-         * HttpClient}. The default executor uses a {@linkplain
-         * Executors#newCachedThreadPool(ThreadFactory) cached thread pool},
-         * with a custom thread factory.
-         *
-         * @implNote If a security manager has been installed, the thread
-         * factory creates threads that run with an access control context that
-         * has no permissions.
-         *
-         * @param executor the Executor
-         * @return this builder
-         */
-        public abstract Builder executor(Executor executor);
-
-        /**
-         * Specifies whether requests will automatically follow redirects issued
-         * by the server.
-         *
-         * <p> If this method is not invoked prior to {@linkplain #build()
-         * building}, then newly built clients will use a default redirection
-         * policy of {@link Redirect#NEVER NEVER}.
-         *
-         * @param policy the redirection policy
-         * @return this builder
-         */
-        public abstract Builder followRedirects(Redirect policy);
-
-        /**
-         * Requests a specific HTTP protocol version where possible.
-         *
-         * <p> If this method is not invoked prior to {@linkplain #build()
-         * building}, then newly built clients will prefer {@linkplain
-         * Version#HTTP_2 HTTP/2}.
-         *
-         * <p> If set to {@linkplain Version#HTTP_2 HTTP/2}, then each request
-         * will attempt to upgrade to HTTP/2. If the upgrade succeeds, then the
-         * response to this request will use HTTP/2 and all subsequent requests
-         * and responses to the same
-         * <a href="https://tools.ietf.org/html/rfc6454#section-4">origin server</a>
-         * will use HTTP/2. If the upgrade fails, then the response will be
-         * handled using HTTP/1.1
-         *
-         * @param version the requested HTTP protocol version
-         * @return this builder
-         */
-        public abstract Builder version(HttpClient.Version version);
-
-        /**
-         * Sets the default priority for any HTTP/2 requests sent from this
-         * client. The value provided must be between {@code 1} and {@code 256}
-         * (inclusive).
-         *
-         * @param priority the priority weighting
-         * @return this builder
-         * @throws IllegalArgumentException if the given priority is out of range
-         */
-        public abstract Builder priority(int priority);
-
-        /**
-         * Sets a {@link java.net.ProxySelector}.
-         *
-         * @apiNote {@link ProxySelector#of(InetSocketAddress)}
-         * provides a {@code ProxySelector} which uses a single proxy for all
-         * requests. The system-wide proxy selector can be retrieved by
-         * {@link ProxySelector#getDefault()}.
-         *
-         * @implNote
-         * If this method is not invoked prior to {@linkplain #build()
-         * building}, then newly built clients will use the {@linkplain
-         * ProxySelector#getDefault() default proxy selector}, which
-         * is normally adequate for client applications. This default
-         * behavior can be turned off by supplying an explicit proxy
-         * selector to this method, such as {@link #NO_PROXY} or one
-         * returned by {@link ProxySelector#of(InetSocketAddress)},
-         * before calling {@link #build()}.
-         *
-         * @param selector the ProxySelector
-         * @return this builder
-         */
-        public abstract Builder proxy(ProxySelector selector);
-
-        /**
-         * Sets an authenticator to use for HTTP authentication.
-         *
-         * @param a the Authenticator
-         * @return this builder
-         */
-        public abstract Builder authenticator(Authenticator a);
-
-        /**
-         * Returns a new {@link HttpClient} built from the current state of this
-         * builder.
-         *
-         * @return this builder
-         */
-        public abstract HttpClient build();
-    }
-
-
-    /**
-     * Returns an {@code Optional} containing this client's {@linkplain
-     * CookieHandler}. If no {@code CookieHandler} was set in this client's
-     * builder, then the {@code Optional} is empty.
-     *
-     * @return an {@code Optional} containing this client's {@code CookieHandler}
-     */
-    public abstract Optional<CookieHandler> cookieHandler();
-
-    /**
-     * Returns the follow redirects policy for this client. The default value
-     * for client's built by builders that do not specify a redirect policy is
-     * {@link HttpClient.Redirect#NEVER NEVER}.
-     *
-     * @return this client's follow redirects setting
-     */
-    public abstract Redirect followRedirects();
-
-    /**
-     * Returns an {@code Optional} containing the {@code ProxySelector}
-     * supplied to this client. If no proxy selector was set in this client's
-     * builder, then the {@code Optional} is empty.
-     *
-     * <p> Even though this method may return an empty optional, the {@code
-     * HttpClient} may still have an non-exposed {@linkplain
-     * Builder#proxy(ProxySelector) default proxy selector} that is
-     * used for sending HTTP requests.
-     *
-     * @return an {@code Optional} containing the proxy selector supplied
-     *        to this client.
-     */
-    public abstract Optional<ProxySelector> proxy();
-
-    /**
-     * Returns this client's {@code SSLContext}.
-     *
-     * <p> If no {@code SSLContext} was set in this client's builder, then the
-     * {@linkplain SSLContext#getDefault() default context} is returned.
-     *
-     * @return this client's SSLContext
-     */
-    public abstract SSLContext sslContext();
-
-    /**
-     * Returns a copy of this client's {@link SSLParameters}.
-     *
-     * <p> If no {@code SSLParameters} were set in the client's builder, then an
-     * implementation specific default set of parameters, that the client will
-     * use, is returned.
-     *
-     * @return this client's {@code SSLParameters}
-     */
-    public abstract SSLParameters sslParameters();
-
-    /**
-     * Returns an {@code Optional} containing the {@link Authenticator} set on
-     * this client. If no {@code Authenticator} was set in the client's builder,
-     * then the {@code Optional} is empty.
-     *
-     * @return an {@code Optional} containing this client's {@code Authenticator}
-     */
-    public abstract Optional<Authenticator> authenticator();
-
-    /**
-     * Returns the HTTP protocol version requested for this client. The default
-     * value is {@link HttpClient.Version#HTTP_2}
-     *
-     * @return the HTTP protocol version requested
-     */
-    public abstract HttpClient.Version version();
-
-    /**
-     * Returns an {@code Optional} containing this client's {@linkplain
-     * Executor}. If no {@code Executor} was set in the client's builder,
-     * then the {@code Optional} is empty.
-     *
-     * <p> Even though this method may return an empty optional, the {@code
-     * HttpClient} may still have an non-exposed {@linkplain
-     * HttpClient.Builder#executor(Executor) default executor} that is used for
-     * executing asynchronous and dependent tasks.
-     *
-     * @return an {@code Optional} containing this client's {@code Executor}
-     */
-    public abstract Optional<Executor> executor();
-
-    /**
-     * The HTTP protocol version.
-     * {@Incubating}
-     *
-     * @since 9
-     */
-    public enum Version {
-
-        /**
-         * HTTP version 1.1
-         */
-        HTTP_1_1,
-
-        /**
-         * HTTP version 2
-         */
-        HTTP_2
-    }
-
-    /**
-     * Defines automatic redirection policy.
-     * {@Incubating}
-     *
-     * <p> This is checked whenever a {@code 3XX} response code is received. If
-     * redirection does not happen automatically then the response is returned
-     * to the user, where it can be handled manually.
-     *
-     * <p> {@code Redirect} policy is set via the {@link
-     * HttpClient.Builder#followRedirects(HttpClient.Redirect)} method.
-     *
-     * @since 9
-     */
-    public enum Redirect {
-
-        /**
-         * Never redirect.
-         */
-        NEVER,
-
-        /**
-         * Always redirect.
-         */
-        ALWAYS,
-
-        /**
-         * Redirect to same protocol only. Redirection may occur from HTTP URLs
-         * to other HTTP URLs, and from HTTPS URLs to other HTTPS URLs.
-         */
-        SAME_PROTOCOL,
-
-        /**
-         * Redirect always except from HTTPS URLs to HTTP URLs.
-         */
-        SECURE
-    }
-
-    /**
-     * Sends the given request using this client, blocking if necessary to get
-     * the response. The returned {@link HttpResponse}{@code <T>} contains the
-     * response status, headers, and body ( as handled by given response body
-     * handler ).
-     *
-     * @param <T> the response body type
-     * @param req the request
-     * @param responseBodyHandler the response body handler
-     * @return the response body
-     * @throws IOException if an I/O error occurs when sending or receiving
-     * @throws InterruptedException if the operation is interrupted
-     * @throws IllegalArgumentException if the request method is not supported
-     * @throws SecurityException If a security manager has been installed
-     *          and it denies {@link java.net.URLPermission access} to the
-     *          URL in the given request, or proxy if one is configured.
-     *          See HttpRequest for further information about
-     *          <a href="HttpRequest.html#securitychecks">security checks</a>.
-     */
-    public abstract <T> HttpResponse<T>
-    send(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)
-        throws IOException, InterruptedException;
-
-    /**
-     * Sends the given request asynchronously using this client and the given
-     * response handler.
-     *
-     * <p> The returned completable future completes exceptionally with:
-     * <ul>
-     * <li>{@link IOException} - if an I/O error occurs when sending or receiving</li>
-     * <li>{@link IllegalArgumentException} - if the request method is not supported</li>
-     * <li>{@link SecurityException} - If a security manager has been installed
-     *          and it denies {@link java.net.URLPermission access} to the
-     *          URL in the given request, or proxy if one is configured.
-     *          See HttpRequest for further information about
-     *          <a href="HttpRequest.html#securitychecks">security checks</a>.</li>
-     * </ul>
-     *
-     * @param <T> the response body type
-     * @param req the request
-     * @param responseBodyHandler the response body handler
-     * @return a {@code CompletableFuture<HttpResponse<T>>}
-     */
-    public abstract <T> CompletableFuture<HttpResponse<T>>
-    sendAsync(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler);
-
-    /**
-     * Sends the given request asynchronously using this client and the given
-     * multi response handler.
-     *
-     * <p> The returned completable future completes exceptionally with:
-     * <ul>
-     * <li>{@link IOException} - if an I/O error occurs when sending or receiving</li>
-     * <li>{@link IllegalArgumentException} - if the request method is not supported</li>
-     * <li>{@link SecurityException} - If a security manager has been installed
-     *          and it denies {@link java.net.URLPermission access} to the
-     *          URL in the given request, or proxy if one is configured.
-     *          See HttpRequest for further information about
-     *          <a href="HttpRequest.html#securitychecks">security checks</a>.</li>
-     * </ul>
-     *
-     * @param <U> a type representing the aggregated results
-     * @param <T> a type representing all of the response bodies
-     * @param req the request
-     * @param multiSubscriber the multiSubscriber for the request
-     * @return a {@code CompletableFuture<U>}
-     */
-    public abstract <U, T> CompletableFuture<U>
-    sendAsync(HttpRequest req, HttpResponse.MultiSubscriber<U, T> multiSubscriber);
-
-    /**
-     * Creates a new {@code WebSocket} builder (optional operation).
-     *
-     * <p> <b>Example</b>
-     * <pre>{@code
-     *     HttpClient client = HttpClient.newHttpClient();
-     *     CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
-     *             .buildAsync(URI.create("ws://websocket.example.com"), listener);
-     * }</pre>
-     *
-     * <p> Finer control over the WebSocket Opening Handshake can be achieved
-     * by using a custom {@code HttpClient}.
-     *
-     * <p> <b>Example</b>
-     * <pre>{@code
-     *     InetSocketAddress addr = new InetSocketAddress("proxy.example.com", 80);
-     *     HttpClient client = HttpClient.newBuilder()
-     *             .proxy(ProxySelector.of(addr))
-     *             .build();
-     *     CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
-     *             .buildAsync(URI.create("ws://websocket.example.com"), listener);
-     * }</pre>
-     *
-     * <p> A {@code WebSocket.Builder} returned from this method is not safe for
-     * use by multiple threads without external synchronization.
-     *
-     * @implSpec The default implementation of this method throws
-     * {@code UnsupportedOperationException}. Clients obtained through
-     * {@link HttpClient#newHttpClient()} or {@link HttpClient#newBuilder()}
-     * return a {@code WebSocket} builder.
-     *
-     * @implNote Both builder and {@code WebSocket}s created with it operate in
-     * a non-blocking fashion. That is, their methods do not block before
-     * returning a {@code CompletableFuture}. Asynchronous tasks are executed in
-     * this {@code HttpClient}'s executor.
-     *
-     * <p> When a {@code CompletionStage} returned from
-     * {@link WebSocket.Listener#onClose Listener.onClose} completes,
-     * the {@code WebSocket} will send a Close message that has the same code
-     * the received message has and an empty reason.
-     *
-     * @return a {@code WebSocket.Builder}
-     * @throws UnsupportedOperationException
-     *         if this {@code HttpClient} does not provide WebSocket support
-     */
-    public WebSocket.Builder newWebSocketBuilder() {
-        throw new UnsupportedOperationException();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientBuilderImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ProxySelector;
-import java.util.concurrent.Executor;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.internal.common.Utils;
-import static java.util.Objects.requireNonNull;
-
-class HttpClientBuilderImpl extends HttpClient.Builder {
-
-    CookieHandler cookieHandler;
-    HttpClient.Redirect followRedirects;
-    ProxySelector proxy;
-    Authenticator authenticator;
-    HttpClient.Version version;
-    Executor executor;
-    // Security parameters
-    SSLContext sslContext;
-    SSLParameters sslParams;
-    int priority = -1;
-
-    @Override
-    public HttpClientBuilderImpl cookieHandler(CookieHandler cookieHandler) {
-        requireNonNull(cookieHandler);
-        this.cookieHandler = cookieHandler;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl sslContext(SSLContext sslContext) {
-        requireNonNull(sslContext);
-        this.sslContext = sslContext;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl sslParameters(SSLParameters sslParameters) {
-        requireNonNull(sslParameters);
-        this.sslParams = Utils.copySSLParameters(sslParameters);
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl executor(Executor s) {
-        requireNonNull(s);
-        this.executor = s;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl followRedirects(HttpClient.Redirect policy) {
-        requireNonNull(policy);
-        this.followRedirects = policy;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl version(HttpClient.Version version) {
-        requireNonNull(version);
-        this.version = version;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl priority(int priority) {
-        if (priority < 1 || priority > 256) {
-            throw new IllegalArgumentException("priority must be between 1 and 256");
-        }
-        this.priority = priority;
-        return this;
-    }
-
-    @Override
-    public HttpClientBuilderImpl proxy(ProxySelector proxy) {
-        requireNonNull(proxy);
-        this.proxy = proxy;
-        return this;
-    }
-
-
-    @Override
-    public HttpClientBuilderImpl authenticator(Authenticator a) {
-        requireNonNull(a);
-        this.authenticator = a;
-        return this;
-    }
-
-    @Override
-    public HttpClient build() {
-        return HttpClientImpl.create(this);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientFacade.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.ref.Reference;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-
-/**
- * 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 <U, T> CompletableFuture<U>
-    sendAsync(HttpRequest req, HttpResponse.MultiSubscriber<U, T> multiSubscriber) {
-        try {
-            return impl.sendAsync(req, multiSubscriber);
-        } finally {
-            Reference.reachabilityFence(this);
-        }
-    }
-
-    @Override
-    public WebSocket.Builder newWebSocketBuilder() {
-        try {
-            return impl.newWebSocketBuilder();
-        } finally {
-            Reference.reachabilityFence(this);
-        }
-    }
-
-    @Override
-    public String toString() {
-        // Used by tests to get the client's id.
-        return impl.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1044 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.lang.ref.WeakReference;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ProxySelector;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivilegedAction;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Stream;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.MultiSubscriber;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Pair;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.websocket.BuilderImpl;
-import jdk.internal.misc.InnocuousThread;
-
-/**
- * Client implementation. Contains all configuration information and also
- * the selector manager thread which allows async events to be registered
- * and delivered when they occur. See AsyncEvent.
- */
-class HttpClientImpl extends HttpClient {
-
-    static final boolean DEBUG = Utils.DEBUG;  // Revisit: temporary dev flag.
-    static final boolean DEBUGELAPSED = Utils.TESTING || DEBUG;  // Revisit: temporary dev flag.
-    static final boolean DEBUGTIMEOUT = false; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    final System.Logger  debugelapsed = Utils.getDebugLogger(this::dbgString, DEBUGELAPSED);
-    final System.Logger  debugtimeout = Utils.getDebugLogger(this::dbgString, DEBUGTIMEOUT);
-    static final AtomicLong CLIENT_IDS = new AtomicLong();
-
-    // Define the default factory as a static inner class
-    // that embeds all the necessary logic to avoid
-    // the risk of using a lambda that might keep a reference on the
-    // HttpClient instance from which it was created (helps with
-    // heapdump analysis).
-    private static final class DefaultThreadFactory implements ThreadFactory {
-        private final String namePrefix;
-        private final AtomicInteger nextId = new AtomicInteger();
-
-        DefaultThreadFactory(long clientID) {
-            namePrefix = "HttpClient-" + clientID + "-Worker-";
-        }
-
-        @Override
-        public Thread newThread(Runnable r) {
-            String name = namePrefix + nextId.getAndIncrement();
-            Thread t;
-            if (System.getSecurityManager() == null) {
-                t = new Thread(null, r, name, 0, false);
-            } else {
-                t = InnocuousThread.newThread(name, r);
-            }
-            t.setDaemon(true);
-            return t;
-        }
-    }
-
-    private final CookieHandler cookieHandler;
-    private final Redirect followRedirects;
-    private final Optional<ProxySelector> userProxySelector;
-    private final ProxySelector proxySelector;
-    private final Authenticator authenticator;
-    private final Version version;
-    private final ConnectionPool connections;
-    private final Executor executor;
-    private final boolean isDefaultExecutor;
-    // Security parameters
-    private final SSLContext sslContext;
-    private final SSLParameters sslParams;
-    private final SelectorManager selmgr;
-    private final FilterFactory filters;
-    private final Http2ClientImpl client2;
-    private final long id;
-    private final String dbgTag;
-
-    // This reference is used to keep track of the facade HttpClient
-    // that was returned to the application code.
-    // It makes it possible to know when the application no longer
-    // holds any reference to the HttpClient.
-    // Unfortunately, this information is not enough to know when
-    // to exit the SelectorManager thread. Because of the asynchronous
-    // nature of the API, we also need to wait until all pending operations
-    // have completed.
-    private final WeakReference<HttpClientFacade> facadeRef;
-
-    // This counter keeps track of the number of operations pending
-    // on the HttpClient. The SelectorManager thread will wait
-    // until there are no longer any pending operations and the
-    // facadeRef is cleared before exiting.
-    //
-    // The pendingOperationCount is incremented every time a send/sendAsync
-    // operation is invoked on the HttpClient, and is decremented when
-    // the HttpResponse<T> object is returned to the user.
-    // However, at this point, the body may not have been fully read yet.
-    // This is the case when the response T is implemented as a streaming
-    // subscriber (such as an InputStream).
-    //
-    // To take care of this issue the pendingOperationCount will additionally
-    // be incremented/decremented in the following cases:
-    //
-    // 1. For HTTP/2  it is incremented when a stream is added to the
-    //    Http2Connection streams map, and decreased when the stream is removed
-    //    from the map. This should also take care of push promises.
-    // 2. For WebSocket the count is increased when creating a
-    //    DetachedConnectionChannel for the socket, and decreased
-    //    when the the channel is closed.
-    //    In addition, the HttpClient facade is passed to the WebSocket builder,
-    //    (instead of the client implementation delegate).
-    // 3. For HTTP/1.1 the count is incremented before starting to parse the body
-    //    response, and decremented when the parser has reached the end of the
-    //    response body flow.
-    //
-    // This should ensure that the selector manager thread remains alive until
-    // the response has been fully received or the web socket is closed.
-    private final AtomicLong pendingOperationCount = new AtomicLong();
-    private final AtomicLong pendingWebSocketCount = new AtomicLong();
-    private final AtomicLong pendingHttpRequestCount = new AtomicLong();
-
-    /** A Set of, deadline first, ordered timeout events. */
-    private final TreeSet<TimeoutEvent> timeouts;
-
-    /**
-     * This is a bit tricky:
-     * 1. an HttpClientFacade has a final HttpClientImpl field.
-     * 2. an HttpClientImpl has a final WeakReference<HttpClientFacade> field,
-     *    where the referent is the facade created for that instance.
-     * 3. We cannot just create the HttpClientFacade in the HttpClientImpl
-     *    constructor, because it would be only weakly referenced and could
-     *    be GC'ed before we can return it.
-     * The solution is to use an instance of SingleFacadeFactory which will
-     * allow the caller of new HttpClientImpl(...) to retrieve the facade
-     * after the HttpClientImpl has been created.
-     */
-    private static final class SingleFacadeFactory {
-        HttpClientFacade facade;
-        HttpClientFacade createFacade(HttpClientImpl impl) {
-            assert facade == null;
-            return (facade = new HttpClientFacade(impl));
-        }
-    }
-
-    static HttpClientFacade create(HttpClientBuilderImpl builder) {
-        SingleFacadeFactory facadeFactory = new SingleFacadeFactory();
-        HttpClientImpl impl = new HttpClientImpl(builder, facadeFactory);
-        impl.start();
-        assert facadeFactory.facade != null;
-        assert impl.facadeRef.get() == facadeFactory.facade;
-        return facadeFactory.facade;
-    }
-
-    private HttpClientImpl(HttpClientBuilderImpl builder,
-                           SingleFacadeFactory facadeFactory) {
-        id = CLIENT_IDS.incrementAndGet();
-        dbgTag = "HttpClientImpl(" + id +")";
-        if (builder.sslContext == null) {
-            try {
-                sslContext = SSLContext.getDefault();
-            } catch (NoSuchAlgorithmException ex) {
-                throw new InternalError(ex);
-            }
-        } else {
-            sslContext = builder.sslContext;
-        }
-        Executor ex = builder.executor;
-        if (ex == null) {
-            ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
-            isDefaultExecutor = true;
-        } else {
-            ex = builder.executor;
-            isDefaultExecutor = false;
-        }
-        facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
-        client2 = new Http2ClientImpl(this);
-        executor = ex;
-        cookieHandler = builder.cookieHandler;
-        followRedirects = builder.followRedirects == null ?
-                Redirect.NEVER : builder.followRedirects;
-        this.userProxySelector = Optional.ofNullable(builder.proxy);
-        this.proxySelector = userProxySelector
-                .orElseGet(HttpClientImpl::getDefaultProxySelector);
-        debug.log(Level.DEBUG, "proxySelector is %s (user-supplied=%s)",
-                this.proxySelector, userProxySelector.isPresent());
-        authenticator = builder.authenticator;
-        if (builder.version == null) {
-            version = HttpClient.Version.HTTP_2;
-        } else {
-            version = builder.version;
-        }
-        if (builder.sslParams == null) {
-            sslParams = getDefaultParams(sslContext);
-        } else {
-            sslParams = builder.sslParams;
-        }
-        connections = new ConnectionPool(id);
-        connections.start();
-        timeouts = new TreeSet<>();
-        try {
-            selmgr = new SelectorManager(this);
-        } catch (IOException e) {
-            // unlikely
-            throw new InternalError(e);
-        }
-        selmgr.setDaemon(true);
-        filters = new FilterFactory();
-        initFilters();
-        assert facadeRef.get() != null;
-    }
-
-    private void start() {
-        selmgr.start();
-    }
-
-    // Called from the SelectorManager thread, just before exiting.
-    // Clears the HTTP/1.1 and HTTP/2 cache, ensuring that the connections
-    // that may be still lingering there are properly closed (and their
-    // possibly still opened SocketChannel released).
-    private void stop() {
-        // Clears HTTP/1.1 cache and close its connections
-        connections.stop();
-        // Clears HTTP/2 cache and close its connections.
-        client2.stop();
-    }
-
-    private static SSLParameters getDefaultParams(SSLContext ctx) {
-        SSLParameters params = ctx.getSupportedSSLParameters();
-        params.setProtocols(new String[]{"TLSv1.2"});
-        return params;
-    }
-
-    private static ProxySelector getDefaultProxySelector() {
-        PrivilegedAction<ProxySelector> action = ProxySelector::getDefault;
-        return AccessController.doPrivileged(action);
-    }
-
-    // Returns the facade that was returned to the application code.
-    // May be null if that facade is no longer referenced.
-    final HttpClientFacade facade() {
-        return facadeRef.get();
-    }
-
-    // Increments the pendingOperationCount.
-    final long reference() {
-        pendingHttpRequestCount.incrementAndGet();
-        return pendingOperationCount.incrementAndGet();
-    }
-
-    // Decrements the pendingOperationCount.
-    final long unreference() {
-        final long count = pendingOperationCount.decrementAndGet();
-        final long httpCount = pendingHttpRequestCount.decrementAndGet();
-        final long webSocketCount = pendingWebSocketCount.get();
-        if (count == 0 && facade() == null) {
-            selmgr.wakeupSelector();
-        }
-        assert httpCount >= 0 : "count of HTTP operations < 0";
-        assert webSocketCount >= 0 : "count of WS operations < 0";
-        assert count >= 0 : "count of pending operations < 0";
-        return count;
-    }
-
-    // Increments the pendingOperationCount.
-    final long webSocketOpen() {
-        pendingWebSocketCount.incrementAndGet();
-        return pendingOperationCount.incrementAndGet();
-    }
-
-    // Decrements the pendingOperationCount.
-    final long webSocketClose() {
-        final long count = pendingOperationCount.decrementAndGet();
-        final long webSocketCount = pendingWebSocketCount.decrementAndGet();
-        final long httpCount = pendingHttpRequestCount.get();
-        if (count == 0 && facade() == null) {
-            selmgr.wakeupSelector();
-        }
-        assert httpCount >= 0 : "count of HTTP operations < 0";
-        assert webSocketCount >= 0 : "count of WS operations < 0";
-        assert count >= 0 : "count of pending operations < 0";
-        return count;
-    }
-
-    // Returns the pendingOperationCount.
-    final long referenceCount() {
-        return pendingOperationCount.get();
-    }
-
-    // Called by the SelectorManager thread to figure out whether it's time
-    // to terminate.
-    final boolean isReferenced() {
-        HttpClient facade = facade();
-        return facade != null || referenceCount() > 0;
-    }
-
-    /**
-     * Wait for activity on given exchange.
-     * The following occurs in the SelectorManager thread.
-     *
-     *  1) add to selector
-     *  2) If selector fires for this exchange then
-     *     call AsyncEvent.handle()
-     *
-     * If exchange needs to change interest ops, then call registerEvent() again.
-     */
-    void registerEvent(AsyncEvent exchange) throws IOException {
-        selmgr.register(exchange);
-    }
-
-    /**
-     * Only used from RawChannel to disconnect the channel from
-     * the selector
-     */
-    void cancelRegistration(SocketChannel s) {
-        selmgr.cancel(s);
-    }
-
-    /**
-     * Allows an AsyncEvent to modify its interestOps.
-     * @param event The modified event.
-     */
-    void eventUpdated(AsyncEvent event) throws ClosedChannelException {
-        assert !(event instanceof AsyncTriggerEvent);
-        selmgr.eventUpdated(event);
-    }
-
-    boolean isSelectorThread() {
-        return Thread.currentThread() == selmgr;
-    }
-
-    Http2ClientImpl client2() {
-        return client2;
-    }
-
-    private void debugCompleted(String tag, long startNanos, HttpRequest req) {
-        if (debugelapsed.isLoggable(Level.DEBUG)) {
-            debugelapsed.log(Level.DEBUG, () -> tag + " elapsed "
-                    + (System.nanoTime() - startNanos)/1000_000L
-                    + " millis for " + req.method()
-                    + " to " + req.uri());
-        }
-    }
-
-    @Override
-    public <T> HttpResponse<T>
-    send(HttpRequest req, BodyHandler<T> responseHandler)
-        throws IOException, InterruptedException
-    {
-        try {
-            return sendAsync(req, responseHandler).get();
-        } catch (ExecutionException e) {
-            Throwable t = e.getCause();
-            if (t instanceof Error)
-                throw (Error)t;
-            if (t instanceof RuntimeException)
-                throw (RuntimeException)t;
-            else if (t instanceof IOException)
-                throw Utils.getIOException(t);
-            else
-                throw new InternalError("Unexpected exception", t);
-        }
-    }
-
-    @Override
-    public <T> CompletableFuture<HttpResponse<T>>
-    sendAsync(HttpRequest userRequest, BodyHandler<T> responseHandler)
-    {
-        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<Void,T> mex = new MultiExchange<>(userRequest,
-                                                            requestImpl,
-                                                            this,
-                                                            responseHandler,
-                                                            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;
-        }
-    }
-
-    @Override
-    public <U, T> CompletableFuture<U>
-    sendAsync(HttpRequest userRequest, MultiSubscriber<U, T> responseHandler) {
-        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 multi %s", userRequest);
-
-            MultiExchange<U,T> mex = new MultiExchange<>(userRequest,
-                                                         requestImpl,
-                                                         this,
-                                                         responseHandler,
-                                                         acc);
-            CompletableFuture<U> res = mex.multiResponseAsync()
-                      .whenComplete((b,t) -> unreference());
-            if (DEBUGELAPSED) {
-                res = res.whenComplete(
-                        (b,t) -> debugCompleted("ClientImpl (async)", start, userRequest));
-            }
-            // makes sure that any dependent actions happen in the executor
-            if (acc != null) {
-                res.whenCompleteAsync((r, t) -> { /* do nothing */},
-                                      new PrivilegedExecutor(executor, acc));
-            }
-
-            return res;
-        } catch(Throwable t) {
-            unreference();
-            debugCompleted("ClientImpl (async)", start, userRequest);
-            throw t;
-        }
-    }
-
-    // Main loop for this client's selector
-    private final static class SelectorManager extends Thread {
-
-        // For testing purposes we have an internal System property that
-        // can control the frequency at which the selector manager will wake
-        // up when there are no pending operations.
-        // Increasing the frequency (shorter delays) might allow the selector
-        // to observe that the facade is no longer referenced and might allow
-        // the selector thread to terminate more timely - for when nothing is
-        // ongoing it will only check for that condition every NODEADLINE ms.
-        // To avoid misuse of the property, the delay that can be specified
-        // is comprised between [MIN_NODEADLINE, MAX_NODEADLINE], and its default
-        // value if unspecified (or <= 0) is DEF_NODEADLINE = 3000ms
-        // The property is -Djdk.httpclient.internal.selector.timeout=<millis>
-        private static final int MIN_NODEADLINE = 1000; // ms
-        private static final int MAX_NODEADLINE = 1000 * 1200; // ms
-        private static final int DEF_NODEADLINE = 3000; // ms
-        private static final long NODEADLINE; // default is DEF_NODEADLINE ms
-        static {
-            // ensure NODEADLINE is initialized with some valid value.
-            long deadline =  Utils.getIntegerNetProperty(
-                "jdk.httpclient.internal.selector.timeout",
-                DEF_NODEADLINE); // millis
-            if (deadline <= 0) deadline = DEF_NODEADLINE;
-            deadline = Math.max(deadline, MIN_NODEADLINE);
-            NODEADLINE = Math.min(deadline, MAX_NODEADLINE);
-        }
-
-        private final Selector selector;
-        private volatile boolean closed;
-        private final List<AsyncEvent> registrations;
-        private final System.Logger debug;
-        private final System.Logger debugtimeout;
-        HttpClientImpl owner;
-        ConnectionPool pool;
-
-        SelectorManager(HttpClientImpl ref) throws IOException {
-            super(null, null, "HttpClient-" + ref.id + "-SelectorManager", 0, false);
-            owner = ref;
-            debug = ref.debug;
-            debugtimeout = ref.debugtimeout;
-            pool = ref.connectionPool();
-            registrations = new ArrayList<>();
-            selector = Selector.open();
-        }
-
-        void eventUpdated(AsyncEvent e) throws ClosedChannelException {
-            if (Thread.currentThread() == this) {
-                SelectionKey key = e.channel().keyFor(selector);
-                SelectorAttachment sa = (SelectorAttachment) key.attachment();
-                if (sa != null) sa.register(e);
-            } else {
-                register(e);
-            }
-        }
-
-        // This returns immediately. So caller not allowed to send/receive
-        // on connection.
-        synchronized void register(AsyncEvent e) {
-            registrations.add(e);
-            selector.wakeup();
-        }
-
-        synchronized void cancel(SocketChannel e) {
-            SelectionKey key = e.keyFor(selector);
-            if (key != null) {
-                key.cancel();
-            }
-            selector.wakeup();
-        }
-
-        void wakeupSelector() {
-            selector.wakeup();
-        }
-
-        synchronized void shutdown() {
-            debug.log(Level.DEBUG, "SelectorManager shutting down");
-            closed = true;
-            try {
-                selector.close();
-            } catch (IOException ignored) {
-            } finally {
-                owner.stop();
-            }
-        }
-
-        @Override
-        public void run() {
-            List<Pair<AsyncEvent,IOException>> errorList = new ArrayList<>();
-            List<AsyncEvent> readyList = new ArrayList<>();
-            try {
-                while (!Thread.currentThread().isInterrupted()) {
-                    synchronized (this) {
-                        assert errorList.isEmpty();
-                        assert readyList.isEmpty();
-                        for (AsyncEvent event : registrations) {
-                            if (event instanceof AsyncTriggerEvent) {
-                                readyList.add(event);
-                                continue;
-                            }
-                            SelectableChannel chan = event.channel();
-                            SelectionKey key = null;
-                            try {
-                                key = chan.keyFor(selector);
-                                SelectorAttachment sa;
-                                if (key == null || !key.isValid()) {
-                                    if (key != null) {
-                                        // key is canceled.
-                                        // invoke selectNow() to purge it
-                                        // before registering the new event.
-                                        selector.selectNow();
-                                    }
-                                    sa = new SelectorAttachment(chan, selector);
-                                } else {
-                                    sa = (SelectorAttachment) key.attachment();
-                                }
-                                // may throw IOE if channel closed: that's OK
-                                sa.register(event);
-                                if (!chan.isOpen()) {
-                                    throw new IOException("Channel closed");
-                                }
-                            } catch (IOException e) {
-                                Log.logTrace("HttpClientImpl: " + e);
-                                debug.log(Level.DEBUG, () ->
-                                        "Got " + e.getClass().getName()
-                                                 + " while handling"
-                                                 + " registration events");
-                                chan.close();
-                                // let the event abort deal with it
-                                errorList.add(new Pair<>(event, e));
-                                if (key != null) {
-                                    key.cancel();
-                                    selector.selectNow();
-                                }
-                            }
-                        }
-                        registrations.clear();
-                        selector.selectedKeys().clear();
-                    }
-
-                    for (AsyncEvent event : readyList) {
-                        assert event instanceof AsyncTriggerEvent;
-                        event.handle();
-                    }
-                    readyList.clear();
-
-                    for (Pair<AsyncEvent,IOException> error : errorList) {
-                        // an IOException was raised and the channel closed.
-                        handleEvent(error.first, error.second);
-                    }
-                    errorList.clear();
-
-                    // Check whether client is still alive, and if not,
-                    // gracefully stop this thread
-                    if (!owner.isReferenced()) {
-                        Log.logTrace("HttpClient no longer referenced. Exiting...");
-                        return;
-                    }
-
-                    // Timeouts will have milliseconds granularity. It is important
-                    // to handle them in a timely fashion.
-                    long nextTimeout = owner.purgeTimeoutsAndReturnNextDeadline();
-                    debugtimeout.log(Level.DEBUG, "next timeout: %d", nextTimeout);
-
-                    // Keep-alive have seconds granularity. It's not really an
-                    // issue if we keep connections linger a bit more in the keep
-                    // alive cache.
-                    long nextExpiry = pool.purgeExpiredConnectionsAndReturnNextDeadline();
-                    debugtimeout.log(Level.DEBUG, "next expired: %d", nextExpiry);
-
-                    assert nextTimeout >= 0;
-                    assert nextExpiry >= 0;
-
-                    // Don't wait for ever as it might prevent the thread to
-                    // stop gracefully. millis will be 0 if no deadline was found.
-                    if (nextTimeout <= 0) nextTimeout = NODEADLINE;
-
-                    // Clip nextExpiry at NODEADLINE limit. The default
-                    // keep alive is 1200 seconds (half an hour) - we don't
-                    // want to wait that long.
-                    if (nextExpiry <= 0) nextExpiry = NODEADLINE;
-                    else nextExpiry = Math.min(NODEADLINE, nextExpiry);
-
-                    // takes the least of the two.
-                    long millis = Math.min(nextExpiry, nextTimeout);
-
-                    debugtimeout.log(Level.DEBUG, "Next deadline is %d",
-                                     (millis == 0 ? NODEADLINE : millis));
-                    //debugPrint(selector);
-                    int n = selector.select(millis == 0 ? NODEADLINE : millis);
-                    if (n == 0) {
-                        // Check whether client is still alive, and if not,
-                        // gracefully stop this thread
-                        if (!owner.isReferenced()) {
-                            Log.logTrace("HttpClient no longer referenced. Exiting...");
-                            return;
-                        }
-                        owner.purgeTimeoutsAndReturnNextDeadline();
-                        continue;
-                    }
-                    Set<SelectionKey> keys = selector.selectedKeys();
-
-                    assert errorList.isEmpty();
-                    for (SelectionKey key : keys) {
-                        SelectorAttachment sa = (SelectorAttachment) key.attachment();
-                        if (!key.isValid()) {
-                            IOException ex = sa.chan.isOpen()
-                                    ? new IOException("Invalid key")
-                                    : new ClosedChannelException();
-                            sa.pending.forEach(e -> errorList.add(new Pair<>(e,ex)));
-                            sa.pending.clear();
-                            continue;
-                        }
-
-                        int eventsOccurred;
-                        try {
-                            eventsOccurred = key.readyOps();
-                        } catch (CancelledKeyException ex) {
-                            IOException io = Utils.getIOException(ex);
-                            sa.pending.forEach(e -> errorList.add(new Pair<>(e,io)));
-                            sa.pending.clear();
-                            continue;
-                        }
-                        sa.events(eventsOccurred).forEach(readyList::add);
-                        sa.resetInterestOps(eventsOccurred);
-                    }
-                    selector.selectNow(); // complete cancellation
-                    selector.selectedKeys().clear();
-
-                    for (AsyncEvent event : readyList) {
-                        handleEvent(event, null); // will be delegated to executor
-                    }
-                    readyList.clear();
-                    errorList.forEach((p) -> handleEvent(p.first, p.second));
-                    errorList.clear();
-                }
-            } catch (Throwable e) {
-                //e.printStackTrace();
-                if (!closed) {
-                    // This terminates thread. So, better just print stack trace
-                    String err = Utils.stackTrace(e);
-                    Log.logError("HttpClientImpl: fatal error: " + err);
-                }
-                debug.log(Level.DEBUG, "shutting down", e);
-                if (Utils.ASSERTIONSENABLED && !debug.isLoggable(Level.DEBUG)) {
-                    e.printStackTrace(System.err); // always print the stack
-                }
-            } finally {
-                shutdown();
-            }
-        }
-
-//        void debugPrint(Selector selector) {
-//            System.err.println("Selector: debugprint start");
-//            Set<SelectionKey> keys = selector.keys();
-//            for (SelectionKey key : keys) {
-//                SelectableChannel c = key.channel();
-//                int ops = key.interestOps();
-//                System.err.printf("selector chan:%s ops:%d\n", c, ops);
-//            }
-//            System.err.println("Selector: debugprint end");
-//        }
-
-        /** Handles the given event. The given ioe may be null. */
-        void handleEvent(AsyncEvent event, IOException ioe) {
-            if (closed || ioe != null) {
-                event.abort(ioe);
-            } else {
-                event.handle();
-            }
-        }
-    }
-
-    /**
-     * Tracks multiple user level registrations associated with one NIO
-     * registration (SelectionKey). In this implementation, registrations
-     * are one-off and when an event is posted the registration is cancelled
-     * until explicitly registered again.
-     *
-     * <p> No external synchronization required as this class is only used
-     * by the SelectorManager thread. One of these objects required per
-     * connection.
-     */
-    private static class SelectorAttachment {
-        private final SelectableChannel chan;
-        private final Selector selector;
-        private final Set<AsyncEvent> pending;
-        private final static System.Logger debug =
-                Utils.getDebugLogger("SelectorAttachment"::toString, DEBUG);
-        private int interestOps;
-
-        SelectorAttachment(SelectableChannel chan, Selector selector) {
-            this.pending = new HashSet<>();
-            this.chan = chan;
-            this.selector = selector;
-        }
-
-        void register(AsyncEvent e) throws ClosedChannelException {
-            int newOps = e.interestOps();
-            boolean reRegister = (interestOps & newOps) != newOps;
-            interestOps |= newOps;
-            pending.add(e);
-            if (reRegister) {
-                // first time registration happens here also
-                chan.register(selector, interestOps, this);
-            }
-        }
-
-        /**
-         * Returns a Stream<AsyncEvents> containing only events that are
-         * registered with the given {@code interestOps}.
-         */
-        Stream<AsyncEvent> events(int interestOps) {
-            return pending.stream()
-                    .filter(ev -> (ev.interestOps() & interestOps) != 0);
-        }
-
-        /**
-         * Removes any events with the given {@code interestOps}, and if no
-         * events remaining, cancels the associated SelectionKey.
-         */
-        void resetInterestOps(int interestOps) {
-            int newOps = 0;
-
-            Iterator<AsyncEvent> itr = pending.iterator();
-            while (itr.hasNext()) {
-                AsyncEvent event = itr.next();
-                int evops = event.interestOps();
-                if (event.repeating()) {
-                    newOps |= evops;
-                    continue;
-                }
-                if ((evops & interestOps) != 0) {
-                    itr.remove();
-                } else {
-                    newOps |= evops;
-                }
-            }
-
-            this.interestOps = newOps;
-            SelectionKey key = chan.keyFor(selector);
-            if (newOps == 0 && pending.isEmpty()) {
-                key.cancel();
-            } else {
-                try {
-                    key.interestOps(newOps);
-                } catch (CancelledKeyException x) {
-                    // channel may have been closed
-                    debug.log(Level.DEBUG, "key cancelled for " + chan);
-                    abortPending(x);
-                }
-            }
-        }
-
-        void abortPending(Throwable x) {
-            if (!pending.isEmpty()) {
-                AsyncEvent[] evts = pending.toArray(new AsyncEvent[0]);
-                pending.clear();
-                IOException io = Utils.getIOException(x);
-                for (AsyncEvent event : evts) {
-                    event.abort(io);
-                }
-            }
-        }
-    }
-
-    /*package-private*/ SSLContext theSSLContext() {
-        return sslContext;
-    }
-
-    @Override
-    public SSLContext sslContext() {
-        return sslContext;
-    }
-
-    @Override
-    public SSLParameters sslParameters() {
-        return Utils.copySSLParameters(sslParams);
-    }
-
-    @Override
-    public Optional<Authenticator> authenticator() {
-        return Optional.ofNullable(authenticator);
-    }
-
-    /*package-private*/ final Executor theExecutor() {
-        return executor;
-    }
-
-    @Override
-    public final Optional<Executor> executor() {
-        return isDefaultExecutor ? Optional.empty() : Optional.of(executor);
-    }
-
-    ConnectionPool connectionPool() {
-        return connections;
-    }
-
-    @Override
-    public Redirect followRedirects() {
-        return followRedirects;
-    }
-
-
-    @Override
-    public Optional<CookieHandler> cookieHandler() {
-        return Optional.ofNullable(cookieHandler);
-    }
-
-    @Override
-    public Optional<ProxySelector> proxy() {
-        return this.userProxySelector;
-    }
-
-    // Return the effective proxy that this client uses.
-    ProxySelector proxySelector() {
-        return proxySelector;
-    }
-
-    @Override
-    public WebSocket.Builder newWebSocketBuilder() {
-        // Make sure to pass the HttpClientFacade to the WebSocket builder.
-        // This will ensure that the facade is not released before the
-        // WebSocket has been created, at which point the pendingOperationCount
-        // will have been incremented by the DetachedConnectionChannel
-        // (see PlainHttpConnection.detachChannel())
-        return new BuilderImpl(this.facade(), proxySelector);
-    }
-
-    @Override
-    public Version version() {
-        return version;
-    }
-
-    String dbgString() {
-        return dbgTag;
-    }
-
-    @Override
-    public String toString() {
-        // Used by tests to get the client's id and compute the
-        // name of the SelectorManager thread.
-        return super.toString() + ("(" + id + ")");
-    }
-
-    private void initFilters() {
-        addFilter(AuthenticationFilter.class);
-        addFilter(RedirectFilter.class);
-        if (this.cookieHandler != null) {
-            addFilter(CookieFilter.class);
-        }
-    }
-
-    private void addFilter(Class<? extends HeaderFilter> f) {
-        filters.addFilter(f);
-    }
-
-    final List<HeaderFilter> filterChain() {
-        return filters.getFilterChain();
-    }
-
-    // Timer controls.
-    // Timers are implemented through timed Selector.select() calls.
-
-    synchronized void registerTimer(TimeoutEvent event) {
-        Log.logTrace("Registering timer {0}", event);
-        timeouts.add(event);
-        selmgr.wakeupSelector();
-    }
-
-    synchronized void cancelTimer(TimeoutEvent event) {
-        Log.logTrace("Canceling timer {0}", event);
-        timeouts.remove(event);
-    }
-
-    /**
-     * Purges ( handles ) timer events that have passed their deadline, and
-     * returns the amount of time, in milliseconds, until the next earliest
-     * event. A return value of 0 means that there are no events.
-     */
-    private long purgeTimeoutsAndReturnNextDeadline() {
-        long diff = 0L;
-        List<TimeoutEvent> toHandle = null;
-        int remaining = 0;
-        // enter critical section to retrieve the timeout event to handle
-        synchronized(this) {
-            if (timeouts.isEmpty()) return 0L;
-
-            Instant now = Instant.now();
-            Iterator<TimeoutEvent> itr = timeouts.iterator();
-            while (itr.hasNext()) {
-                TimeoutEvent event = itr.next();
-                diff = now.until(event.deadline(), ChronoUnit.MILLIS);
-                if (diff <= 0) {
-                    itr.remove();
-                    toHandle = (toHandle == null) ? new ArrayList<>() : toHandle;
-                    toHandle.add(event);
-                } else {
-                    break;
-                }
-            }
-            remaining = timeouts.size();
-        }
-
-        // can be useful for debugging
-        if (toHandle != null && Log.trace()) {
-            Log.logTrace("purgeTimeoutsAndReturnNextDeadline: handling "
-                    + (toHandle == null ? 0 : toHandle.size()) + " events, "
-                    + "remaining " + remaining
-                    + ", next deadline: " + (diff < 0 ? 0L : diff));
-        }
-
-        // handle timeout events out of critical section
-        if (toHandle != null) {
-            Throwable failed = null;
-            for (TimeoutEvent event : toHandle) {
-                try {
-                   Log.logTrace("Firing timer {0}", event);
-                   event.handle();
-                } catch (Error | RuntimeException e) {
-                    // Not expected. Handle remaining events then throw...
-                    // If e is an OOME or SOE it might simply trigger a new
-                    // error from here - but in this case there's not much we
-                    // could do anyway. Just let it flow...
-                    if (failed == null) failed = e;
-                    else failed.addSuppressed(e);
-                    Log.logTrace("Failed to handle event {0}: {1}", event, e);
-                }
-            }
-            if (failed instanceof Error) throw (Error) failed;
-            if (failed instanceof RuntimeException) throw (RuntimeException) failed;
-        }
-
-        // return time to wait until next event. 0L if there's no more events.
-        return diff < 0 ? 0L : diff;
-    }
-
-    // used for the connection window
-    int getReceiveBufferSize() {
-        return Utils.getIntegerNetProperty(
-                "jdk.httpclient.receiveBufferSize", 2 * 1024 * 1024
-        );
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,410 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.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.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Flow;
-import jdk.incubator.http.HttpClient.Version;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
-
-/**
- * Wraps socket channel layer and takes care of SSL also.
- *
- * Subtypes are:
- *      PlainHttpConnection: regular direct TCP connection to server
- *      PlainProxyConnection: plain text proxy connection
- *      PlainTunnelingConnection: opens plain text (CONNECT) tunnel to server
- *      AsyncSSLConnection: TLS channel direct to server
- *      AsyncSSLTunnelConnection: TLS channel via (CONNECT) proxy tunnel
- */
-abstract class HttpConnection implements Closeable {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    final static System.Logger DEBUG_LOGGER = Utils.getDebugLogger(
-            () -> "HttpConnection(SocketTube(?))", DEBUG);
-
-    /** The address this connection is connected to. Could be a server or a proxy. */
-    final InetSocketAddress address;
-    private final HttpClientImpl client;
-    private final TrailingOperations trailingOperations;
-
-    HttpConnection(InetSocketAddress address, HttpClientImpl client) {
-        this.address = address;
-        this.client = client;
-        trailingOperations = new TrailingOperations();
-    }
-
-    private static final class TrailingOperations {
-        private final Map<CompletionStage<?>, Boolean> operations =
-                new IdentityHashMap<>();
-        void add(CompletionStage<?> cf) {
-            synchronized(operations) {
-                cf.whenComplete((r,t)-> remove(cf));
-                operations.put(cf, Boolean.TRUE);
-            }
-        }
-        boolean remove(CompletionStage<?> cf) {
-            synchronized(operations) {
-                return operations.remove(cf);
-            }
-        }
-    }
-
-    final void addTrailingOperation(CompletionStage<?> cf) {
-        trailingOperations.add(cf);
-    }
-
-//    final void removeTrailingOperation(CompletableFuture<?> cf) {
-//        trailingOperations.remove(cf);
-//    }
-
-    final HttpClientImpl client() {
-        return client;
-    }
-
-    //public abstract void connect() throws IOException, InterruptedException;
-
-    public abstract CompletableFuture<Void> connectAsync();
-
-    /** Tells whether, or not, this connection is connected to its destination. */
-    abstract boolean connected();
-
-    /** Tells whether, or not, this connection is secure ( over SSL ) */
-    abstract boolean isSecure();
-
-    /** Tells whether, or not, this connection is proxied. */
-    abstract boolean isProxied();
-
-    /** Tells whether, or not, this connection is open. */
-    final boolean isOpen() {
-        return channel().isOpen() &&
-                (connected() ? !getConnectionFlow().isFinished() : true);
-    }
-
-    interface HttpPublisher extends FlowTube.TubePublisher {
-        void enqueue(List<ByteBuffer> buffers) throws IOException;
-        void enqueueUnordered(List<ByteBuffer> buffers) throws IOException;
-        void signalEnqueued() throws IOException;
-    }
-
-    /**
-     * Returns the HTTP publisher associated with this connection.  May be null
-     * if invoked before connecting.
-     */
-    abstract HttpPublisher publisher();
-
-    /**
-     * 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) {
-                    alpn = new String[] { "h2", "http/1.1" };
-                }
-                return getSSLConnection(addr, proxy, alpn, client);
-            }
-        }
-    }
-
-    private static HttpConnection getSSLConnection(InetSocketAddress addr,
-                                                   InetSocketAddress proxy,
-                                                   String[] alpn,
-                                                   HttpClientImpl client) {
-        if (proxy != null)
-            return new AsyncSSLTunnelConnection(addr, client, alpn, proxy);
-        else
-            return new AsyncSSLConnection(addr, client, alpn);
-    }
-
-    /* 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);
-
-        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();
-        }
-    }
-
-    abstract SocketChannel channel();
-
-    final InetSocketAddress address() {
-        return address;
-    }
-
-    abstract ConnectionPool.CacheKey cacheKey();
-
-//    // overridden in SSL only
-//    SSLParameters sslParameters() {
-//        return null;
-//    }
-
-    /**
-     * Closes this connection, by returning the socket to its connection pool.
-     */
-    @Override
-    public abstract void close();
-
-    abstract void shutdownInput() throws IOException;
-
-    abstract void shutdownOutput() throws IOException;
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    abstract static class DetachedConnectionChannel implements Closeable {
-        DetachedConnectionChannel() {}
-        abstract SocketChannel channel();
-        abstract long write(ByteBuffer[] buffers, int start, int number)
-                throws IOException;
-        abstract void shutdownInput() throws IOException;
-        abstract void shutdownOutput() throws IOException;
-        abstract ByteBuffer read() throws IOException;
-        @Override
-        public abstract void close();
-        @Override
-        public String toString() {
-            return this.getClass().getSimpleName() + ": " + channel().toString();
-        }
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    abstract DetachedConnectionChannel detachChannel();
-
-    abstract FlowTube getConnectionFlow();
-
-    /**
-     * A publisher that makes it possible to publish (write)
-     * ordered (normal priority) and unordered (high priority)
-     * buffers downstream.
-     */
-    final class PlainHttpPublisher implements HttpPublisher {
-        final Object reading;
-        PlainHttpPublisher() {
-            this(new Object());
-        }
-        PlainHttpPublisher(Object readingLock) {
-            this.reading = readingLock;
-        }
-        final ConcurrentLinkedDeque<List<ByteBuffer>> queue = new ConcurrentLinkedDeque<>();
-        volatile Flow.Subscriber<? super List<ByteBuffer>> subscriber;
-        volatile HttpWriteSubscription subscription;
-        final SequentialScheduler writeScheduler =
-                    new SequentialScheduler(this::flushTask);
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-            synchronized (reading) {
-                //assert this.subscription == null;
-                //assert this.subscriber == null;
-                if (subscription == null) {
-                    subscription = new HttpWriteSubscription();
-                }
-                this.subscriber = subscriber;
-            }
-            // TODO: should we do this in the flow?
-            subscriber.onSubscribe(subscription);
-            signal();
-        }
-
-        void flushTask(DeferredCompleter completer) {
-            try {
-                HttpWriteSubscription sub = subscription;
-                if (sub != null) sub.flush();
-            } finally {
-                completer.complete();
-            }
-        }
-
-        void signal() {
-            writeScheduler.runOrSchedule();
-        }
-
-        final class HttpWriteSubscription implements Flow.Subscription {
-            final Demand demand = new Demand();
-
-            @Override
-            public void request(long n) {
-                if (n <= 0) throw new IllegalArgumentException("non-positive request");
-                demand.increase(n);
-                debug.log(Level.DEBUG, () -> "HttpPublisher: got request of "
-                            + n + " from "
-                            + getConnectionFlow());
-                writeScheduler.runOrSchedule();
-            }
-
-            @Override
-            public void cancel() {
-                debug.log(Level.DEBUG, () -> "HttpPublisher: cancelled by "
-                          + getConnectionFlow());
-            }
-
-            void flush() {
-                while (!queue.isEmpty() && demand.tryDecrement()) {
-                    List<ByteBuffer> elem = queue.poll();
-                    debug.log(Level.DEBUG, () -> "HttpPublisher: sending "
-                                + Utils.remaining(elem) + " bytes ("
-                                + elem.size() + " buffers) to "
-                                + getConnectionFlow());
-                    subscriber.onNext(elem);
-                }
-            }
-        }
-
-        @Override
-        public void enqueue(List<ByteBuffer> buffers) throws IOException {
-            queue.add(buffers);
-            int bytes = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
-            debug.log(Level.DEBUG, "added %d bytes to the write queue", bytes);
-        }
-
-        @Override
-        public void enqueueUnordered(List<ByteBuffer> buffers) throws IOException {
-            // Unordered frames are sent before existing frames.
-            int bytes = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
-            queue.addFirst(buffers);
-            debug.log(Level.DEBUG, "inserted %d bytes in the write queue", bytes);
-        }
-
-        @Override
-        public void signalEnqueued() throws IOException {
-            debug.log(Level.DEBUG, "signalling the publisher of the write queue");
-            signal();
-        }
-    }
-
-    String dbgTag = null;
-    final String dbgString() {
-        FlowTube flow = getConnectionFlow();
-        String tag = dbgTag;
-        if (tag == null && flow != null) {
-            dbgTag = tag = this.getClass().getSimpleName() + "(" + flow + ")";
-        } else if (tag == null) {
-            tag = this.getClass().getSimpleName() + "(?)";
-        }
-        return tag;
-    }
-
-    @Override
-    public String toString() {
-        return "HttpConnection: " + channel().toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpHeaders.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.OptionalLong;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.unmodifiableList;
-import static java.util.Objects.requireNonNull;
-
-/**
- * A read-only view of a set of HTTP headers.
- * {@Incubating}
- *
- * <p> The methods of this class ( that accept a String header name ), and the
- * Map returned by the {@linkplain #map() map} method, operate without regard to
- * case when retrieving the header value.
- *
- * <p> HttpHeaders instances are immutable.
- *
- * @since 9
- */
-public abstract class HttpHeaders {
-
-    /**
-     * Creates an HttpHeaders.
-     */
-    protected HttpHeaders() {}
-
-    /**
-     * Returns an {@link Optional} containing the first value of the given named
-     * (and possibly multi-valued) header. If the header is not present, then
-     * the returned {@code Optional} is empty.
-     *
-     * @implSpec
-     * The default implementation invokes
-     * {@code allValues(name).stream().findFirst()}
-     *
-     * @param name the header name
-     * @return an {@code Optional<String>} for the first named value
-     */
-    public Optional<String> firstValue(String name) {
-        return allValues(name).stream().findFirst();
-    }
-
-    /**
-     * Returns an {@link OptionalLong} containing the first value of the
-     * named header field. If the header is not present, then the Optional is
-     * empty. If the header is present but contains a value that does not parse
-     * as a {@code Long} value, then an exception is thrown.
-     *
-     * @implSpec
-     * The default implementation invokes
-     * {@code allValues(name).stream().mapToLong(Long::valueOf).findFirst()}
-     *
-     * @param name the header name
-     * @return  an {@code OptionalLong}
-     * @throws NumberFormatException if a value is found, but does not parse as
-     *                               a Long
-     */
-    public OptionalLong firstValueAsLong(String name) {
-        return allValues(name).stream().mapToLong(Long::valueOf).findFirst();
-    }
-
-    /**
-     * Returns an unmodifiable List of all of the values of the given named
-     * header. Always returns a List, which may be empty if the header is not
-     * present.
-     *
-     * @implSpec
-     * The default implementation invokes, among other things, the
-     * {@code map().get(name)} to retrieve the list of header values.
-     *
-     * @param name the header name
-     * @return a List of String values
-     */
-    public List<String> allValues(String name) {
-        requireNonNull(name);
-        List<String> values = map().get(name);
-        // Making unmodifiable list out of empty in order to make a list which
-        // throws UOE unconditionally
-        return values != null ? values : unmodifiableList(emptyList());
-    }
-
-    /**
-     * Returns an unmodifiable multi Map view of this HttpHeaders.
-     *
-     * @return the Map
-     */
-    public abstract Map<String, List<String>> map();
-
-    /**
-     * Tests this HTTP headers instance for equality with the given object.
-     *
-     * <p> If the given object is not an {@code HttpHeaders} then this
-     * method returns {@code false}. Two HTTP headers are equal if each
-     * of their corresponding {@linkplain #map() maps} are equal.
-     *
-     * <p> This method satisfies the general contract of the {@link
-     * Object#equals(Object) Object.equals} method.
-     *
-     * @param obj the object to which this object is to be compared
-     * @return {@code true} if, and only if, the given object is an {@code
-     *         HttpHeaders} that is equal to this HTTP headers
-     */
-    public final boolean equals(Object obj) {
-        if (!(obj instanceof HttpHeaders))
-            return false;
-        HttpHeaders that = (HttpHeaders)obj;
-        return this.map().equals(that.map());
-    }
-
-    /**
-     * Computes a hash code for this HTTP headers instance.
-     *
-     * <p> The hash code is based upon the components of the HTTP headers
-     * {@linkplain #map() map}, and satisfies the general contract of the
-     * {@link Object#hashCode Object.hashCode} method.
-     *
-     * @return the hash-code value for this HTTP headers
-     */
-    public final int hashCode() {
-        return map().hashCode();
-    }
-
-    /**
-     * Returns this HTTP headers as a string.
-     *
-     * @return a string describing the HTTP headers
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(super.toString()).append(" ");
-        sb.append(map());
-        sb.append(" }");
-        return sb.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,793 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URLPermission;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.Duration;
-import java.util.Iterator;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.function.Supplier;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/**
- * Represents one HTTP request which can be sent to a server.
- * {@Incubating }
- *
- * <p> {@code HttpRequest} instances are built from {@code HttpRequest}
- * {@linkplain HttpRequest.Builder builders}. {@code HttpRequest} builders
- * are obtained by calling {@link HttpRequest#newBuilder(URI) HttpRequest.newBuilder}.
- * A request's {@linkplain URI}, headers and body can be set. Request bodies are
- * provided through a {@link BodyPublisher} object supplied to the
- * {@link Builder#DELETE(BodyPublisher) DELETE},
- * {@link Builder#POST(BodyPublisher) POST} or
- * {@link Builder#PUT(BodyPublisher) PUT} methods.
- * {@link Builder#GET() GET} does not take a body. Once all required
- * parameters have been set in the builder, {@link Builder#build() } is called
- * to return the {@code HttpRequest}. Builders can also be copied and modified
- * multiple times in order to build multiple related requests that differ in
- * some parameters.
- *
- * <p> Two simple, example HTTP interactions are shown below:
- * <pre>
- * {@code
- *      HttpClient client = HttpClient.newHttpClient();
- *
- *      // GET
- *      HttpResponse<String> response = client.send(
- *          HttpRequest
- *              .newBuilder(new URI("http://www.foo.com/"))
- *              .headers("Foo", "foovalue", "Bar", "barvalue")
- *              .GET()
- *              .build(),
- *          BodyHandler.asString()
- *      );
- *      int statusCode = response.statusCode();
- *      String body = response.body();
- *
- *      // POST
- *      HttpResponse<Path> response = client.send(
- *          HttpRequest
- *              .newBuilder(new URI("http://www.foo.com/"))
- *              .headers("Foo", "foovalue", "Bar", "barvalue")
- *              .POST(BodyPublisher.fromString("Hello world"))
- *              .build(),
- *          BodyHandler.asFile(Paths.get("/path"))
- *      );
- *      int statusCode = response.statusCode();
- *      Path body = response.body(); // should be "/path"
- * }
- * </pre>
- *
- * <p> The request is sent and the response obtained by calling one of the
- * following methods in {@link HttpClient}.
- * <ul><li>{@link HttpClient#send(HttpRequest, HttpResponse.BodyHandler)} blocks
- * until the entire request has been sent and the response has been received.</li>
- * <li>{@link HttpClient#sendAsync(HttpRequest,HttpResponse.BodyHandler)} sends the
- * request and receives the response asynchronously. Returns immediately with a
- * {@link java.util.concurrent.CompletableFuture CompletableFuture}&lt;{@link
- * HttpResponse}&gt;.</li>
- * <li>{@link HttpClient#sendAsync(HttpRequest, HttpResponse.MultiSubscriber) }
- * sends the request asynchronously, expecting multiple responses. This
- * capability is of most relevance to HTTP/2 server push, but can be used for
- * single responses (HTTP/1.1 or HTTP/2) also.</li>
- * </ul>
- *
- * <p> Once a {@link HttpResponse} is received, the headers, response code
- * and body (typically) are available. Whether the body has been read or not
- * depends on the type {@code <T>} of the response body. See below.
- *
- * <p> See below for discussion of synchronous versus asynchronous usage.
- *
- * <p> <b>Request bodies</b>
- *
- * <p> Request bodies can be sent using one of the convenience request publisher
- * implementations below, provided in {@link BodyPublisher}. Alternatively, a
- * custom Publisher implementation can be used.
- * <ul>
- * <li>{@link BodyPublisher#fromByteArray(byte[]) fromByteArray(byte[])} from byte array</li>
- * <li>{@link BodyPublisher#fromByteArrays(Iterable) fromByteArrays(Iterable)}
- *      from an Iterable of byte arrays</li>
- * <li>{@link BodyPublisher#fromFile(java.nio.file.Path) fromFile(Path)} from the file located
- *     at the given Path</li>
- * <li>{@link BodyPublisher#fromString(java.lang.String) fromString(String)} from a String </li>
- * <li>{@link BodyPublisher#fromInputStream(Supplier) fromInputStream}({@link Supplier}&lt;
- *      {@link InputStream}&gt;) from an InputStream obtained from a Supplier</li>
- * <li>{@link BodyPublisher#noBody() } no request body is sent</li>
- * </ul>
- *
- * <p> <b>Response bodies</b>
- *
- * <p> Responses bodies are handled at two levels. When sending the request,
- * a response body handler is specified. This is a function ({@linkplain
- * HttpResponse.BodyHandler}) which will be called with the response status code
- * and headers, once they are received. This function is then expected to return
- * a {@link HttpResponse.BodySubscriber}{@code <T>} which is then used to read
- * the response body, converting it into an instance of T. After this occurs,
- * the response becomes available in a {@link HttpResponse}, and {@link
- * HttpResponse#body()} can then be called to obtain the actual body. Some
- * implementations and examples of usage of both {@link
- * HttpResponse.BodySubscriber} and {@link HttpResponse.BodyHandler} are
- * provided in {@link HttpResponse}:
- *
- * <p> <b>Some of the pre-defined body handlers</b><br>
- * <ul>
- * <li>{@link HttpResponse.BodyHandler#asByteArray() BodyHandler.asByteArray()}
- * stores the body in a byte array</li>
- * <li>{@link HttpResponse.BodyHandler#asString() BodyHandler.asString()}
- * stores the body as a String </li>
- * <li>{@link HttpResponse.BodyHandler#asFile(java.nio.file.Path)
- * BodyHandler.asFile(Path)} stores the body in a named file</li>
- * <li>{@link HttpResponse.BodyHandler#discard(Object) BodyHandler.discard()}
- * discards the response body and returns the given value instead.</li>
- * </ul>
- *
- * <p> <b>Multi responses</b>
- *
- * <p> With HTTP/2 it is possible for a server to return a main response and zero
- * or more additional responses (known as server pushes) to a client-initiated
- * request. These are handled using a special response subscriber called {@link
- * HttpResponse.MultiSubscriber}.
- *
- * <p> <b>Blocking/asynchronous behavior and thread usage</b>
- *
- * <p> There are two styles of request sending: <i>synchronous</i> and
- * <i>asynchronous</i>. {@link HttpClient#send(HttpRequest, HttpResponse.BodyHandler) }
- * blocks the calling thread until the request has been sent and the response received.
- *
- * <p> {@link HttpClient#sendAsync(HttpRequest, HttpResponse.BodyHandler)} is
- * asynchronous and returns immediately with a {@link CompletableFuture}&lt;{@link
- * HttpResponse}&gt; and when this object completes (possibly in a different
- * thread) the response has been received.
- *
- * <p> {@link HttpClient#sendAsync(HttpRequest, HttpResponse.MultiSubscriber)}
- * is the variant for multi responses and is also asynchronous.
- *
- * <p> Instances of {@code CompletableFuture} can be combined in different ways
- * to declare the dependencies among several asynchronous tasks, while allowing
- * for the maximum level of parallelism to be utilized.
- *
- * <p> <a id="securitychecks"></a><b>Security checks</b></a>
- *
- * <p> If a security manager is present then security checks are performed by
- * the HTTP Client's sending methods. An appropriate {@link URLPermission} is
- * required to access the destination server, and proxy server if one has
- * been configured. The {@code URLPermission} form used to access proxies uses a
- * method parameter of {@code "CONNECT"} (for all kinds of proxying) and a URL
- * string  of the form {@code "socket://host:port"} where host and port specify
- * the proxy's address.
- *
- * <p> In this implementation, if an explicit {@linkplain
- * HttpClient.Builder#executor(Executor) executor} has not been set for an
- * {@code HttpClient}, and a security manager has been installed, then the
- * default executor will execute asynchronous and dependent tasks in a context
- * that is granted no permissions. Custom {@linkplain HttpRequest.BodyPublisher
- * request body publishers}, {@linkplain HttpResponse.BodyHandler response body
- * handlers}, {@linkplain HttpResponse.BodySubscriber response body subscribers},
- * and {@linkplain WebSocket.Listener WebSocket Listeners}, if executing
- * operations that require privileges, should do so  within an appropriate
- * {@linkplain AccessController#doPrivileged(PrivilegedAction) privileged context}.
- *
- * <p> <b>Examples</b>
- * <pre>{@code
- *      HttpClient client = HttpClient
- *              .newBuilder()
- *              .build();
- *
- *      HttpRequest request = HttpRequest
- *              .newBuilder(new URI("http://www.foo.com/"))
- *              .POST(BodyPublisher.fromString("Hello world"))
- *              .build();
- *
- *      HttpResponse<Path> response =
- *          client.send(request, BodyHandler.asFile(Paths.get("/path")));
- *
- *      Path body = response.body();
- * }</pre>
- *
- * <p><b>Asynchronous Example</b>
- *
- * <p> The above example will work asynchronously, if {@link HttpClient#sendAsync
- * (HttpRequest, HttpResponse.BodyHandler) sendAsync} is used instead of
- * {@link HttpClient#send(HttpRequest,HttpResponse.BodyHandler) send}
- * in which case the returned object is a {@link CompletableFuture}{@code <HttpResponse>}
- * instead of {@link HttpResponse}. The following example shows how multiple requests
- * can be sent asynchronously. It also shows how dependent asynchronous operations
- * (receiving response, and receiving response body) can be chained easily using
- * one of the many methods in {@code CompletableFuture}.
- * <pre>
- * {@code
- *  // fetch a list of target URIs asynchronously and store them in Files.
- *
- *      List<URI> targets = ...
- *
- *      List<CompletableFuture<File>> futures = targets
- *          .stream()
- *          .map(target -> client
- *                  .sendAsync(
- *                      HttpRequest.newBuilder(target)
- *                                 .GET()
- *                                 .build(),
- *                      BodyHandler.asFile(Paths.get("base", target.getPath())))
- *                  .thenApply(response -> response.body())
- *                  .thenApply(path -> path.toFile()))
- *          .collect(Collectors.toList());
- *
- *      // all async operations waited for here
- *
- *      CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
- *          .join();
- *
- *      // all elements of futures have completed and can be examined.
- *      // Use File.exists() to check whether file was successfully downloaded
- * }
- * </pre>
- *
- * <p> Unless otherwise stated, {@code null} parameter values will cause methods
- * of this class to throw {@code NullPointerException}.
- *
- * @since 9
- */
-public abstract class HttpRequest {
-
-    /**
-     * Creates an HttpRequest.
-     */
-    protected HttpRequest() {}
-
-    /**
-     * A builder of {@linkplain HttpRequest HTTP Requests}.
-     * {@Incubating}
-     *
-     * <p> Instances of {@code HttpRequest.Builder} are created by calling {@link
-     * HttpRequest#newBuilder(URI)} or {@link HttpRequest#newBuilder()}.
-     *
-     * <p> Each of the setter methods in this class modifies the state of the
-     * builder and returns <i>this</i> (ie. the same instance). The methods are
-     * not synchronized and should not be called from multiple threads without
-     * external synchronization.
-     *
-     * <p> Note, that not all request headers may be set by user code. Some are
-     * restricted for security reasons and others such as the headers relating
-     * to authentication, redirection and cookie management are managed by
-     * specific APIs rather than through directly user set headers.
-     *
-     * <p> The {@linkplain #build() build} method returns a new {@code
-     * HttpRequest} each time it is invoked.
-     *
-     * @since 9
-     */
-    public abstract static class Builder {
-
-        /**
-         * Creates a Builder.
-         */
-        protected Builder() {}
-
-        /**
-         * Sets this {@code HttpRequest}'s request {@code URI}.
-         *
-         * @param uri the request URI
-         * @return this request builder
-         * @throws IllegalArgumentException if the {@code URI} scheme is not
-         *         supported
-         */
-        public abstract Builder uri(URI uri);
-
-        /**
-         * Requests the server to acknowledge the request before sending the
-         * body. This is disabled by default. If enabled, the server is
-         * requested to send an error response or a {@code 100 Continue}
-         * response before the client sends the request body. This means the
-         * request publisher for the request will not be invoked until this
-         * interim response is received.
-         *
-         * @param enable {@code true} if Expect continue to be sent
-         * @return this request builder
-         */
-        public abstract Builder expectContinue(boolean enable);
-
-        /**
-         * Sets the preferred {@link HttpClient.Version} for this request.
-         *
-         * <p> The corresponding {@link HttpResponse} should be checked for the
-         * version that was actually used. If the version is not set in a
-         * request, then the version requested will be that of the sending
-         * {@link HttpClient}.
-         *
-         * @param version the HTTP protocol version requested
-         * @return this request builder
-         */
-        public abstract Builder version(HttpClient.Version version);
-
-        /**
-         * Adds the given name value pair to the set of headers for this request.
-         * The given value is added to the list of values for that name.
-         *
-         * @param name the header name
-         * @param value the header value
-         * @return this request builder
-         * @throws IllegalArgumentException if the header name or value is not
-         *         valid, see <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
-         *         RFC 7230 section-3.2</a>
-         */
-        public abstract Builder header(String name, String value);
-
-        /**
-         * Adds the given name value pairs to the set of headers for this
-         * request. The supplied {@code String} instances must alternate as
-         * header names and header values.
-         * To add several values to the same name then the same name must
-         * be supplied with each new value.
-         *
-         * @param headers the list of name value pairs
-         * @return this request builder
-         * @throws IllegalArgumentException if there are an odd number of
-         *         parameters, or if a header name or value is not valid, see
-         *         <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
-         *         RFC 7230 section-3.2</a>
-         */
-        public abstract Builder headers(String... headers);
-
-        /**
-         * Sets a timeout for this request. If the response is not received
-         * within the specified timeout then a {@link HttpTimeoutException} is
-         * thrown from {@link HttpClient#send(jdk.incubator.http.HttpRequest,
-         * jdk.incubator.http.HttpResponse.BodyHandler) HttpClient::send} or
-         * {@link HttpClient#sendAsync(jdk.incubator.http.HttpRequest,
-         * jdk.incubator.http.HttpResponse.BodyHandler) HttpClient::sendAsync}
-         * completes exceptionally with a {@code HttpTimeoutException}. The effect
-         * of not setting a timeout is the same as setting an infinite Duration, ie.
-         * block forever.
-         *
-         * @param duration the timeout duration
-         * @return this request builder
-         * @throws IllegalArgumentException if the duration is non-positive
-         */
-        public abstract Builder timeout(Duration duration);
-
-        /**
-         * Sets the given name value pair to the set of headers for this
-         * request. This overwrites any previously set values for name.
-         *
-         * @param name the header name
-         * @param value the header value
-         * @return this request builder
-         * @throws IllegalArgumentException if the header name or value is not valid,
-         *         see <a href="https://tools.ietf.org/html/rfc7230#section-3.2">
-         *         RFC 7230 section-3.2</a>
-         */
-        public abstract Builder setHeader(String name, String value);
-
-        /**
-         * Sets the request method of this builder to GET.
-         * This is the default.
-         *
-         * @return a {@code HttpRequest}
-         */
-        public abstract Builder GET();
-
-        /**
-         * Sets the request method of this builder to POST and sets its
-         * request body publisher to the given value.
-         *
-         * @param bodyPublisher the body publisher
-         *
-         * @return a {@code HttpRequest}
-         */
-        public abstract Builder POST(BodyPublisher bodyPublisher);
-
-        /**
-         * Sets the request method of this builder to PUT and sets its
-         * request body publisher to the given value.
-         *
-         * @param bodyPublisher the body publisher
-         *
-         * @return a {@code HttpRequest}
-         */
-        public abstract Builder PUT(BodyPublisher bodyPublisher);
-
-        /**
-         * Sets the request method of this builder to DELETE and sets its
-         * request body publisher to the given value.
-         *
-         * @param bodyPublisher the body publisher
-         *
-         * @return a {@code HttpRequest}
-         */
-
-        public abstract Builder DELETE(BodyPublisher bodyPublisher);
-
-        /**
-         * Sets the request method and request body of this builder to the
-         * given values.
-         *
-         * @apiNote The {@linkplain BodyPublisher#noBody() noBody} request
-         * body publisher can be used where no request body is required or
-         * appropriate.
-         *
-         * @param method the method to use
-         * @param bodyPublisher the body publisher
-         * @return a {@code HttpRequest}
-         * @throws IllegalArgumentException if the method is unrecognised
-         */
-        public abstract Builder method(String method, BodyPublisher bodyPublisher);
-
-        /**
-         * Builds and returns a {@link HttpRequest}.
-         *
-         * @return the request
-         * @throws IllegalStateException if a URI has not been set
-         */
-        public abstract HttpRequest build();
-
-        /**
-         * Returns an exact duplicate copy of this {@code Builder} based on
-         * current state. The new builder can then be modified independently of
-         * this builder.
-         *
-         * @return an exact copy of this Builder
-         */
-        public abstract Builder copy();
-    }
-
-    /**
-     * Creates a {@code HttpRequest} builder.
-     *
-     * @param uri the request URI
-     * @return a new request builder
-     * @throws IllegalArgumentException if the URI scheme is not supported.
-     */
-    public static HttpRequest.Builder newBuilder(URI uri) {
-        return new HttpRequestBuilderImpl(uri);
-    }
-
-    /**
-     * Creates a {@code HttpRequest} builder.
-     *
-     * @return a new request builder
-     */
-    public static HttpRequest.Builder newBuilder() {
-        return new HttpRequestBuilderImpl();
-    }
-
-    /**
-     * Returns an {@code Optional} containing the {@link BodyPublisher} set on
-     * this request. If no {@code BodyPublisher} was set in the requests's
-     * builder, then the {@code Optional} is empty.
-     *
-     * @return an {@code Optional} containing this request's {@code BodyPublisher}
-     */
-    public abstract Optional<BodyPublisher> bodyPublisher();
-
-    /**
-     * Returns the request method for this request. If not set explicitly,
-     * the default method for any request is "GET".
-     *
-     * @return this request's method
-     */
-    public abstract String method();
-
-    /**
-     * Returns an {@code Optional} containing this request's timeout duration.
-     * If the timeout duration was not set in the request's builder, then the
-     * {@code Optional} is empty.
-     *
-     * @return an {@code Optional} containing this request's timeout duration
-     */
-    public abstract Optional<Duration> timeout();
-
-    /**
-     * Returns this request's {@link HttpRequest.Builder#expectContinue(boolean)
-     * expect continue } setting.
-     *
-     * @return this request's expect continue setting
-     */
-    public abstract boolean expectContinue();
-
-    /**
-     * Returns this request's request {@code URI}.
-     *
-     * @return this request's URI
-     */
-    public abstract URI uri();
-
-    /**
-     * Returns an {@code Optional} containing the HTTP protocol version that
-     * will be requested for this {@code HttpRequest}. If the version was not
-     * set in the request's builder, then the {@code Optional} is empty.
-     * In that case, the version requested will be that of the sending
-     * {@link HttpClient}. The corresponding {@link HttpResponse} should be
-     * queried to determine the version that was actually used.
-     *
-     * @return HTTP protocol version
-     */
-    public abstract Optional<HttpClient.Version> version();
-
-    /**
-     * The (user-accessible) request headers that this request was (or will be)
-     * sent with.
-     *
-     * @return this request's HttpHeaders
-     */
-    public abstract HttpHeaders headers();
-
-    /**
-     * Tests this HTTP request instance for equality with the given object.
-     *
-     * <p> If the given object is not an {@code HttpRequest} then this
-     * method returns {@code false}. Two HTTP requests are equal if their URI,
-     * method, and headers fields are all equal.
-     *
-     * <p> This method satisfies the general contract of the {@link
-     * Object#equals(Object) Object.equals} method.
-     *
-     * @param obj the object to which this object is to be compared
-     * @return {@code true} if, and only if, the given object is an {@code
-     *         HttpRequest} that is equal to this HTTP request
-     */
-    @Override
-    public final boolean equals(Object obj) {
-       if (! (obj instanceof HttpRequest))
-           return false;
-       HttpRequest that = (HttpRequest)obj;
-       if (!that.method().equals(this.method()))
-           return false;
-       if (!that.uri().equals(this.uri()))
-           return false;
-       if (!that.headers().equals(this.headers()))
-           return false;
-       return true;
-    }
-
-    /**
-     * Computes a hash code for this HTTP request instance.
-     *
-     * <p> The hash code is based upon the HTTP request's URI, method, and
-     * header components, and satisfies the general contract of the
-     * {@link Object#hashCode Object.hashCode} method.
-     *
-     * @return the hash-code value for this HTTP request
-     */
-    public final int hashCode() {
-        return method().hashCode()
-                + uri().hashCode()
-                + headers().hashCode();
-    }
-
-    /**
-     * A Publisher which converts high level Java objects into flows of
-     * byte buffers suitable for sending as request bodies.
-     * {@Incubating}
-     *
-     * <p> The {@code BodyPublisher} class implements {@link Flow.Publisher
-     * Flow.Publisher&lt;ByteBuffer&gt;} which means that a {@code BodyPublisher}
-     * acts as a publisher of {@linkplain ByteBuffer byte buffers}.
-     *
-     * <p> The HTTP client implementation subscribes to the publisher in order
-     * to receive the flow of outgoing data buffers. The normal semantics of
-     * {@link Flow.Subscriber} and {@link Flow.Publisher} are implemented by the
-     * library and are expected from publisher implementations. Each outgoing
-     * request results in one {@code Subscriber} subscribing to the {@code
-     * BodyPublisher} in order to provide the sequence of byte buffers
-     * containing the request body.
-     * Instances of {@code ByteBuffer} published  by the publisher must be
-     * allocated by the publisher, and must not be accessed after being handed
-     * over to the library.
-     * These subscriptions complete normally when the request is fully sent,
-     * and can be canceled or terminated early through error. If a request
-     * needs to be resent for any reason, then a new subscription is created
-     * which is expected to generate the same data as before.
-     *
-     * <p> A publisher that reports a {@linkplain #contentLength() content
-     * length} of {@code 0} may not be subscribed to by the HTTP client
-     * implementation, as it has effectively no data to publish.
-     */
-    public interface BodyPublisher extends Flow.Publisher<ByteBuffer> {
-
-        /**
-         * Returns a request body publisher whose body is retrieved from the
-         * given {@code Flow.Publisher}. The returned request body publisher
-         * has an unknown content length.
-         *
-         * @apiNote This method can be used as an adapter between {@code
-         * BodyPublisher} and {@code Flow.Publisher}, where the amount of
-         * request body that the publisher will publish is unknown.
-         *
-         * @param publisher the publisher responsible for publishing the body
-         * @return a BodyPublisher
-         */
-        static BodyPublisher fromPublisher(Flow.Publisher<? extends ByteBuffer> publisher) {
-            return new RequestPublishers.PublisherAdapter(publisher, -1L);
-        }
-
-        /**
-         * Returns a request body publisher whose body is retrieved from the
-         * given {@code Flow.Publisher}. The returned request body publisher
-         * has the given content length.
-         *
-         * <p> The given {@code contentLength} is a positive number, that
-         * represents the exact amount of bytes the {@code publisher} must
-         * publish.
-         *
-         * @apiNote This method can be used as an adapter between {@code
-         * BodyPublisher} and {@code Flow.Publisher}, where the amount of
-         * request body that the publisher will publish is known.
-         *
-         * @param publisher the publisher responsible for publishing the body
-         * @param contentLength a positive number representing the exact
-         *                      amount of bytes the publisher will publish
-         * @throws IllegalArgumentException if the content length is
-         *                                  non-positive
-         * @return a BodyPublisher
-         */
-        static BodyPublisher fromPublisher(Flow.Publisher<? extends ByteBuffer> publisher,
-                                           long contentLength) {
-            if (contentLength < 1)
-                throw new IllegalArgumentException("non-positive contentLength: " + contentLength);
-            return new RequestPublishers.PublisherAdapter(publisher, contentLength);
-        }
-
-        /**
-         * Returns a request body publisher whose body is the given {@code
-         * String}, converted using the {@link StandardCharsets#UTF_8 UTF_8}
-         * character set.
-         *
-         * @param body the String containing the body
-         * @return a BodyPublisher
-         */
-        static BodyPublisher fromString(String body) {
-            return fromString(body, UTF_8);
-        }
-
-        /**
-         * Returns a request body publisher whose body is the given {@code
-         * String}, converted using the given character set.
-         *
-         * @param s the String containing the body
-         * @param charset the character set to convert the string to bytes
-         * @return a BodyPublisher
-         */
-        static BodyPublisher fromString(String s, Charset charset) {
-            return new RequestPublishers.StringPublisher(s, charset);
-        }
-
-        /**
-         * A request body publisher that reads its data from an {@link
-         * InputStream}. A {@link Supplier} of {@code InputStream} is used in
-         * case the request needs to be repeated, as the content is not buffered.
-         * The {@code Supplier} may return {@code null} on subsequent attempts,
-         * in which case the request fails.
-         *
-         * @param streamSupplier a Supplier of open InputStreams
-         * @return a BodyPublisher
-         */
-        // TODO (spec): specify that the stream will be closed
-        static BodyPublisher fromInputStream(Supplier<? extends InputStream> streamSupplier) {
-            return new RequestPublishers.InputStreamPublisher(streamSupplier);
-        }
-
-        /**
-         * Returns a request body publisher whose body is the given byte array.
-         *
-         * @param buf the byte array containing the body
-         * @return a BodyPublisher
-         */
-        static BodyPublisher fromByteArray(byte[] buf) {
-            return new RequestPublishers.ByteArrayPublisher(buf);
-        }
-
-        /**
-         * Returns a request body publisher whose body is the content of the
-         * given byte array of {@code length} bytes starting from the specified
-         * {@code offset}.
-         *
-         * @param buf the byte array containing the body
-         * @param offset the offset of the first byte
-         * @param length the number of bytes to use
-         * @return a BodyPublisher
-         * @throws IndexOutOfBoundsException if the sub-range is defined to be
-         *                                   out-of-bounds
-         */
-        static BodyPublisher fromByteArray(byte[] buf, int offset, int length) {
-            Objects.checkFromIndexSize(offset, length, buf.length);
-            return new RequestPublishers.ByteArrayPublisher(buf, offset, length);
-        }
-
-        private static String pathForSecurityCheck(Path path) {
-            return path.toFile().getPath();
-        }
-
-        /**
-         * A request body publisher that takes data from the contents of a File.
-         *
-         * @param path the path to the file containing the body
-         * @return a BodyPublisher
-         * @throws java.io.FileNotFoundException if the path is not found
-         * @throws SecurityException if a security manager has been installed
-         *          and it denies {@link SecurityManager#checkRead(String)
-         *          read access} to the given file
-         */
-        static BodyPublisher fromFile(Path path) throws FileNotFoundException {
-            Objects.requireNonNull(path);
-            SecurityManager sm = System.getSecurityManager();
-            if (sm != null)
-                sm.checkRead(pathForSecurityCheck(path));
-            if (Files.notExists(path))
-                throw new FileNotFoundException(path + " not found");
-            return new RequestPublishers.FilePublisher(path);
-        }
-
-        /**
-         * A request body publisher that takes data from an {@code Iterable}
-         * of byte arrays. An {@link Iterable} is provided which supplies
-         * {@link Iterator} instances. Each attempt to send the request results
-         * in one invocation of the {@code Iterable}.
-         *
-         * @param iter an Iterable of byte arrays
-         * @return a BodyPublisher
-         */
-        static BodyPublisher fromByteArrays(Iterable<byte[]> iter) {
-            return new RequestPublishers.IterablePublisher(iter);
-        }
-
-        /**
-         * A request body publisher which sends no request body.
-         *
-         * @return a BodyPublisher which completes immediately and sends
-         *         no request body.
-         */
-        static BodyPublisher noBody() {
-            return new RequestPublishers.EmptyPublisher();
-        }
-
-        /**
-         * Returns the content length for this request body. May be zero
-         * if no request body being sent, greater than zero for a fixed
-         * length content, or less than zero for an unknown content length.
-         *
-         * This method may be invoked before the publisher is subscribed to.
-         * This method may be invoked more than once by the HTTP client
-         * implementation, and MUST return the same constant value each time.
-         *
-         * @return the content length for this request body, if known
-         */
-        long contentLength();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.URI;
-import java.time.Duration;
-import java.util.Optional;
-import jdk.incubator.http.HttpRequest.BodyPublisher;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static jdk.incubator.http.internal.common.Utils.isValidName;
-import static jdk.incubator.http.internal.common.Utils.isValidValue;
-
-class HttpRequestBuilderImpl extends HttpRequest.Builder {
-
-    private HttpHeadersImpl userHeaders;
-    private URI uri;
-    private String method;
-    private boolean expectContinue;
-    private BodyPublisher bodyPublisher;
-    private volatile Optional<HttpClient.Version> version;
-    private Duration duration;
-
-    public HttpRequestBuilderImpl(URI uri) {
-        requireNonNull(uri, "uri must be non-null");
-        checkURI(uri);
-        this.uri = uri;
-        this.userHeaders = new HttpHeadersImpl();
-        this.method = "GET"; // default, as per spec
-        this.version = Optional.empty();
-    }
-
-    public HttpRequestBuilderImpl() {
-        this.userHeaders = new HttpHeadersImpl();
-        this.method = "GET"; // default, as per spec
-        this.version = Optional.empty();
-    }
-
-    @Override
-    public HttpRequestBuilderImpl uri(URI uri) {
-        requireNonNull(uri, "uri must be non-null");
-        checkURI(uri);
-        this.uri = uri;
-        return this;
-    }
-
-    private static IllegalArgumentException newIAE(String message, Object... args) {
-        return new IllegalArgumentException(format(message, args));
-    }
-
-    private static void checkURI(URI uri) {
-        String scheme = uri.getScheme();
-        if (scheme == null)
-            throw newIAE("URI with undefined scheme");
-        scheme = scheme.toLowerCase();
-        if (!(scheme.equals("https") || scheme.equals("http"))) {
-            throw newIAE("invalid URI scheme %s", scheme);
-        }
-        if (uri.getHost() == null) {
-            throw newIAE("unsupported URI %s", uri);
-        }
-    }
-
-    @Override
-    public HttpRequestBuilderImpl copy() {
-        HttpRequestBuilderImpl b = new HttpRequestBuilderImpl(this.uri);
-        b.userHeaders = this.userHeaders.deepCopy();
-        b.method = this.method;
-        b.expectContinue = this.expectContinue;
-        b.bodyPublisher = bodyPublisher;
-        b.uri = uri;
-        b.duration = duration;
-        b.version = version;
-        return b;
-    }
-
-    private void checkNameAndValue(String name, String value) {
-        requireNonNull(name, "name");
-        requireNonNull(value, "value");
-        if (!isValidName(name)) {
-            throw newIAE("invalid header name:", name);
-        }
-        if (!isValidValue(value)) {
-            throw newIAE("invalid header value:%s", value);
-        }
-    }
-
-    @Override
-    public HttpRequestBuilderImpl setHeader(String name, String value) {
-        checkNameAndValue(name, value);
-        userHeaders.setHeader(name, value);
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl header(String name, String value) {
-        checkNameAndValue(name, value);
-        userHeaders.addHeader(name, value);
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl headers(String... params) {
-        requireNonNull(params);
-        if (params.length == 0 || params.length % 2 != 0) {
-            throw newIAE("wrong number, %d, of parameters", params.length);
-        }
-        for (int i = 0; i < params.length; i += 2) {
-            String name  = params[i];
-            String value = params[i + 1];
-            header(name, value);
-        }
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl expectContinue(boolean enable) {
-        expectContinue = enable;
-        return this;
-    }
-
-    @Override
-    public HttpRequestBuilderImpl version(HttpClient.Version version) {
-        requireNonNull(version);
-        this.version = Optional.of(version);
-        return this;
-    }
-
-    HttpHeadersImpl headers() {  return userHeaders; }
-
-    URI uri() { return uri; }
-
-    String method() { return method; }
-
-    boolean expectContinue() { return expectContinue; }
-
-    BodyPublisher bodyPublisher() { return bodyPublisher; }
-
-    Optional<HttpClient.Version> version() { return version; }
-
-    @Override
-    public HttpRequest.Builder GET() {
-        return method0("GET", null);
-    }
-
-    @Override
-    public HttpRequest.Builder POST(BodyPublisher body) {
-        return method0("POST", requireNonNull(body));
-    }
-
-    @Override
-    public HttpRequest.Builder DELETE(BodyPublisher body) {
-        return method0("DELETE", requireNonNull(body));
-    }
-
-    @Override
-    public HttpRequest.Builder PUT(BodyPublisher body) {
-        return method0("PUT", requireNonNull(body));
-    }
-
-    @Override
-    public HttpRequest.Builder method(String method, BodyPublisher body) {
-        requireNonNull(method);
-        if (method.equals(""))
-            throw newIAE("illegal method <empty string>");
-        if (method.equals("CONNECT"))
-            throw newIAE("method CONNECT is not supported");
-        return method0(method, requireNonNull(body));
-    }
-
-    private HttpRequest.Builder method0(String method, BodyPublisher body) {
-        assert method != null;
-        assert !method.equals("GET") ? body != null : true;
-        assert !method.equals("");
-        this.method = method;
-        this.bodyPublisher = body;
-        return this;
-    }
-
-    @Override
-    public HttpRequest build() {
-        if (uri == null)
-            throw new IllegalStateException("uri is null");
-        assert method != null;
-        return new HttpRequestImpl(this);
-    }
-
-    @Override
-    public HttpRequest.Builder timeout(Duration duration) {
-        requireNonNull(duration);
-        if (duration.isNegative() || Duration.ZERO.equals(duration))
-            throw new IllegalArgumentException("Invalid duration: " + duration);
-        this.duration = duration;
-        return this;
-    }
-
-    Duration timeout() { return duration; }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,322 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.websocket.WebSocketRequest;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.Duration;
-import java.util.List;
-import java.util.Locale;
-import java.util.Optional;
-
-import static jdk.incubator.http.internal.common.Utils.ALLOWED_HEADERS;
-
-class HttpRequestImpl extends HttpRequest implements WebSocketRequest {
-
-    private final HttpHeaders userHeaders;
-    private final HttpHeadersImpl systemHeaders;
-    private final URI uri;
-    private Proxy proxy;
-    private InetSocketAddress authority; // only used when URI not specified
-    private final String method;
-    final BodyPublisher requestPublisher;
-    final boolean secure;
-    final boolean expectContinue;
-    private boolean isWebSocket;
-    private 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();
-    }
-
-    /**
-     * 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();
-    }
-
-    /** 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();
-    }
-
-    /* used for creating CONNECT requests  */
-    HttpRequestImpl(String method, InetSocketAddress authority) {
-        // 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)
-        this.method = method;
-        this.systemHeaders = new HttpHeadersImpl();
-        this.userHeaders = ImmutableHeaders.empty();
-        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);
-    }
-
-    /**
-     * 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;
-    }
-
-    @Override
-    public String toString() {
-        return (uri == null ? "" : uri.toString()) + " " + method;
-    }
-
-    @Override
-    public HttpHeaders headers() {
-        return userHeaders;
-    }
-
-    InetSocketAddress authority() { return authority; }
-
-    void setH2Upgrade(Http2ClientImpl h2client) {
-        systemHeaders.setHeader("Connection", "Upgrade, HTTP2-Settings");
-        systemHeaders.setHeader("Upgrade", "h2c");
-        systemHeaders.setHeader("HTTP2-Settings", h2client.getSettingsString());
-    }
-
-    @Override
-    public boolean expectContinue() { return expectContinue; }
-
-    /** Retrieves the proxy, from the given ProxySelector, if there is one. */
-    private static Proxy retrieveProxy(ProxySelector ps, URI uri) {
-        Proxy proxy = null;
-        List<Proxy> pl = ps.select(uri);
-        if (!pl.isEmpty()) {
-            Proxy p = pl.get(0);
-            if (p.type() == Proxy.Type.HTTP)
-                proxy = p;
-        }
-        return proxy;
-    }
-
-    InetSocketAddress proxy() {
-        if (proxy == null || proxy.type() != Proxy.Type.HTTP
-                || method.equalsIgnoreCase("CONNECT")) {
-            return null;
-        }
-        return (InetSocketAddress)proxy.address();
-    }
-
-    boolean secure() { return secure; }
-
-    @Override
-    public void setProxy(Proxy proxy) {
-        assert isWebSocket;
-        this.proxy = proxy;
-    }
-
-    @Override
-    public void isWebSocket(boolean is) {
-        isWebSocket = is;
-    }
-
-    boolean isWebSocket() {
-        return isWebSocket;
-    }
-
-    @Override
-    public Optional<BodyPublisher> bodyPublisher() {
-        return requestPublisher == null ? Optional.empty()
-                                        : Optional.of(requestPublisher);
-    }
-
-    /**
-     * Returns the request method for this request. If not set explicitly,
-     * the default method for any request is "GET".
-     */
-    @Override
-    public String method() { return method; }
-
-    @Override
-    public URI uri() { return uri; }
-
-    @Override
-    public Optional<Duration> timeout() {
-        return timeout == null ? Optional.empty() : Optional.of(timeout);
-    }
-
-    HttpHeaders getUserHeaders() { return userHeaders; }
-
-    HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
-
-    @Override
-    public Optional<HttpClient.Version> version() { return version; }
-
-    void addSystemHeader(String name, String value) {
-        systemHeaders.addHeader(name, value);
-    }
-
-    @Override
-    public void setSystemHeader(String name, String value) {
-        systemHeaders.setHeader(name, value);
-    }
-
-    InetSocketAddress getAddress() {
-        URI uri = uri();
-        if (uri == null) {
-            return authority();
-        }
-        int p = uri.getPort();
-        if (p == -1) {
-            if (uri.getScheme().equalsIgnoreCase("https")) {
-                p = 443;
-            } else {
-                p = 80;
-            }
-        }
-        final String host = uri.getHost();
-        final int port = p;
-        if (proxy() == null) {
-            PrivilegedAction<InetSocketAddress> pa = () -> new InetSocketAddress(host, port);
-            return AccessController.doPrivileged(pa);
-        } else {
-            return InetSocketAddress.createUnresolved(host, port);
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponse.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1102 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import jdk.incubator.http.ResponseSubscribers.MultiSubscriberImpl;
-import static jdk.incubator.http.internal.common.Utils.unchecked;
-import static jdk.incubator.http.internal.common.Utils.charsetFrom;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.channels.FileChannel;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.security.AccessControlContext;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import javax.net.ssl.SSLParameters;
-
-/**
- * Represents a response to a {@link HttpRequest}.
- * {@Incubating}
- *
- * <p> A {@code HttpResponse} is available when the response status code and
- * headers have been received, and typically after the response body has also
- * been received. This depends on the response body handler provided when
- * sending the request. In all cases, the response body handler is invoked
- * before the body is read. This gives applications an opportunity to decide
- * how to handle the body.
- *
- * <p> Methods are provided in this class for accessing the response headers,
- * and response body.
- *
- * <p><b>Response handlers and subscribers</b>
- *
- * <p> Response bodies are handled at two levels. Application code supplies a
- * response handler ({@link BodyHandler}) which may examine the response status
- * code and headers, and which then returns a {@link BodySubscriber} to actually
- * read (or discard) the body and convert it into some useful Java object type.
- * The handler can return one of the pre-defined subscriber types, or a custom
- * subscriber, or if the body is to be discarded it can call {@link
- * BodySubscriber#discard(Object) discard} and return a subscriber which
- * discards the response body. Static implementations of both handlers and
- * subscribers are provided in {@linkplain BodyHandler BodyHandler} and
- * {@linkplain BodySubscriber BodySubscriber} respectively. In all cases, the
- * handler functions provided are convenience implementations which ignore the
- * supplied status code and headers and return the relevant pre-defined {@code
- * BodySubscriber}.
- *
- * <p> See {@link BodyHandler} for example usage.
- *
- * @param <T> the response body type
- * @since 9
- */
-public abstract class HttpResponse<T> {
-
-    /**
-     * Creates an HttpResponse.
-     */
-    protected HttpResponse() { }
-
-    /**
-     * Returns the status code for this response.
-     *
-     * @return the response code
-     */
-    public abstract int statusCode();
-
-    /**
-     * Returns the {@link HttpRequest} corresponding to this response.
-     *
-     * <p> This may not be the original request provided by the caller,
-     * for example, if that request was redirected.
-     *
-     * @see #previousResponse()
-     *
-     * @return the request
-     */
-    public abstract HttpRequest request();
-
-    /**
-     * Returns an {@code Optional} containing the previous intermediate response
-     * if one was received. An intermediate response is one that is received
-     * as a result of redirection or authentication. If no previous response
-     * was received then an empty {@code Optional} is returned.
-     *
-     * @return an Optional containing the HttpResponse, if any.
-     */
-    public abstract Optional<HttpResponse<T>> previousResponse();
-
-    /**
-     * Returns the received response headers.
-     *
-     * @return the response headers
-     */
-    public abstract HttpHeaders headers();
-
-    /**
-     * Returns the body. Depending on the type of {@code T}, the returned body
-     * may represent the body after it was read (such as {@code byte[]}, or
-     * {@code String}, or {@code Path}) or it may represent an object with
-     * which the body is read, such as an {@link java.io.InputStream}.
-     *
-     * <p> If this {@code HttpResponse} was returned from an invocation of
-     * {@link #previousResponse()} then this method returns {@code null}
-     *
-     * @return the body
-     */
-    public abstract T body();
-
-    /**
-     * Returns the {@link javax.net.ssl.SSLParameters} in effect for this
-     * response. Returns {@code null} if this is not a HTTPS response.
-     *
-     * @return the SSLParameters associated with the response
-     */
-    public abstract SSLParameters sslParameters();
-
-    /**
-     * Returns the {@code URI} that the response was received from. This may be
-     * different from the request {@code URI} if redirection occurred.
-     *
-     * @return the URI of the response
-     */
-    public abstract URI uri();
-
-    /**
-     * Returns the HTTP protocol version that was used for this response.
-     *
-     * @return HTTP protocol version
-     */
-    public abstract HttpClient.Version version();
-
-
-    private static String pathForSecurityCheck(Path path) {
-        return path.toFile().getPath();
-    }
-
-    /** A body handler that is further restricted by a given ACC. */
-    interface UntrustedBodyHandler<T> extends BodyHandler<T> {
-        void setAccessControlContext(AccessControlContext acc);
-    }
-
-    /**
-     * A Path body handler.
-     *
-     * Note: Exists mainly too allow setting of the senders ACC post creation of
-     * the handler.
-     */
-    static class PathBodyHandler implements UntrustedBodyHandler<Path> {
-        private final Path file;
-        private final OpenOption[]openOptions;
-        private volatile AccessControlContext acc;
-
-        PathBodyHandler(Path file, OpenOption... openOptions) {
-            this.file = file;
-            this.openOptions = openOptions;
-        }
-
-        @Override
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
-        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
-            ResponseSubscribers.PathSubscriber bs = (ResponseSubscribers.PathSubscriber)
-                    BodySubscriber.asFileImpl(file, openOptions);
-            bs.setAccessControlContext(acc);
-            return bs;
-        }
-    }
-
-    // Similar to Path body handler, but for file download. Supports setting ACC.
-    static class FileDownloadBodyHandler implements UntrustedBodyHandler<Path> {
-        private final Path directory;
-        private final OpenOption[]openOptions;
-        private volatile AccessControlContext acc;
-
-        FileDownloadBodyHandler(Path directory, OpenOption... openOptions) {
-            this.directory = directory;
-            this.openOptions = openOptions;
-        }
-
-        @Override
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
-        public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
-            String dispoHeader = headers.firstValue("Content-Disposition")
-                    .orElseThrow(() -> unchecked(new IOException("No Content-Disposition")));
-            if (!dispoHeader.startsWith("attachment;")) {
-                throw unchecked(new IOException("Unknown Content-Disposition type"));
-            }
-            int n = dispoHeader.indexOf("filename=");
-            if (n == -1) {
-                throw unchecked(new IOException("Bad Content-Disposition type"));
-            }
-            int lastsemi = dispoHeader.lastIndexOf(';');
-            String disposition;
-            if (lastsemi < n) {
-                disposition = dispoHeader.substring(n + 9);
-            } else {
-                disposition = dispoHeader.substring(n + 9, lastsemi);
-            }
-            Path file = Paths.get(directory.toString(), disposition);
-
-            ResponseSubscribers.PathSubscriber bs = (ResponseSubscribers.PathSubscriber)
-                    BodySubscriber.asFileImpl(file, openOptions);
-            bs.setAccessControlContext(acc);
-            return bs;
-        }
-    }
-
-    /**
-     * A handler for response bodies.
-     * {@Incubating}
-     *
-     * <p> This is a function that takes two parameters: the response status code,
-     * and the response headers, and which returns a {@linkplain BodySubscriber}.
-     * The function is always called just before the response body is read. Its
-     * implementation may examine the status code or headers and must decide,
-     * whether to accept the response body or discard it, and if accepting it,
-     * exactly how to handle it.
-     *
-     * <p> Some pre-defined implementations which do not utilize the status code
-     * or headers (meaning the body is always accepted) are defined:
-     * <ul><li>{@link #asByteArray() }</li>
-     * <li>{@link #asByteArrayConsumer(java.util.function.Consumer)
-     * asByteArrayConsumer(Consumer)}</li>
-     * <li>{@link #asString(java.nio.charset.Charset) asString(Charset)}</li>
-     * <li>{@link #asFile(Path, OpenOption...)
-     * asFile(Path,OpenOption...)}</li>
-     * <li>{@link #asFileDownload(java.nio.file.Path,OpenOption...)
-     * asFileDownload(Path,OpenOption...)}</li>
-     * <li>{@link #asInputStream() asInputStream()}</li>
-     * <li>{@link #discard(Object) }</li>
-     * <li>{@link #buffering(BodyHandler, int)
-     * buffering(BodyHandler,int)}</li>
-     * </ul>
-     *
-     * <p> These implementations return the equivalent {@link BodySubscriber}.
-     * Alternatively, the handler can be used to examine the status code
-     * or headers and return different body subscribers as appropriate.
-     *
-     * <p><b>Examples of handler usage</b>
-     *
-     * <p> The first example uses one of the predefined handler functions which
-     * ignores the response headers and status, and always process the response
-     * body in the same way.
-     * <pre>
-     * {@code
-     *      HttpResponse<Path> resp = HttpRequest
-     *              .create(URI.create("http://www.foo.com"))
-     *              .GET()
-     *              .response(BodyHandler.asFile(Paths.get("/tmp/f")));
-     * }
-     * </pre>
-     * Note, that even though these pre-defined handlers ignore the status code
-     * and headers, this information is still accessible from the
-     * {@code HttpResponse} when it is returned.
-     *
-     * <p> In the second example, the function returns a different subscriber
-     * depending on the status code.
-     * <pre>
-     * {@code
-     *      HttpResponse<Path> resp1 = HttpRequest
-     *              .create(URI.create("http://www.foo.com"))
-     *              .GET()
-     *              .response(
-     *                  (status, headers) -> status == 200
-     *                      ? BodySubscriber.asFile(Paths.get("/tmp/f"))
-     *                      : BodySubscriber.discard(Paths.get("/NULL")));
-     * }
-     * </pre>
-     *
-     * @param <T> the response body type
-     */
-    @FunctionalInterface
-    public interface BodyHandler<T> {
-
-        /**
-         * Returns a {@link BodySubscriber BodySubscriber} considering the given
-         * response status code and headers. This method is always called before
-         * the body is read and its implementation can decide to keep the body
-         * and store it somewhere, or else discard it by returning the {@code
-         * BodySubscriber} returned from {@link BodySubscriber#discard(Object)
-         * discard}.
-         *
-         * @param statusCode the HTTP status code received
-         * @param responseHeaders the response headers received
-         * @return a body subscriber
-         */
-        public BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
-
-        /**
-         * Returns a response body handler that returns a {@link BodySubscriber
-         * BodySubscriber}{@code <Void>} obtained from {@linkplain
-         * BodySubscriber#fromSubscriber(Subscriber)}, with the given
-         * {@code subscriber}.
-         *
-         * <p> The response body is not available through this, or the {@code
-         * HttpResponse} API, but instead all response body is forwarded to the
-         * given {@code subscriber}, which should make it available, if
-         * appropriate, through some other mechanism, e.g. an entry in a
-         * database, etc.
-         *
-         * @apiNote This method can be used as an adapter between {@code
-         * BodySubscriber} and {@code Flow.Subscriber}.
-         *
-         * <p> For example:
-         * <pre> {@code
-         *  TextSubscriber subscriber = new TextSubscriber();
-         *  HttpResponse<Void> response = client.sendAsync(request,
-         *      BodyHandler.fromSubscriber(subscriber)).join();
-         *  System.out.println(response.statusCode());
-         * }</pre>
-         *
-         * @param subscriber the subscriber
-         * @return a response body handler
-         */
-        public static BodyHandler<Void>
-        fromSubscriber(Subscriber<? super List<ByteBuffer>> subscriber) {
-            Objects.requireNonNull(subscriber);
-            return (status, headers) -> BodySubscriber.fromSubscriber(subscriber,
-                                                                      s -> null);
-        }
-
-        /**
-         * Returns a response body handler that returns a {@link BodySubscriber
-         * BodySubscriber}{@code <T>} obtained from {@link
-         * BodySubscriber#fromSubscriber(Subscriber, Function)}, with the
-         * given {@code subscriber} and {@code finisher} function.
-         *
-         * <p> The given {@code finisher} function is applied after the given
-         * subscriber's {@code onComplete} has been invoked. The {@code finisher}
-         * function is invoked with the given subscriber, and returns a value
-         * that is set as the response's body.
-         *
-         * @apiNote This method can be used as an adapter between {@code
-         * BodySubscriber} and {@code Flow.Subscriber}.
-         *
-         * <p> For example:
-         * <pre> {@code
-         * TextSubscriber subscriber = ...;  // accumulates bytes and transforms them into a String
-         * HttpResponse<String> response = client.sendAsync(request,
-         *     BodyHandler.fromSubscriber(subscriber, TextSubscriber::getTextResult)).join();
-         * String text = response.body();
-         * }</pre>
-         *
-         * @param <S> the type of the Subscriber
-         * @param <T> the type of the response body
-         * @param subscriber the subscriber
-         * @param finisher a function to be applied after the subscriber has completed
-         * @return a response body handler
-         */
-        public static <S extends Subscriber<? super List<ByteBuffer>>,T> BodyHandler<T>
-        fromSubscriber(S subscriber, Function<S,T> finisher) {
-            Objects.requireNonNull(subscriber);
-            Objects.requireNonNull(finisher);
-            return (status, headers) -> BodySubscriber.fromSubscriber(subscriber,
-                                                                      finisher);
-        }
-
-        /**
-         * Returns a response body handler which discards the response body and
-         * uses the given value as a replacement for it.
-         *
-         * @param <U> the response body type
-         * @param value the value of U to return as the body, may be {@code null}
-         * @return a response body handler
-         */
-        public static <U> BodyHandler<U> discard(U value) {
-            return (status, headers) -> BodySubscriber.discard(value);
-        }
-
-        /**
-         * Returns a {@code BodyHandler<String>} that returns a
-         * {@link BodySubscriber BodySubscriber}{@code <String>} obtained from
-         * {@link BodySubscriber#asString(Charset) BodySubscriber.asString(Charset)}.
-         * The body is decoded using the given character set.
-         *
-         * @param charset the character set to convert the body with
-         * @return a response body handler
-         */
-        public static BodyHandler<String> asString(Charset charset) {
-            Objects.requireNonNull(charset);
-            return (status, headers) -> BodySubscriber.asString(charset);
-        }
-
-        /**
-         * Returns a {@code BodyHandler<Path>} that returns a
-         * {@link BodySubscriber BodySubscriber}{@code <Path>} obtained from
-         * {@link BodySubscriber#asFile(Path, OpenOption...)
-         * BodySubscriber.asFile(Path,OpenOption...)}.
-         *
-         * <p> When the {@code HttpResponse} object is returned, the body has
-         * been completely written to the file, and {@link #body()} returns a
-         * reference to its {@link Path}.
-         *
-         * @param file the filename to store the body in
-         * @param openOptions any options to use when opening/creating the file
-         * @return a response body handler
-         * @throws SecurityException If a security manager has been installed
-         *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file. The {@link
-         *          SecurityManager#checkDelete(String) checkDelete} method is
-         *          invoked to check delete access if the file is opened with
-         *          the {@code DELETE_ON_CLOSE} option.
-         */
-        public static BodyHandler<Path> asFile(Path file, OpenOption... openOptions) {
-            Objects.requireNonNull(file);
-            List<OpenOption> opts = List.of(openOptions);
-            SecurityManager sm = System.getSecurityManager();
-            if (sm != null) {
-                String fn = pathForSecurityCheck(file);
-                sm.checkWrite(fn);
-                if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE))
-                    sm.checkDelete(fn);
-                if (opts.contains(StandardOpenOption.READ))
-                    sm.checkRead(fn);
-            }
-            return new PathBodyHandler(file, openOptions);
-        }
-
-        /**
-         * Returns a {@code BodyHandler<Path>} that returns a
-         * {@link BodySubscriber BodySubscriber}{@code <Path>} obtained from
-         * {@link BodySubscriber#asFile(Path) BodySubscriber.asFile(Path)}.
-         *
-         * <p> When the {@code HttpResponse} object is returned, the body has
-         * been completely written to the file, and {@link #body()} returns a
-         * reference to its {@link Path}.
-         *
-         * @param file the file to store the body in
-         * @return a response body handler
-         * @throws SecurityException if a security manager has been installed
-         *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file
-         */
-        public static BodyHandler<Path> asFile(Path file) {
-            return BodyHandler.asFile(file, StandardOpenOption.CREATE,
-                                            StandardOpenOption.WRITE);
-        }
-
-        /**
-         * Returns a {@code BodyHandler<Path>} that returns a
-         * {@link BodySubscriber BodySubscriber}&lt;{@link Path}&gt;
-         * where the download directory is specified, but the filename is
-         * obtained from the {@code Content-Disposition} response header. The
-         * {@code Content-Disposition} header must specify the <i>attachment</i>
-         * type and must also contain a <i>filename</i> parameter. If the
-         * filename specifies multiple path components only the final component
-         * is used as the filename (with the given directory name).
-         *
-         * <p> When the {@code HttpResponse} object is returned, the body has
-         * been completely written to the file and {@link #body()} returns a
-         * {@code Path} object for the file. The returned {@code Path} is the
-         * combination of the supplied directory name and the file name supplied
-         * by the server. If the destination directory does not exist or cannot
-         * be written to, then the response will fail with an {@link IOException}.
-         *
-         * @param directory the directory to store the file in
-         * @param openOptions open options
-         * @return a response body handler
-         * @throws SecurityException If a security manager has been installed
-         *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file. The {@link
-         *          SecurityManager#checkDelete(String) checkDelete} method is
-         *          invoked to check delete access if the file is opened with
-         *          the {@code DELETE_ON_CLOSE} option.
-         */
-         //####: check if the dir exists and is writable??
-        public static BodyHandler<Path> asFileDownload(Path directory,
-                                                       OpenOption... openOptions) {
-            Objects.requireNonNull(directory);
-            List<OpenOption> opts = List.of(openOptions);
-            SecurityManager sm = System.getSecurityManager();
-            if (sm != null) {
-                String fn = pathForSecurityCheck(directory);
-                sm.checkWrite(fn);
-                if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE))
-                    sm.checkDelete(fn);
-                if (opts.contains(StandardOpenOption.READ))
-                    sm.checkRead(fn);
-            }
-            return new FileDownloadBodyHandler(directory, openOptions);
-        }
-
-        /**
-         * Returns a {@code BodyHandler<InputStream>} that returns a
-         * {@link BodySubscriber BodySubscriber}{@code <InputStream>} obtained
-         * from {@link BodySubscriber#asInputStream() BodySubscriber.asInputStream}.
-         *
-         * <p> When the {@code HttpResponse} object is returned, the response
-         * headers will have been completely read, but the body may not have
-         * been fully received yet. The {@link #body()} method returns an
-         * {@link InputStream} from which the body can be read as it is received.
-         *
-         * @apiNote See {@link BodySubscriber#asInputStream()} for more information.
-         *
-         * @return a response body handler
-         */
-        public static BodyHandler<InputStream> asInputStream() {
-            return (status, headers) -> BodySubscriber.asInputStream();
-        }
-
-        /**
-         * Returns a {@code BodyHandler<Void>} that returns a
-         * {@link BodySubscriber BodySubscriber}{@code <Void>} obtained from
-         * {@link BodySubscriber#asByteArrayConsumer(Consumer)
-         * BodySubscriber.asByteArrayConsumer(Consumer)}.
-         *
-         * <p> When the {@code HttpResponse} object is returned, the body has
-         * been completely written to the consumer.
-         *
-         * @param consumer a Consumer to accept the response body
-         * @return a response body handler
-         */
-        public static BodyHandler<Void> asByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
-            Objects.requireNonNull(consumer);
-            return (status, headers) -> BodySubscriber.asByteArrayConsumer(consumer);
-        }
-
-        /**
-         * Returns a {@code BodyHandler<byte[]>} that returns a
-         * {@link BodySubscriber BodySubscriber}&lt;{@code byte[]}&gt; obtained
-         * from {@link BodySubscriber#asByteArray() BodySubscriber.asByteArray()}.
-         *
-         * <p> When the {@code HttpResponse} object is returned, the body has
-         * been completely written to the byte array.
-         *
-         * @return a response body handler
-         */
-        public static BodyHandler<byte[]> asByteArray() {
-            return (status, headers) -> BodySubscriber.asByteArray();
-        }
-
-        /**
-         * Returns a {@code BodyHandler<String>} that returns a
-         * {@link BodySubscriber BodySubscriber}{@code <String>} obtained from
-         * {@link BodySubscriber#asString(java.nio.charset.Charset)
-         * BodySubscriber.asString(Charset)}. The body is
-         * decoded using the character set specified in
-         * the {@code Content-encoding} response header. If there is no such
-         * header, or the character set is not supported, then
-         * {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} is used.
-         *
-         * <p> When the {@code HttpResponse} object is returned, the body has
-         * been completely written to the string.
-         *
-         * @return a response body handler
-         */
-        public static BodyHandler<String> asString() {
-            return (status, headers) -> BodySubscriber.asString(charsetFrom(headers));
-        }
-
-        /**
-         * Returns a {@code BodyHandler} which, when invoked, returns a {@linkplain
-         * BodySubscriber#buffering(BodySubscriber,int) buffering BodySubscriber}
-         * that buffers data before delivering it to the downstream subscriber.
-         * These {@code BodySubscriber} instances are created by calling
-         * {@linkplain BodySubscriber#buffering(BodySubscriber,int)
-         * BodySubscriber.buffering} with a subscriber obtained from the given
-         * downstream handler and the {@code bufferSize} parameter.
-         *
-         * @param downstreamHandler the downstream handler
-         * @param bufferSize the buffer size parameter passed to {@linkplain
-         *        BodySubscriber#buffering(BodySubscriber,int) BodySubscriber.buffering}
-         * @return a body handler
-         * @throws IllegalArgumentException if {@code bufferSize <= 0}
-         */
-         public static <T> BodyHandler<T> buffering(BodyHandler<T> downstreamHandler,
-                                                    int bufferSize) {
-             Objects.requireNonNull(downstreamHandler);
-             if (bufferSize <= 0)
-                 throw new IllegalArgumentException("must be greater than 0");
-             return (status, headers) -> BodySubscriber
-                     .buffering(downstreamHandler.apply(status, headers),
-                                bufferSize);
-         }
-    }
-
-    /**
-     * A subscriber for response bodies.
-     * {@Incubating}
-     *
-     * <p> The object acts as a {@link Flow.Subscriber}&lt;{@link List}&lt;{@link
-     * ByteBuffer}&gt;&gt; to the HTTP client implementation, which publishes
-     * unmodifiable lists of ByteBuffers containing the response body. The Flow
-     * of data, as well as the order of ByteBuffers in the Flow lists, is a
-     * strictly ordered representation of the response body. Both the Lists and
-     * the ByteBuffers, once passed to the subscriber, are no longer used by the
-     * HTTP client. The subscriber converts the incoming buffers of data to some
-     * user-defined object type {@code T}.
-     *
-     * <p> The {@link #getBody()} method returns a {@link CompletionStage}{@code
-     * <T>} that provides the response body object. The {@code CompletionStage}
-     * must be obtainable at any time. When it completes depends on the nature
-     * of type {@code T}. In many cases, when {@code T} represents the entire
-     * body after being read then it completes after the body has been read. If
-     * {@code T} is a streaming type such as {@link java.io.InputStream} then it
-     * completes before the body has been read, because the calling code uses it
-     * to consume the data.
-     *
-     * @apiNote To ensure that all resources associated with the
-     * corresponding exchange are properly released, an implementation
-     * of {@code BodySubscriber} must ensure to {@linkplain
-     * Flow.Subscription#request request} more data until {@link
-     * #onComplete() onComplete} or {@link #onError(Throwable) onError}
-     * are signalled, or {@linkplain Flow.Subscription#request cancel} its
-     * {@linkplain #onSubscribe(Flow.Subscription) subscription}
-     * if unable or unwilling to do so.
-     * Calling {@code cancel} before exhausting the data may cause
-     * the underlying HTTP connection to be closed and prevent it
-     * from being reused for subsequent operations.
-     *
-     * @param <T> the response body type
-     */
-    public interface BodySubscriber<T>
-            extends Flow.Subscriber<List<ByteBuffer>> {
-
-        /**
-         * Returns a {@code CompletionStage} which when completed will return
-         * the response body object.
-         *
-         * @return a CompletionStage for the response body
-         */
-        public CompletionStage<T> getBody();
-
-        /**
-         * Returns a body subscriber that forwards all response body to the
-         * given {@code Flow.Subscriber}. The {@linkplain #getBody()} completion
-         * stage} of the returned body subscriber completes after one of the
-         * given subscribers {@code onComplete} or {@code onError} has been
-         * invoked.
-         *
-         * @apiNote This method can be used as an adapter between {@code
-         * BodySubscriber} and {@code Flow.Subscriber}.
-         *
-         * @param <S> the type of the Subscriber
-         * @param subscriber the subscriber
-         * @return a body subscriber
-         */
-        public static <S extends Subscriber<? super List<ByteBuffer>>> BodySubscriber<Void>
-        fromSubscriber(S subscriber) {
-            return new ResponseSubscribers.SubscriberAdapter<S,Void>(subscriber, s -> null);
-        }
-
-        /**
-         * Returns a body subscriber that forwards all response body to the
-         * given {@code Flow.Subscriber}. The {@linkplain #getBody()} completion
-         * stage} of the returned body subscriber completes after one of the
-         * given subscribers {@code onComplete} or {@code onError} has been
-         * invoked.
-         *
-         * <p> The given {@code finisher} function is applied after the given
-         * subscriber's {@code onComplete} has been invoked. The {@code finisher}
-         * function is invoked with the given subscriber, and returns a value
-         * that is set as the response's body.
-         *
-         * @apiNote This method can be used as an adapter between {@code
-         * BodySubscriber} and {@code Flow.Subscriber}.
-         *
-         * @param <S> the type of the Subscriber
-         * @param <T> the type of the response body
-         * @param subscriber the subscriber
-         * @param finisher a function to be applied after the subscriber has
-         *                 completed
-         * @return a body subscriber
-         */
-        public static <S extends Subscriber<? super List<ByteBuffer>>,T> BodySubscriber<T>
-        fromSubscriber(S subscriber,
-                       Function<S,T> finisher) {
-            return new ResponseSubscribers.SubscriberAdapter<S,T>(subscriber, finisher);
-        }
-
-        /**
-         * Returns a body subscriber which stores the response body as a {@code
-         * String} converted using the given {@code Charset}.
-         *
-         * <p> The {@link HttpResponse} using this subscriber is available after
-         * the entire response has been read.
-         *
-         * @param charset the character set to convert the String with
-         * @return a body subscriber
-         */
-        public static BodySubscriber<String> asString(Charset charset) {
-            Objects.requireNonNull(charset);
-            return new ResponseSubscribers.ByteArraySubscriber<>(
-                    bytes -> new String(bytes, charset)
-            );
-        }
-
-        /**
-         * Returns a {@code BodySubscriber} which stores the response body as a
-         * byte array.
-         *
-         * <p> The {@link HttpResponse} using this subscriber is available after
-         * the entire response has been read.
-         *
-         * @return a body subscriber
-         */
-        public static BodySubscriber<byte[]> asByteArray() {
-            return new ResponseSubscribers.ByteArraySubscriber<>(
-                    Function.identity() // no conversion
-            );
-        }
-
-        // no security check
-        private static BodySubscriber<Path> asFileImpl(Path file, OpenOption... openOptions) {
-            return new ResponseSubscribers.PathSubscriber(file, openOptions);
-        }
-
-        /**
-         * Returns a {@code BodySubscriber} which stores the response body in a
-         * file opened with the given options and name. The file will be opened
-         * with the given options using {@link FileChannel#open(Path,OpenOption...)
-         * FileChannel.open} just before the body is read. Any exception thrown
-         * will be returned or thrown from {@link HttpClient#send(HttpRequest,
-         * BodyHandler) HttpClient::send} or {@link HttpClient#sendAsync(HttpRequest,
-         * BodyHandler) HttpClient::sendAsync} as appropriate.
-         *
-         * <p> The {@link HttpResponse} using this subscriber is available after
-         * the entire response has been read.
-         *
-         * @param file the file to store the body in
-         * @param openOptions the list of options to open the file with
-         * @return a body subscriber
-         * @throws SecurityException If a security manager has been installed
-         *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file. The {@link
-         *          SecurityManager#checkDelete(String) checkDelete} method is
-         *          invoked to check delete access if the file is opened with the
-         *          {@code DELETE_ON_CLOSE} option.
-         */
-        public static BodySubscriber<Path> asFile(Path file, OpenOption... openOptions) {
-            Objects.requireNonNull(file);
-            List<OpenOption> opts = List.of(openOptions);
-            SecurityManager sm = System.getSecurityManager();
-            if (sm != null) {
-                String fn = pathForSecurityCheck(file);
-                sm.checkWrite(fn);
-                if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE))
-                    sm.checkDelete(fn);
-                if (opts.contains(StandardOpenOption.READ))
-                    sm.checkRead(fn);
-            }
-            return asFileImpl(file, openOptions);
-        }
-
-        /**
-         * Returns a {@code BodySubscriber} which stores the response body in a
-         * file opened with the given name. Has the same effect as calling
-         * {@link #asFile(Path, OpenOption...) asFile} with the standard open
-         * options {@code CREATE} and {@code WRITE}
-         *
-         * <p> The {@link HttpResponse} using this subscriber is available after
-         * the entire response has been read.
-         *
-         * @param file the file to store the body in
-         * @return a body subscriber
-         * @throws SecurityException if a security manager has been installed
-         *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file
-         */
-        public static BodySubscriber<Path> asFile(Path file) {
-            return asFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
-        }
-
-        /**
-         * Returns a {@code BodySubscriber} which provides the incoming body
-         * data to the provided Consumer of {@code Optional<byte[]>}. Each
-         * call to {@link Consumer#accept(java.lang.Object) Consumer.accept()}
-         * will contain a non empty {@code Optional}, except for the final
-         * invocation after all body data has been read, when the {@code
-         * Optional} will be empty.
-         *
-         * <p> The {@link HttpResponse} using this subscriber is available after
-         * the entire response has been read.
-         *
-         * @param consumer a Consumer of byte arrays
-         * @return a BodySubscriber
-         */
-        public static BodySubscriber<Void> asByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
-            return new ResponseSubscribers.ConsumerSubscriber(consumer);
-        }
-
-        /**
-         * Returns a {@code BodySubscriber} which streams the response body as
-         * an {@link InputStream}.
-         *
-         * <p> The {@link HttpResponse} using this subscriber is available
-         * immediately after the response headers have been read, without
-         * requiring to wait for the entire body to be processed. The response
-         * body can then be read directly from the {@link InputStream}.
-         *
-         * @apiNote To ensure that all resources associated with the
-         * corresponding exchange are properly released the caller must
-         * ensure to either read all bytes until EOF is reached, or call
-         * {@link InputStream#close} if it is unable or unwilling to do so.
-         * Calling {@code close} before exhausting the stream may cause
-         * the underlying HTTP connection to be closed and prevent it
-         * from being reused for subsequent operations.
-         *
-         * @return a body subscriber that streams the response body as an
-         *         {@link InputStream}.
-         */
-        public static BodySubscriber<InputStream> asInputStream() {
-            return new ResponseSubscribers.HttpResponseInputStream();
-        }
-
-        /**
-         * Returns a response subscriber which discards the response body. The
-         * supplied value is the value that will be returned from
-         * {@link HttpResponse#body()}.
-         *
-         * @param <U> The type of the response body
-         * @param value the value to return from HttpResponse.body(), may be {@code null}
-         * @return a {@code BodySubscriber}
-         */
-        public static <U> BodySubscriber<U> discard(U value) {
-            return new ResponseSubscribers.NullSubscriber<>(Optional.ofNullable(value));
-        }
-
-        /**
-         * Returns a {@code BodySubscriber} which buffers data before delivering
-         * it to the given downstream subscriber. The subscriber guarantees to
-         * deliver {@code buffersize} bytes of data to each invocation of the
-         * downstream's {@linkplain #onNext(Object) onNext} method, except for
-         * the final invocation, just before {@linkplain #onComplete() onComplete}
-         * is invoked. The final invocation of {@code onNext} may contain fewer
-         * than {@code buffersize} bytes.
-         *
-         * <p> The returned subscriber delegates its {@link #getBody()} method
-         * to the downstream subscriber.
-         *
-         * @param downstream the downstream subscriber
-         * @param bufferSize the buffer size
-         * @return a buffering body subscriber
-         * @throws IllegalArgumentException if {@code bufferSize <= 0}
-         */
-         public static <T> BodySubscriber<T> buffering(BodySubscriber<T> downstream,
-                                                       int bufferSize) {
-             if (bufferSize <= 0)
-                 throw new IllegalArgumentException("must be greater than 0");
-             return new BufferingSubscriber<T>(downstream, bufferSize);
-         }
-    }
-
-    /**
-     * A response subscriber for a HTTP/2 multi response.
-     * {@Incubating}
-     *
-     * <p> A multi response comprises a main response, and zero or more additional
-     * responses. Each additional response is sent by the server in response to
-     * requests (PUSH_PROMISEs) that the server also generates. Additional responses are
-     * typically resources that the server expects the client will need which
-     * are related to the initial request.
-     * <p>
-     * Note. Instead of implementing this interface, applications should consider
-     * first using the mechanism (built on this interface) provided by
-     * {@link MultiSubscriber#asMap(java.util.function.Function, boolean)
-     * MultiSubscriber.asMap()} which is a slightly simplified, but also
-     * general purpose interface.
-     * <p>
-     * The server generated requests are also known as <i>push promises</i>.
-     * The server is permitted to send any number of these requests up to the
-     * point where the main response is fully received. Therefore, after
-     * completion of the main response, the final number of additional
-     * responses is known. Additional responses may be canceled, but given that
-     * the server does not wait for any acknowledgment before sending the
-     * response, this must be done quickly to avoid unnecessary data transmission.
-     *
-     * <p> {@code MultiSubscriber}s are parameterized with a type {@code U} which
-     * represents some meaningful aggregate of the responses received. This
-     * would typically be a collection of response or response body objects.
-     *
-     * @param <U> a type representing the aggregated results
-     * @param <T> a type representing all of the response bodies
-     *
-     * @since 9
-     */
-    public interface MultiSubscriber<U,T> {
-        /**
-         * Called for the main request from the user. This {@link HttpRequest}
-         * parameter is the request that was supplied to {@link
-         * HttpClient#sendAsync(HttpRequest, MultiSubscriber)}. The
-         * implementation must return an {@link BodyHandler} for the response
-         * body.
-         *
-         * @param request the request
-         *
-         * @return an optional body handler
-         */
-        BodyHandler<T> onRequest(HttpRequest request);
-
-        /**
-         * Called for each push promise that is received. The {@link HttpRequest}
-         * parameter represents the PUSH_PROMISE. The implementation must return
-         * an {@code Optional} of {@link BodyHandler} for the response body.
-         * Different handlers (of the same type) can be returned for different
-         * pushes within the same multi send. If no handler (an empty {@code
-         * Optional}) is returned, then the push will be canceled. If required,
-         * the {@code CompletableFuture<Void>} supplied to the {@code
-         * onFinalPushPromise} parameter of {@link
-         * #completion(CompletableFuture, CompletableFuture)} can be used to
-         * determine when the final PUSH_PROMISE is received.
-         *
-         * @param pushPromise the push promise
-         *
-         * @return an optional body handler
-         */
-        Optional<BodyHandler<T>> onPushPromise(HttpRequest pushPromise);
-
-        /**
-         * Called for each response received. For each request either one of
-         * onResponse() or onError() is guaranteed to be called, but not both.
-         *
-         * <p> Note: The reason for switching to this callback interface rather
-         * than using CompletableFutures supplied to onRequest() is that there
-         * is a subtle interaction between those CFs and the CF returned from
-         * completion() (or when onComplete() was called formerly). The completion()
-         * CF will not complete until after all of the work done by the onResponse()
-         * calls is done. Whereas if you just create CF's dependent on a supplied
-         * CF (to onRequest()) then the implementation has no visibility of the
-         * dependent CFs and can't guarantee to call onComplete() (or complete
-         * the completion() CF) after the dependent CFs complete.
-         *
-         * @param response the response received
-         */
-        void onResponse(HttpResponse<T> response);
-
-        /**
-         * Called if an error occurs receiving a response. For each request
-         * either one of onResponse() or onError() is guaranteed to be called,
-         * but not both.
-         *
-         * @param request the main request or subsequent push promise
-         * @param t the Throwable that caused the error
-         */
-        void onError(HttpRequest request, Throwable t);
-
-        /**
-         * Returns a {@link java.util.concurrent.CompletableFuture}{@code <U>}
-         * which completes when the aggregate result object itself is available.
-         * It is expected that the returned {@code CompletableFuture} will depend
-         * on one of the given {@code CompletableFuture<Void}s which themselves
-         * complete after all individual responses associated with the multi
-         * response have completed, or after all push promises have been received.
-         * This method is called after {@link #onRequest(HttpRequest)} but
-         * before any other methods.
-         *
-         * @implNote Implementations might follow the pattern shown below
-         * <pre>
-         * {@code
-         *      CompletableFuture<U> completion(
-         *              CompletableFuture<Void> onComplete,
-         *              CompletableFuture<Void> onFinalPushPromise)
-         *      {
-         *          return onComplete.thenApply((v) -> {
-         *              U u = ... instantiate and populate a U instance
-         *              return u;
-         *          });
-         *      }
-         * }
-         * </pre>
-         *
-         * @param onComplete a CompletableFuture which completes after all
-         * responses have been received relating to this multi request.
-         *
-         * @param onFinalPushPromise CompletableFuture which completes after all
-         * push promises have been received.
-         *
-         * @return the aggregate CF response object
-         */
-        CompletableFuture<U> completion(CompletableFuture<Void> onComplete,
-                CompletableFuture<Void> onFinalPushPromise);
-
-        /**
-         * Returns a general purpose handler for multi responses. The aggregated
-         * result object produced by this handler is a
-         * {@code Map<HttpRequest,CompletableFuture<HttpResponse<V>>>}. Each
-         * request (both the original user generated request and each server
-         * generated push promise) is returned as a key of the map. The value
-         * corresponding to each key is a
-         * {@code CompletableFuture<HttpResponse<V>>}.
-         *
-         * <p> There are two ways to use these handlers, depending on the value
-         * of the <i>completion</I> parameter. If completion is true, then the
-         * aggregated result will be available after all responses have
-         * themselves completed. If <i>completion</i> is false, then the
-         * aggregated result will be available immediately after the last push
-         * promise was received. In the former case, this implies that all the
-         * CompletableFutures in the map values will have completed. In the
-         * latter case, they may or may not have completed yet.
-         *
-         * <p> The simplest way to use these handlers is to set completion to
-         * {@code true}, and then all (results) values in the Map will be
-         * accessible without blocking.
-         * <p>
-         * See {@link #asMap(java.util.function.Function, boolean)}
-         * for a code sample of using this interface.
-         *
-         * <p> See {@link #asMap(Function, boolean)} for a code sample of using
-         * this interface.
-         *
-         * @param <V> the body type used for all responses
-         * @param reqHandler a function invoked for the user's request and each
-         *                   push promise
-         * @param completion {@code true} if the aggregate CompletableFuture
-         *                   completes after all responses have been received,
-         *                   or {@code false} after all push promises received
-         *
-         * @return a MultiSubscriber
-         */
-        public static <V> MultiSubscriber<MultiMapResult<V>,V> asMap(
-                Function<HttpRequest, Optional<HttpResponse.BodyHandler<V>>> reqHandler,
-                boolean completion) {
-            return new MultiSubscriberImpl<V>(reqHandler.andThen(optv -> optv.get()),
-                                              reqHandler,
-                                              completion);
-        }
-
-        /**
-         * Returns a general purpose handler for multi responses. This is a
-         * convenience method which invokes {@link #asMap(Function,boolean)
-         * asMap(Function, true)} meaning that the aggregate result
-         * object completes after all responses have been received.
-         *
-         * <p><b>Example usage:</b>
-         * <br>
-         * <pre>
-         * {@code
-         *          HttpRequest request = HttpRequest.newBuilder()
-         *                  .uri(URI.create("https://www.foo.com/"))
-         *                  .GET()
-         *                  .build();
-         *
-         *          HttpClient client = HttpClient.newHttpClient();
-         *
-         *          Map<HttpRequest,CompletableFuture<HttpResponse<String>>> results = client
-         *              .sendAsync(request, MultiSubscriber.asMap(
-         *                  (req) -> Optional.of(HttpResponse.BodyHandler.asString())))
-         *              .join();
-         * }</pre>
-         *
-         * <p> The lambda in this example is the simplest possible implementation,
-         * where neither the incoming requests are examined, nor the response
-         * headers, and every push that the server sends is accepted. When the
-         * join() call returns, all {@code HttpResponse}s and their associated
-         * body objects are available.
-         *
-         * @param <V> the body type used for all responses
-         * @param reqHandler a function invoked for each push promise and the
-         *                   main request
-         * @return a MultiSubscriber
-         */
-        public static <V> MultiSubscriber<MultiMapResult<V>,V> asMap(
-                Function<HttpRequest, Optional<HttpResponse.BodyHandler<V>>> reqHandler) {
-
-            return asMap(reqHandler, true);
-        }
-
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpResponseImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,186 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Supplier;
-import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.internal.websocket.RawChannel;
-
-/**
- * The implementation class for HttpResponse
- */
-class HttpResponseImpl<T> extends HttpResponse<T> implements RawChannel.Provider {
-
-    final int responseCode;
-    final Exchange<T> exchange;
-    final HttpRequest initialRequest;
-    final Optional<HttpResponse<T>> previousResponse;
-    final HttpHeaders headers;
-    final SSLParameters sslParameters;
-    final URI uri;
-    final HttpClient.Version version;
-    RawChannel rawchan;
-    final HttpConnection connection;
-    final Stream<T> stream;
-    final T body;
-
-    public HttpResponseImpl(HttpRequest initialRequest,
-                            Response response,
-                            HttpResponse<T> previousResponse,
-                            T body,
-                            Exchange<T> exch) {
-        this.responseCode = response.statusCode();
-        this.exchange = exch;
-        this.initialRequest = initialRequest;
-        this.previousResponse = Optional.ofNullable(previousResponse);
-        this.headers = response.headers();
-        //this.trailers = trailers;
-        this.sslParameters = exch.client().sslParameters();
-        this.uri = response.request().uri();
-        this.version = response.version();
-        this.connection = exch.exchImpl.connection();
-        this.stream = null;
-        this.body = body;
-    }
-
-//    // A response to a PUSH_PROMISE
-//    public HttpResponseImpl(Response response,
-//                            HttpRequestImpl pushRequest,
-//                            ImmutableHeaders headers,
-//                            Stream<T> stream,
-//                            SSLParameters sslParameters,
-//                            T body) {
-//        this.responseCode = response.statusCode();
-//        this.exchange = null;
-//        this.initialRequest = null; // ## fix this
-//        this.finalRequest = pushRequest;
-//        this.headers = headers;
-//        //this.trailers = null;
-//        this.sslParameters = sslParameters;
-//        this.uri = finalRequest.uri(); // TODO: take from headers
-//        this.version = HttpClient.Version.HTTP_2;
-//        this.connection = stream.connection();
-//        this.stream = stream;
-//        this.body = body;
-//    }
-
-    private ExchangeImpl<?> exchangeImpl() {
-        return exchange != null ? exchange.exchImpl : stream;
-    }
-
-    @Override
-    public int statusCode() {
-        return responseCode;
-    }
-
-    @Override
-    public HttpRequest request() {
-        return initialRequest;
-    }
-
-    @Override
-    public Optional<HttpResponse<T>> previousResponse() {
-        return previousResponse;
-    }
-
-    @Override
-    public HttpHeaders headers() {
-        return headers;
-    }
-
-    @Override
-    public T body() {
-        return body;
-    }
-
-    @Override
-    public SSLParameters sslParameters() {
-        return sslParameters;
-    }
-
-    @Override
-    public URI uri() {
-        return uri;
-    }
-
-    @Override
-    public HttpClient.Version version() {
-        return version;
-    }
-    // keepalive flag determines whether connection is closed or kept alive
-    // by reading/skipping data
-
-    /**
-     * Returns a RawChannel that may be used for WebSocket protocol.
-     * @implNote This implementation does not support RawChannel over
-     *           HTTP/2 connections.
-     * @return a RawChannel that may be used for WebSocket protocol.
-     * @throws UnsupportedOperationException if getting a RawChannel over
-     *         this connection is not supported.
-     * @throws IOException if an I/O exception occurs while retrieving
-     *         the channel.
-     */
-    @Override
-    public synchronized RawChannel rawChannel() throws IOException {
-        if (rawchan == null) {
-            ExchangeImpl<?> exchImpl = exchangeImpl();
-            if (!(exchImpl instanceof Http1Exchange)) {
-                // RawChannel is only used for WebSocket - and WebSocket
-                // is not supported over HTTP/2 yet, so we should not come
-                // here. Getting a RawChannel over HTTP/2 might be supported
-                // in the future, but it would entail retrieving any left over
-                // bytes that might have been read but not consumed by the
-                // HTTP/2 connection.
-                throw new UnsupportedOperationException("RawChannel is not supported over HTTP/2");
-            }
-            // Http1Exchange may have some remaining bytes in its
-            // internal buffer.
-            Supplier<ByteBuffer> initial = ((Http1Exchange<?>)exchImpl)::drainLeftOverBytes;
-            rawchan = new RawChannelImpl(exchange.client(), connection, initial);
-        }
-        return rawchan;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        String method = request().method();
-        URI uri = request().uri();
-        String uristring = uri == null ? "" : uri.toString();
-        sb.append('(')
-          .append(method)
-          .append(" ")
-          .append(uristring)
-          .append(") ")
-          .append(statusCode());
-        return sb.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpTimeoutException.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-
-/**
- * Thrown when a response is not received within a specified time period.
- * {@Incubating}
- */
-public class HttpTimeoutException extends IOException {
-
-    private static final long serialVersionUID = 981344271622632951L;
-
-    public HttpTimeoutException(String message) {
-        super(message);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ImmutableHeaders.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.function.Predicate;
-import static java.util.Collections.emptyMap;
-import static java.util.Collections.unmodifiableList;
-import static java.util.Collections.unmodifiableMap;
-import static java.util.Objects.requireNonNull;
-
-final class ImmutableHeaders extends HttpHeaders {
-
-    private final Map<String, List<String>> map;
-
-    public static ImmutableHeaders empty() {
-        return of(emptyMap());
-    }
-
-    public static ImmutableHeaders of(Map<String, List<String>> src) {
-        return of(src, x -> true);
-    }
-
-    public static ImmutableHeaders of(Map<String, List<String>> src,
-                                      Predicate<? super String> keyAllowed) {
-        requireNonNull(src, "src");
-        requireNonNull(keyAllowed, "keyAllowed");
-        return new ImmutableHeaders(src, keyAllowed);
-    }
-
-    private ImmutableHeaders(Map<String, List<String>> src,
-                             Predicate<? super String> keyAllowed) {
-        Map<String, List<String>> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-        src.entrySet().stream()
-                .filter(e -> keyAllowed.test(e.getKey()))
-                .forEach(e ->
-                        {
-                            List<String> values = new ArrayList<>(e.getValue());
-                            m.put(e.getKey(), unmodifiableList(values));
-                        }
-                );
-        this.map = unmodifiableMap(m);
-    }
-
-    @Override
-    public Map<String, List<String>> map() {
-        return map;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,357 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.time.Duration;
-import java.util.List;
-import java.security.AccessControlContext;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
-import jdk.incubator.http.HttpResponse.UntrustedBodyHandler;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.ConnectionExpiredException;
-import jdk.incubator.http.internal.common.Utils;
-import static jdk.incubator.http.internal.common.MinimalFuture.completedFuture;
-import static jdk.incubator.http.internal.common.MinimalFuture.failedFuture;
-
-/**
- * Encapsulates multiple Exchanges belonging to one HttpRequestImpl.
- * - manages filters
- * - retries due to filters.
- * - I/O errors and most other exceptions get returned directly to user
- *
- * Creates a new Exchange for each request/response interaction
- */
-class MultiExchange<U,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 HttpResponse.MultiSubscriber<U,T> multiResponseSubscriber;
-    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<U,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,
-                  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);
-        }
-        this.exchange = new Exchange<>(request, this);
-        this.multiResponseSubscriber = null;
-        this.pushGroup = null;
-    }
-
-    /**
-     * MultiExchange with multiple responses (HTTP/2 server pushes).
-     */
-    MultiExchange(HttpRequest userRequest,
-                  HttpRequestImpl requestImpl,
-                  HttpClientImpl client,
-                  HttpResponse.MultiSubscriber<U, T> multiResponseSubscriber,
-                  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.multiResponseSubscriber = multiResponseSubscriber;
-        this.pushGroup = new PushGroup<>(multiResponseSubscriber, request, acc);
-        this.exchange = new Exchange<>(request, this);
-        this.responseHandler = pushGroup.mainResponseHandler();
-    }
-
-//    CompletableFuture<Void> multiCompletionCF() {
-//        return pushGroup.groupResult();
-//    }
-
-    private synchronized Exchange<T> getExchange() {
-        return exchange;
-    }
-
-    HttpClientImpl client() {
-        return client;
-    }
-
-//    HttpClient.Redirect followRedirects() {
-//        return client.followRedirects();
-//    }
-
-    HttpClient.Version version() {
-        return request.version().orElse(client.version());
-    }
-
-    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;
-                            });
-                    });
-    }
-
-    CompletableFuture<U> multiResponseAsync() {
-        CompletableFuture<Void> start = new MinimalFuture<>();
-        CompletableFuture<HttpResponse<T>> cf = responseAsync0(start);
-        CompletableFuture<HttpResponse<T>> mainResponse =
-                cf.thenApply(b -> {
-                        multiResponseSubscriber.onResponse(b);
-                        pushGroup.noMorePushes(true);
-                        return b; });
-        pushGroup.setMainResponse(mainResponse);
-        CompletableFuture<U> res = multiResponseSubscriber.completion(pushGroup.groupResult(),
-                                                                      pushGroup.pushesCF());
-        start.completeAsync( () -> null, executor); // trigger execution
-        return res;
-    }
-
-    private CompletableFuture<Response> responseAsyncImpl() {
-        CompletableFuture<Response> cf;
-        if (attempts.incrementAndGet() > max_attempts) {
-            cf = failedFuture(new IOException("Too many retries", retryCause));
-        } else {
-            if (currentreq.timeout().isPresent()) {
-                timedEvent = new TimedEvent(currentreq.timeout().get());
-                client.registerTimer(timedEvent);
-            }
-            try {
-                // 1. apply request filters
-                requestFilters(currentreq);
-            } catch (IOException e) {
-                return failedFuture(e);
-            }
-            Exchange<T> exch = getExchange();
-            // 2. get response
-            cf = exch.responseAsync()
-                     .thenCompose((Response response) -> {
-                        HttpRequestImpl newrequest;
-                        try {
-                            // 3. apply response filters
-                            newrequest = responseFilters(response);
-                        } catch (IOException e) {
-                            return failedFuture(e);
-                        }
-                        // 4. check filter result and repeat or continue
-                        if (newrequest == null) {
-                            if (attempts.get() > 1) {
-                                Log.logError("Succeeded on attempt: " + attempts);
-                            }
-                            return completedFuture(response);
-                        } else {
-                            this.response =
-                                new HttpResponseImpl<>(currentreq, response, this.response, null, exch);
-                            Exchange<T> oldExch = exch;
-                            return exch.ignoreBody().handle((r,t) -> {
-                                currentreq = newrequest;
-                                expiredOnce = false;
-                                setExchange(new Exchange<>(currentreq, this, acc));
-                                return responseAsyncImpl();
-                            }).thenCompose(Function.identity());
-                        } })
-                     .handle((response, ex) -> {
-                        // 5. handle errors and cancel any timer set
-                        cancelTimer();
-                        if (ex == null) {
-                            assert response != null;
-                            return completedFuture(response);
-                        }
-                        // all exceptions thrown are handled here
-                        CompletableFuture<Response> errorCF = getExceptionalCF(ex);
-                        if (errorCF == null) {
-                            return responseAsyncImpl();
-                        } else {
-                            return errorCF;
-                        } })
-                     .thenCompose(Function.identity());
-        }
-        return cf;
-    }
-
-    /**
-     * Takes a Throwable and returns a suitable CompletableFuture that is
-     * completed exceptionally, or null.
-     */
-    private CompletableFuture<Response> getExceptionalCF(Throwable t) {
-        if ((t instanceof CompletionException) || (t instanceof ExecutionException)) {
-            if (t.getCause() != null) {
-                t = t.getCause();
-            }
-        }
-        if (cancelled && t instanceof IOException) {
-            t = new HttpTimeoutException("request timed out");
-        } else if (t instanceof ConnectionExpiredException) {
-            // allow the retry mechanism to do its work
-            // ####: method (GET,HEAD, not POST?), no bytes written or read ( differentiate? )
-            if (t.getCause() != null) retryCause = t.getCause();
-            if (!expiredOnce) {
-                DEBUG_LOGGER.log(Level.DEBUG,
-                    "MultiExchange: ConnectionExpiredException (async): retrying...",
-                    t);
-                expiredOnce = true;
-                return null;
-            } else {
-                DEBUG_LOGGER.log(Level.DEBUG,
-                    "MultiExchange: ConnectionExpiredException (async): already retried once.",
-                    t);
-                if (t.getCause() != null) t = t.getCause();
-            }
-        }
-        return failedFuture(t);
-    }
-
-    class TimedEvent extends TimeoutEvent {
-        TimedEvent(Duration duration) {
-            super(duration);
-        }
-        @Override
-        public void handle() {
-            DEBUG_LOGGER.log(Level.DEBUG,
-                    "Cancelling MultiExchange due to timeout for request %s",
-                     request);
-            cancel(new HttpTimeoutException("request timed out"));
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiMapResult.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-
-/**
- * A {@link java.util.Map} containing the result of a HTTP/2 request and multi-response.
- * {@Incubating}
- * <p>
- * This is one possible implementation of the aggregate result type {@code <U>} returned
- * from {@link HttpClient#sendAsync(HttpRequest,HttpResponse.MultiSubscriber) }.
- * The map is indexed by {@link HttpRequest} and each value is a
- * {@link java.util.concurrent.CompletableFuture}&lt;
- * {@link HttpResponse}{@code <V>}&gt;
- * <p>
- * A {@code MultiMapResult} is obtained from an invocation such as the one shown below:
- * <p>
- * {@link CompletableFuture}&lt;{@code MultiMapResult<V>}&gt;
- * {@link HttpClient#sendAsync(HttpRequest,
- * HttpResponse.MultiSubscriber) HttpClient.sendAsync(}{@link
- * HttpResponse.MultiSubscriber#asMap(java.util.function.Function)
- * MultiSubscriber.asMap(Function)})
- *
- * @param <V> the response body type for all responses
- */
-public class MultiMapResult<V> implements Map<HttpRequest,CompletableFuture<HttpResponse<V>>> {
-    private final Map<HttpRequest,CompletableFuture<HttpResponse<V>>> map;
-
-    MultiMapResult(Map<HttpRequest,CompletableFuture<HttpResponse<V>>> map) {
-        this.map = map;
-    }
-
-    @Override
-    public int size() {
-        return map.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return map.isEmpty();
-    }
-
-    @Override
-    public boolean containsKey(Object key) {
-        return map.containsKey(key);
-    }
-
-    @Override
-    public boolean containsValue(Object value) {
-        return map.containsValue(value);
-    }
-
-    @Override
-    public CompletableFuture<HttpResponse<V>> get(Object key) {
-        return map.get(key);
-    }
-
-    @Override
-    public CompletableFuture<HttpResponse<V>> put(HttpRequest key, CompletableFuture<HttpResponse<V>> value) {
-        return map.put(key, value);
-    }
-
-    @Override
-    public CompletableFuture<HttpResponse<V>> remove(Object key) {
-        return map.remove(key);
-    }
-
-    @Override
-    public void putAll(Map<? extends HttpRequest, ? extends CompletableFuture<HttpResponse<V>>> m) {
-        map.putAll(m);
-    }
-
-    @Override
-    public void clear() {
-        map.clear();
-    }
-
-    @Override
-    public Set<HttpRequest> keySet() {
-        return map.keySet();
-    }
-
-    @Override
-    public Collection<CompletableFuture<HttpResponse<V>>> values() {
-        return map.values();
-    }
-
-    @Override
-    public Set<Entry<HttpRequest, CompletableFuture<HttpResponse<V>>>> entrySet() {
-        return map.entrySet();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainHttpConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.net.StandardSocketOptions;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Plain raw TCP connection direct to destination.
- * The connection operates in asynchronous non-blocking mode.
- * All reads and writes are done non-blocking.
- */
-class PlainHttpConnection extends HttpConnection {
-
-    private final Object reading = new Object();
-    protected final SocketChannel chan;
-    private final FlowTube tube;
-    private final PlainHttpPublisher writePublisher = new PlainHttpPublisher(reading);
-    private volatile boolean connected;
-    private boolean closed;
-
-    // should be volatile to provide proper synchronization(visibility) action
-
-    final class ConnectEvent extends AsyncEvent {
-        private final CompletableFuture<Void> cf;
-
-        ConnectEvent(CompletableFuture<Void> cf) {
-            this.cf = cf;
-        }
-
-        @Override
-        public SelectableChannel channel() {
-            return chan;
-        }
-
-        @Override
-        public int interestOps() {
-            return SelectionKey.OP_CONNECT;
-        }
-
-        @Override
-        public void handle() {
-            try {
-                assert !connected : "Already connected";
-                assert !chan.isBlocking() : "Unexpected blocking channel";
-                debug.log(Level.DEBUG, "ConnectEvent: finishing connect");
-                boolean finished = chan.finishConnect();
-                assert finished : "Expected channel to be connected";
-                debug.log(Level.DEBUG,
-                          "ConnectEvent: connect finished: %s Local addr: %s", finished, chan.getLocalAddress());
-                connected = true;
-                // complete async since the event runs on the SelectorManager thread
-                cf.completeAsync(() -> null, client().theExecutor());
-            } catch (Throwable e) {
-                client().theExecutor().execute( () -> cf.completeExceptionally(e));
-            }
-        }
-
-        @Override
-        public void abort(IOException ioe) {
-            close();
-            client().theExecutor().execute( () -> cf.completeExceptionally(ioe));
-        }
-    }
-
-    @Override
-    public CompletableFuture<Void> connectAsync() {
-        CompletableFuture<Void> cf = new MinimalFuture<>();
-        try {
-            assert !connected : "Already connected";
-            assert !chan.isBlocking() : "Unexpected blocking channel";
-            boolean finished = false;
-            PrivilegedExceptionAction<Boolean> pa = () -> chan.connect(address);
-            try {
-                 finished = AccessController.doPrivileged(pa);
-            } catch (PrivilegedActionException e) {
-                cf.completeExceptionally(e.getCause());
-            }
-            if (finished) {
-                debug.log(Level.DEBUG, "connect finished without blocking");
-                connected = true;
-                cf.complete(null);
-            } else {
-                debug.log(Level.DEBUG, "registering connect event");
-                client().registerEvent(new ConnectEvent(cf));
-            }
-        } catch (Throwable throwable) {
-            cf.completeExceptionally(throwable);
-        }
-        return cf;
-    }
-
-    @Override
-    SocketChannel channel() {
-        return chan;
-    }
-
-    @Override
-    final FlowTube getConnectionFlow() {
-        return tube;
-    }
-
-    PlainHttpConnection(InetSocketAddress addr, HttpClientImpl client) {
-        super(addr, client);
-        try {
-            this.chan = SocketChannel.open();
-            chan.configureBlocking(false);
-            int bufsize = client.getReceiveBufferSize();
-            if (!trySetReceiveBufferSize(bufsize)) {
-                trySetReceiveBufferSize(256*1024);
-            }
-            chan.setOption(StandardSocketOptions.TCP_NODELAY, true);
-            // wrap the connected channel in a Tube for async reading and writing
-            tube = new SocketTube(client(), chan, Utils::getBuffer);
-        } catch (IOException e) {
-            throw new InternalError(e);
-        }
-    }
-
-    private boolean trySetReceiveBufferSize(int bufsize) {
-        try {
-            chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
-            return true;
-        } catch(IOException x) {
-            debug.log(Level.DEBUG,
-                    "Failed to set receive buffer size to %d on %s",
-                    bufsize, chan);
-        }
-        return false;
-    }
-
-    @Override
-    HttpPublisher publisher() { return writePublisher; }
-
-
-    @Override
-    public String toString() {
-        return "PlainHttpConnection: " + super.toString();
-    }
-
-    /**
-     * Closes this connection
-     */
-    @Override
-    public synchronized void close() {
-        if (closed) {
-            return;
-        }
-        closed = true;
-        try {
-            Log.logTrace("Closing: " + toString());
-            chan.close();
-        } catch (IOException e) {}
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        debug.log(Level.DEBUG, "Shutting down input");
-        chan.shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        debug.log(Level.DEBUG, "Shutting down output");
-        chan.shutdownOutput();
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return new ConnectionPool.CacheKey(address, null);
-    }
-
-    @Override
-    synchronized boolean connected() {
-        return connected;
-    }
-
-
-    @Override
-    boolean isSecure() {
-        return false;
-    }
-
-    @Override
-    boolean isProxied() {
-        return false;
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    private static final class PlainDetachedChannel
-            extends DetachedConnectionChannel {
-        final PlainHttpConnection plainConnection;
-        boolean closed;
-        PlainDetachedChannel(PlainHttpConnection conn) {
-            // We're handing the connection channel over to a web socket.
-            // We need the selector manager's thread to stay alive until
-            // the WebSocket is closed.
-            conn.client().webSocketOpen();
-            this.plainConnection = conn;
-        }
-
-        @Override
-        SocketChannel channel() {
-            return plainConnection.channel();
-        }
-
-        @Override
-        ByteBuffer read() throws IOException {
-            ByteBuffer dst = ByteBuffer.allocate(8192);
-            int n = readImpl(dst);
-            if (n > 0) {
-                return dst;
-            } else if (n == 0) {
-                return Utils.EMPTY_BYTEBUFFER;
-            } else {
-                return null;
-            }
-        }
-
-        @Override
-        public void close() {
-            HttpClientImpl client = plainConnection.client();
-            try {
-                plainConnection.close();
-            } finally {
-                // notify the HttpClientImpl that the websocket is no
-                // no longer operating.
-                synchronized(this) {
-                    if (closed == true) return;
-                    closed = true;
-                }
-                client.webSocketClose();
-            }
-        }
-
-        @Override
-        public long write(ByteBuffer[] buffers, int start, int number)
-                throws IOException
-        {
-            return channel().write(buffers, start, number);
-        }
-
-        @Override
-        public void shutdownInput() throws IOException {
-            plainConnection.shutdownInput();
-        }
-
-        @Override
-        public void shutdownOutput() throws IOException {
-            plainConnection.shutdownOutput();
-        }
-
-        private int readImpl(ByteBuffer buf) throws IOException {
-            int mark = buf.position();
-            int n;
-            n = channel().read(buf);
-            if (n == -1) {
-                return -1;
-            }
-            Utils.flipToMark(buf, mark);
-            return n;
-        }
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    @Override
-    DetachedConnectionChannel detachChannel() {
-        client().cancelRegistration(channel());
-        return new PlainDetachedChannel(this);
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainProxyConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.InetSocketAddress;
-
-class PlainProxyConnection extends PlainHttpConnection {
-
-    PlainProxyConnection(InetSocketAddress proxy, HttpClientImpl client) {
-        super(proxy, client);
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return new ConnectionPool.CacheKey(null, address);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-
-/**
- * A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
- * encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.
- * Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.
- */
-final class PlainTunnelingConnection extends HttpConnection {
-
-    final PlainHttpConnection delegate;
-    protected final InetSocketAddress proxyAddr;
-    private volatile boolean connected;
-
-    protected PlainTunnelingConnection(InetSocketAddress addr,
-                                       InetSocketAddress proxy,
-                                       HttpClientImpl client) {
-        super(addr, client);
-        this.proxyAddr = proxy;
-        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);
-                MultiExchange<Void,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() != 200) {
-                                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
-    HttpPublisher publisher() { return delegate.publisher(); }
-
-    @Override
-    boolean connected() {
-        return connected;
-    }
-
-    @Override
-    SocketChannel channel() {
-        return delegate.channel();
-    }
-
-    @Override
-    FlowTube getConnectionFlow() {
-        return delegate.getConnectionFlow();
-    }
-
-    @Override
-    ConnectionPool.CacheKey cacheKey() {
-        return new ConnectionPool.CacheKey(null, proxyAddr);
-    }
-
-    @Override
-    public void close() {
-        delegate.close();
-        connected = false;
-    }
-
-    @Override
-    void shutdownInput() throws IOException {
-        delegate.shutdownInput();
-    }
-
-    @Override
-    void shutdownOutput() throws IOException {
-        delegate.shutdownOutput();
-    }
-
-    @Override
-    boolean isSecure() {
-        return false;
-    }
-
-    @Override
-    boolean isProxied() {
-        return true;
-    }
-
-    // Support for WebSocket/RawChannelImpl which unfortunately
-    // still depends on synchronous read/writes.
-    // It should be removed when RawChannelImpl moves to using asynchronous APIs.
-    @Override
-    DetachedConnectionChannel detachChannel() {
-        return delegate.detachChannel();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PrivilegedExecutor.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Executes tasks within a given access control context, and by a given executor.
- */
-class PrivilegedExecutor implements Executor {
-
-    /** The underlying executor. May be provided by the user. */
-    final Executor executor;
-    /** The ACC to execute the tasks within. */
-    final AccessControlContext acc;
-
-    public PrivilegedExecutor(Executor executor, AccessControlContext acc) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(acc);
-        this.executor = executor;
-        this.acc = acc;
-    }
-
-    private static class PrivilegedRunnable implements Runnable {
-        private final Runnable r;
-        private final AccessControlContext acc;
-        PrivilegedRunnable(Runnable r, AccessControlContext acc) {
-            this.r = r;
-            this.acc = acc;
-        }
-        @Override
-        public void run() {
-            PrivilegedAction<Void> pa = () -> { r.run(); return null; };
-            AccessController.doPrivileged(pa, acc);
-        }
-    }
-
-    @Override
-    public void execute(Runnable r) {
-        executor.execute(new PrivilegedRunnable(r, acc));
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PullPublisher.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.util.Iterator;
-import java.util.concurrent.Flow;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-
-/**
- * A Publisher that publishes items obtained from the given Iterable. Each new
- * subscription gets a new Iterator.
- */
-class PullPublisher<T> implements Flow.Publisher<T> {
-
-    // Only one of `iterable` and `throwable` can be non-null. throwable is
-    // non-null when an error has been encountered, by the creator of
-    // PullPublisher, while subscribing the subscriber, but before subscribe has
-    // completed.
-    private final Iterable<T> iterable;
-    private final Throwable throwable;
-
-    PullPublisher(Iterable<T> iterable, Throwable throwable) {
-        this.iterable = iterable;
-        this.throwable = throwable;
-    }
-
-    PullPublisher(Iterable<T> iterable) {
-        this(iterable, null);
-    }
-
-    @Override
-    public void subscribe(Flow.Subscriber<? super T> subscriber) {
-        Subscription sub;
-        if (throwable != null) {
-            assert iterable == null : "non-null iterable: " + iterable;
-            sub = new Subscription(subscriber, null, throwable);
-        } else {
-            assert throwable == null : "non-null exception: " + throwable;
-            sub = new Subscription(subscriber, iterable.iterator(), null);
-        }
-        subscriber.onSubscribe(sub);
-
-        if (throwable != null) {
-            sub.pullScheduler.runOrSchedule();
-        }
-    }
-
-    private class Subscription implements Flow.Subscription {
-
-        private final Flow.Subscriber<? super T> subscriber;
-        private final Iterator<T> iter;
-        private volatile boolean completed;
-        private volatile boolean cancelled;
-        private volatile Throwable error;
-        final SequentialScheduler pullScheduler = new SequentialScheduler(new PullTask());
-        private final Demand demand = new Demand();
-
-        Subscription(Flow.Subscriber<? super T> subscriber,
-                     Iterator<T> iter,
-                     Throwable throwable) {
-            this.subscriber = subscriber;
-            this.iter = iter;
-            this.error = throwable;
-        }
-
-        final class PullTask extends SequentialScheduler.CompleteRestartableTask {
-            @Override
-            protected void run() {
-                if (completed || cancelled) {
-                    return;
-                }
-
-                Throwable t = error;
-                if (t != null) {
-                    completed = true;
-                    pullScheduler.stop();
-                    subscriber.onError(t);
-                    return;
-                }
-
-                while (demand.tryDecrement() && !cancelled) {
-                    if (!iter.hasNext()) {
-                        break;
-                    } else {
-                        subscriber.onNext(iter.next());
-                    }
-                }
-                if (!iter.hasNext() && !cancelled) {
-                    completed = true;
-                    pullScheduler.stop();
-                    subscriber.onComplete();
-                }
-            }
-        }
-
-        @Override
-        public void request(long n) {
-            if (cancelled)
-                return;  // no-op
-
-            if (n <= 0) {
-                error = new IllegalArgumentException("illegal non-positive request:" + n);
-            } else {
-                demand.increase(n);
-            }
-            pullScheduler.runOrSchedule();
-        }
-
-        @Override
-        public void cancel() {
-            cancelled = true;
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PushGroup.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.security.AccessControlContext;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.UntrustedBodyHandler;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Log;
-
-/**
- * One PushGroup object is associated with the parent Stream of the pushed
- * Streams. This keeps track of all common state associated with the pushes.
- */
-class PushGroup<U,T> {
-    // the overall completion object, completed when all pushes are done.
-    final CompletableFuture<Void> resultCF;
-    final CompletableFuture<Void> noMorePushesCF;
-
-    volatile Throwable error; // any exception that occurred during pushes
-
-    // CF for main response
-    final CompletableFuture<HttpResponse<T>> mainResponse;
-
-    // user's subscriber object
-    final HttpResponse.MultiSubscriber<U, T> multiSubscriber;
-
-    final HttpResponse.BodyHandler<T> mainBodyHandler;
-
-    private final AccessControlContext acc;
-
-    int numberOfPushes;
-    int remainingPushes;
-    boolean noMorePushes = false;
-
-    PushGroup(HttpResponse.MultiSubscriber<U, T> multiSubscriber,
-              HttpRequestImpl req,
-              AccessControlContext acc) {
-        this(multiSubscriber, req, new MinimalFuture<>(), acc);
-    }
-
-    // Check mainBodyHandler before calling nested constructor.
-    private PushGroup(HttpResponse.MultiSubscriber<U, T> multiSubscriber,
-                      HttpRequestImpl req,
-                      CompletableFuture<HttpResponse<T>> mainResponse,
-                      AccessControlContext acc) {
-        this(multiSubscriber,
-             mainResponse,
-             multiSubscriber.onRequest(req),
-             acc);
-    }
-
-    // This private constructor is called after all parameters have been checked.
-    private PushGroup(HttpResponse.MultiSubscriber<U, T> multiSubscriber,
-                      CompletableFuture<HttpResponse<T>> mainResponse,
-                      HttpResponse.BodyHandler<T> mainBodyHandler,
-                      AccessControlContext acc) {
-
-        assert mainResponse != null; // A new instance is created above
-        assert mainBodyHandler != null; // should have been checked above
-
-        this.resultCF = new MinimalFuture<>();
-        this.noMorePushesCF = new MinimalFuture<>();
-        this.multiSubscriber = multiSubscriber;
-        this.mainResponse = mainResponse.thenApply(r -> {
-            multiSubscriber.onResponse(r);
-            return r;
-        });
-        this.mainBodyHandler = mainBodyHandler;
-        if (acc != null) {
-            // Restricts the file publisher with the senders ACC, if any
-            if (mainBodyHandler instanceof UntrustedBodyHandler)
-                ((UntrustedBodyHandler)this.mainBodyHandler).setAccessControlContext(acc);
-        }
-        this.acc = acc;
-    }
-
-    CompletableFuture<Void> groupResult() {
-        return resultCF;
-    }
-
-    HttpResponse.MultiSubscriber<U, T> subscriber() {
-        return multiSubscriber;
-    }
-
-    Optional<BodyHandler<T>> handlerForPushRequest(HttpRequest ppRequest) {
-        Optional<BodyHandler<T>> bh = multiSubscriber.onPushPromise(ppRequest);
-        if (acc != null && bh.isPresent()) {
-            // Restricts the file publisher with the senders ACC, if any
-            BodyHandler<T> x = bh.get();
-            if (x instanceof UntrustedBodyHandler)
-                ((UntrustedBodyHandler)x).setAccessControlContext(acc);
-            bh = Optional.of(x);
-        }
-        return bh;
-    }
-
-    HttpResponse.BodyHandler<T> mainResponseHandler() {
-        return mainBodyHandler;
-    }
-
-    synchronized void setMainResponse(CompletableFuture<HttpResponse<T>> r) {
-        r.whenComplete((HttpResponse<T> response, Throwable t) -> {
-            if (t != null)
-                mainResponse.completeExceptionally(t);
-            else
-                mainResponse.complete(response);
-        });
-    }
-
-    synchronized void addPush() {
-        numberOfPushes++;
-        remainingPushes++;
-    }
-
-    // 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);
-    }
-
-    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");
-            }
-            resultCF.complete(null);
-        }
-    }
-
-    synchronized void pushError(Throwable t) {
-        if (t == null) {
-            return;
-        }
-        this.error = t;
-        resultCF.completeExceptionally(t);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RawChannelImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.websocket.RawChannel;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SocketChannel;
-import java.util.function.Supplier;
-
-/*
- * Each RawChannel corresponds to a TCP connection (SocketChannel) but is
- * connected to a Selector and an ExecutorService for invoking the send and
- * receive callbacks. Also includes SSL processing.
- */
-final class RawChannelImpl implements RawChannel {
-
-    private final HttpClientImpl client;
-    private final HttpConnection.DetachedConnectionChannel detachedChannel;
-    private final Object         initialLock = new Object();
-    private Supplier<ByteBuffer> initial;
-
-    RawChannelImpl(HttpClientImpl client,
-                   HttpConnection connection,
-                   Supplier<ByteBuffer> initial)
-            throws IOException
-    {
-        this.client = client;
-        this.detachedChannel = connection.detachChannel();
-        this.initial = initial;
-
-        SocketChannel chan = connection.channel();
-        client.cancelRegistration(chan);
-        // Constructing a RawChannel is supposed to have a "hand over"
-        // semantics, in other words if construction fails, the channel won't be
-        // needed by anyone, in which case someone still needs to close it
-        try {
-            chan.configureBlocking(false);
-        } catch (IOException e) {
-            try {
-                chan.close();
-            } catch (IOException e1) {
-                e.addSuppressed(e1);
-            } finally {
-                detachedChannel.close();
-            }
-            throw e;
-        }
-    }
-
-    private class NonBlockingRawAsyncEvent extends AsyncEvent {
-
-        private final RawEvent re;
-
-        NonBlockingRawAsyncEvent(RawEvent re) {
-            // !BLOCKING & !REPEATING
-            this.re = re;
-        }
-
-        @Override
-        public SelectableChannel channel() {
-            return detachedChannel.channel();
-        }
-
-        @Override
-        public int interestOps() {
-            return re.interestOps();
-        }
-
-        @Override
-        public void handle() {
-            re.handle();
-        }
-
-        @Override
-        public void abort(IOException ioe) { }
-    }
-
-    @Override
-    public void registerEvent(RawEvent event) throws IOException {
-        client.registerEvent(new NonBlockingRawAsyncEvent(event));
-    }
-
-    @Override
-    public ByteBuffer read() throws IOException {
-        assert !detachedChannel.channel().isBlocking();
-        // connection.read() will no longer be available.
-        return detachedChannel.read();
-    }
-
-    @Override
-    public ByteBuffer initialByteBuffer() {
-        synchronized (initialLock) {
-            if (initial == null) {
-                throw new IllegalStateException();
-            }
-            ByteBuffer ref = initial.get();
-            ref = ref.hasRemaining() ? Utils.copy(ref)
-                    : Utils.EMPTY_BYTEBUFFER;
-            initial = null;
-            return ref;
-        }
-    }
-
-    @Override
-    public long write(ByteBuffer[] src, int offset, int len) throws IOException {
-        // this makes the whitebox driver test fail.
-        return detachedChannel.write(src, offset, len);
-    }
-
-    @Override
-    public void shutdownInput() throws IOException {
-        detachedChannel.shutdownInput();
-    }
-
-    @Override
-    public void shutdownOutput() throws IOException {
-        detachedChannel.shutdownOutput();
-    }
-
-    @Override
-    public void close() throws IOException {
-        detachedChannel.close();
-    }
-
-    @Override
-    public String toString() {
-        return super.toString()+"("+ detachedChannel.toString() + ")";
-    }
-
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RedirectFilter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.net.URI;
-import jdk.incubator.http.internal.common.Utils;
-
-class RedirectFilter implements HeaderFilter {
-
-    HttpRequestImpl request;
-    HttpClientImpl client;
-    HttpClient.Redirect policy;
-    String method;
-    MultiExchange<?,?> exchange;
-    static final int DEFAULT_MAX_REDIRECTS = 5;
-    URI uri;
-
-    static final int max_redirects = Utils.getIntegerNetProperty(
-            "jdk.httpclient.redirects.retrylimit", DEFAULT_MAX_REDIRECTS
-    );
-
-    // A public no-arg constructor is required by FilterFactory
-    public RedirectFilter() {}
-
-    @Override
-    public synchronized void request(HttpRequestImpl r, MultiExchange<?,?> e) throws IOException {
-        this.request = r;
-        this.client = e.client();
-        this.policy = client.followRedirects();
-
-        this.method = r.method();
-        this.uri = r.uri();
-        this.exchange = e;
-    }
-
-    @Override
-    public synchronized HttpRequestImpl response(Response r) throws IOException {
-        return handleResponse(r);
-    }
-
-    /**
-     * checks to see if new request needed and returns it.
-     * Null means response is ok to return to user.
-     */
-    private HttpRequestImpl handleResponse(Response r) {
-        int rcode = r.statusCode();
-        if (rcode == 200 || policy == HttpClient.Redirect.NEVER) {
-            return null;
-        }
-        if (rcode >= 300 && rcode <= 399) {
-            URI redir = getRedirectedURI(r.headers());
-            if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) {
-                //System.out.println("Redirecting to: " + redir);
-                return new HttpRequestImpl(redir, method, request);
-            } else {
-                //System.out.println("Redirect: giving up");
-                return null;
-            }
-        }
-        return null;
-    }
-
-    private URI getRedirectedURI(HttpHeaders headers) {
-        URI redirectedURI;
-        redirectedURI = headers.firstValue("Location")
-                .map(URI::create)
-                .orElseThrow(() -> new UncheckedIOException(
-                        new IOException("Invalid redirection")));
-
-        // redirect could be relative to original URL, but if not
-        // then redirect is used.
-        redirectedURI = uri.resolve(redirectedURI);
-        return redirectedURI;
-    }
-
-    private boolean canRedirect(URI redir) {
-        String newScheme = redir.getScheme();
-        String oldScheme = uri.getScheme();
-        switch (policy) {
-            case ALWAYS:
-                return true;
-            case NEVER:
-                return false;
-            case SECURE:
-                return newScheme.equalsIgnoreCase("https");
-            case SAME_PROTOCOL:
-                return newScheme.equalsIgnoreCase(oldScheme);
-            default:
-                throw new InternalError();
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/RequestPublishers.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,375 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Publisher;
-import java.util.function.Supplier;
-import jdk.incubator.http.HttpRequest.BodyPublisher;
-import jdk.incubator.http.internal.common.Utils;
-
-class RequestPublishers {
-
-    static class ByteArrayPublisher implements HttpRequest.BodyPublisher {
-        private volatile Flow.Publisher<ByteBuffer> delegate;
-        private final int length;
-        private final byte[] content;
-        private final int offset;
-        private final int bufSize;
-
-        ByteArrayPublisher(byte[] content) {
-            this(content, 0, content.length);
-        }
-
-        ByteArrayPublisher(byte[] content, int offset, int length) {
-            this(content, offset, length, Utils.BUFSIZE);
-        }
-
-        /* bufSize exposed for testing purposes */
-        ByteArrayPublisher(byte[] content, int offset, int length, int bufSize) {
-            this.content = content;
-            this.offset = offset;
-            this.length = length;
-            this.bufSize = bufSize;
-        }
-
-        List<ByteBuffer> copy(byte[] content, int offset, int length) {
-            List<ByteBuffer> bufs = new ArrayList<>();
-            while (length > 0) {
-                ByteBuffer b = ByteBuffer.allocate(Math.min(bufSize, length));
-                int max = b.capacity();
-                int tocopy = Math.min(max, length);
-                b.put(content, offset, tocopy);
-                offset += tocopy;
-                length -= tocopy;
-                b.flip();
-                bufs.add(b);
-            }
-            return bufs;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            List<ByteBuffer> copy = copy(content, offset, length);
-            this.delegate = new PullPublisher<>(copy);
-            delegate.subscribe(subscriber);
-        }
-
-        @Override
-        public long contentLength() {
-            return length;
-        }
-    }
-
-    // This implementation has lots of room for improvement.
-    static class IterablePublisher implements HttpRequest.BodyPublisher {
-        private volatile Flow.Publisher<ByteBuffer> delegate;
-        private final Iterable<byte[]> content;
-        private volatile long contentLength;
-
-        IterablePublisher(Iterable<byte[]> content) {
-            this.content = Objects.requireNonNull(content);
-        }
-
-        // The ByteBufferIterator will iterate over the byte[] arrays in
-        // the content one at the time.
-        //
-        class ByteBufferIterator implements Iterator<ByteBuffer> {
-            final ConcurrentLinkedQueue<ByteBuffer> buffers = new ConcurrentLinkedQueue<>();
-            final Iterator<byte[]> iterator = content.iterator();
-            @Override
-            public boolean hasNext() {
-                return !buffers.isEmpty() || iterator.hasNext();
-            }
-
-            @Override
-            public ByteBuffer next() {
-                ByteBuffer buffer = buffers.poll();
-                while (buffer == null) {
-                    copy();
-                    buffer = buffers.poll();
-                }
-                return buffer;
-            }
-
-            ByteBuffer getBuffer() {
-                return Utils.getBuffer();
-            }
-
-            void copy() {
-                byte[] bytes = iterator.next();
-                int length = bytes.length;
-                if (length == 0 && iterator.hasNext()) {
-                    // avoid inserting empty buffers, except
-                    // if that's the last.
-                    return;
-                }
-                int offset = 0;
-                do {
-                    ByteBuffer b = getBuffer();
-                    int max = b.capacity();
-
-                    int tocopy = Math.min(max, length);
-                    b.put(bytes, offset, tocopy);
-                    offset += tocopy;
-                    length -= tocopy;
-                    b.flip();
-                    buffers.add(b);
-                } while (length > 0);
-            }
-        }
-
-        public Iterator<ByteBuffer> iterator() {
-            return new ByteBufferIterator();
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            Iterable<ByteBuffer> iterable = this::iterator;
-            this.delegate = new PullPublisher<>(iterable);
-            delegate.subscribe(subscriber);
-        }
-
-        static long computeLength(Iterable<byte[]> bytes) {
-            long len = 0;
-            for (byte[] b : bytes) {
-                len = Math.addExact(len, (long)b.length);
-            }
-            return len;
-        }
-
-        @Override
-        public long contentLength() {
-            if (contentLength == 0) {
-                synchronized(this) {
-                    if (contentLength == 0) {
-                        contentLength = computeLength(content);
-                    }
-                }
-            }
-            return contentLength;
-        }
-    }
-
-    static class StringPublisher extends ByteArrayPublisher {
-        public StringPublisher(String content, Charset charset) {
-            super(content.getBytes(charset));
-        }
-    }
-
-    static class EmptyPublisher implements HttpRequest.BodyPublisher {
-        private final Flow.Publisher<ByteBuffer> delegate =
-                new PullPublisher<ByteBuffer>(Collections.emptyList(), null);
-
-        @Override
-        public long contentLength() {
-            return 0;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            delegate.subscribe(subscriber);
-        }
-    }
-
-    static class FilePublisher implements BodyPublisher  {
-        private final File file;
-        private volatile AccessControlContext acc;
-
-        FilePublisher(Path name) {
-            file = name.toFile();
-        }
-
-        void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            if (System.getSecurityManager() != null && acc == null)
-                throw new InternalError(
-                        "Unexpected null acc when security manager has been installed");
-
-            InputStream is;
-            try {
-                PrivilegedExceptionAction<FileInputStream> pa =
-                        () -> new FileInputStream(file);
-                is = AccessController.doPrivileged(pa, acc);
-            } catch (PrivilegedActionException pae) {
-                throw new UncheckedIOException((IOException)pae.getCause());
-            }
-            PullPublisher<ByteBuffer> publisher =
-                    new PullPublisher<>(() -> new StreamIterator(is));
-            publisher.subscribe(subscriber);
-        }
-
-        @Override
-        public long contentLength() {
-            assert System.getSecurityManager() != null ? acc != null: true;
-            PrivilegedAction<Long> pa = () -> file.length();
-            return AccessController.doPrivileged(pa, acc);
-        }
-    }
-
-    /**
-     * Reads one buffer ahead all the time, blocking in hasNext()
-     */
-    static class StreamIterator implements Iterator<ByteBuffer> {
-        final InputStream is;
-        final Supplier<? extends ByteBuffer> bufSupplier;
-        volatile ByteBuffer nextBuffer;
-        volatile boolean need2Read = true;
-        volatile boolean haveNext;
-
-        StreamIterator(InputStream is) {
-            this(is, Utils::getBuffer);
-        }
-
-        StreamIterator(InputStream is, Supplier<? extends ByteBuffer> bufSupplier) {
-            this.is = is;
-            this.bufSupplier = bufSupplier;
-        }
-
-//        Throwable error() {
-//            return error;
-//        }
-
-        private int read() {
-            nextBuffer = bufSupplier.get();
-            nextBuffer.clear();
-            byte[] buf = nextBuffer.array();
-            int offset = nextBuffer.arrayOffset();
-            int cap = nextBuffer.capacity();
-            try {
-                int n = is.read(buf, offset, cap);
-                if (n == -1) {
-                    is.close();
-                    return -1;
-                }
-                //flip
-                nextBuffer.limit(n);
-                nextBuffer.position(0);
-                return n;
-            } catch (IOException ex) {
-                return -1;
-            }
-        }
-
-        @Override
-        public synchronized boolean hasNext() {
-            if (need2Read) {
-                haveNext = read() != -1;
-                if (haveNext) {
-                    need2Read = false;
-                }
-                return haveNext;
-            }
-            return haveNext;
-        }
-
-        @Override
-        public synchronized ByteBuffer next() {
-            if (!hasNext()) {
-                throw new NoSuchElementException();
-            }
-            need2Read = true;
-            return nextBuffer;
-        }
-
-    }
-
-    static class InputStreamPublisher implements BodyPublisher {
-        private final Supplier<? extends InputStream> streamSupplier;
-
-        InputStreamPublisher(Supplier<? extends InputStream> streamSupplier) {
-            this.streamSupplier = Objects.requireNonNull(streamSupplier);
-        }
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            PullPublisher<ByteBuffer> publisher;
-            InputStream is = streamSupplier.get();
-            if (is == null) {
-                Throwable t = new IOException("streamSupplier returned null");
-                publisher = new PullPublisher<>(null, t);
-            } else  {
-                publisher = new PullPublisher<>(iterableOf(is), null);
-            }
-            publisher.subscribe(subscriber);
-        }
-
-        protected Iterable<ByteBuffer> iterableOf(InputStream is) {
-            return () -> new StreamIterator(is);
-        }
-
-        @Override
-        public long contentLength() {
-            return -1;
-        }
-    }
-
-    static final class PublisherAdapter implements BodyPublisher {
-
-        private final Publisher<? extends ByteBuffer> publisher;
-        private final long contentLength;
-
-        PublisherAdapter(Publisher<? extends ByteBuffer> publisher,
-                         long contentLength) {
-            this.publisher = Objects.requireNonNull(publisher);
-            this.contentLength = contentLength;
-        }
-
-        @Override
-        public final long contentLength() {
-            return contentLength;
-        }
-
-        @Override
-        public final void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            publisher.subscribe(subscriber);
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Response.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.URI;
-
-/**
- * Response headers and status code.
- */
-class Response {
-    final HttpHeaders headers;
-    final int statusCode;
-    final HttpRequestImpl request;
-    final Exchange<?> exchange;
-    final HttpClient.Version version;
-
-    Response(HttpRequestImpl req,
-             Exchange<?> exchange,
-             HttpHeaders headers,
-             int statusCode,
-             HttpClient.Version version) {
-        this.headers = headers;
-        this.request = req;
-        this.version = version;
-        this.exchange = exchange;
-        this.statusCode = statusCode;
-    }
-
-    HttpRequestImpl request() {
-        return request;
-    }
-
-    HttpClient.Version version() {
-        return version;
-    }
-
-    HttpHeaders headers() {
-        return headers;
-    }
-
-//    Exchange<?> exchange() {
-//        return exchange;
-//    }
-
-    int statusCode() {
-        return statusCode;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        String method = request().method();
-        URI uri = request().uri();
-        String uristring = uri == null ? "" : uri.toString();
-        sb.append('(')
-          .append(method)
-          .append(" ")
-          .append(uristring)
-          .append(") ")
-          .append(statusCode());
-        return sb.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseContent.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,464 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * Implements chunked/fixed transfer encodings of HTTP/1.1 responses.
- *
- * Call pushBody() to read the body (blocking). Data and errors are provided
- * to given Consumers. After final buffer delivered, empty optional delivered
- */
-class ResponseContent {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag.
-
-    final HttpResponse.BodySubscriber<?> pusher;
-    final int contentLength;
-    final HttpHeaders headers;
-    // this needs to run before we complete the body
-    // so that connection can be returned to pool
-    private final Runnable onFinished;
-    private final String dbgTag;
-
-    ResponseContent(HttpConnection connection,
-                    int contentLength,
-                    HttpHeaders h,
-                    HttpResponse.BodySubscriber<?> userSubscriber,
-                    Runnable onFinished)
-    {
-        this.pusher = userSubscriber;
-        this.contentLength = contentLength;
-        this.headers = h;
-        this.onFinished = onFinished;
-        this.dbgTag = connection.dbgString() + "/ResponseContent";
-    }
-
-    static final int LF = 10;
-    static final int CR = 13;
-
-    private boolean chunkedContent, chunkedContentInitialized;
-
-    boolean contentChunked() throws IOException {
-        if (chunkedContentInitialized) {
-            return chunkedContent;
-        }
-        if (contentLength == -1) {
-            String tc = headers.firstValue("Transfer-Encoding")
-                               .orElse("");
-            if (!tc.equals("")) {
-                if (tc.equalsIgnoreCase("chunked")) {
-                    chunkedContent = true;
-                } else {
-                    throw new IOException("invalid content");
-                }
-            } else {
-                chunkedContent = false;
-            }
-        }
-        chunkedContentInitialized = true;
-        return chunkedContent;
-    }
-
-    interface BodyParser extends Consumer<ByteBuffer> {
-        void onSubscribe(AbstractSubscription sub);
-    }
-
-    // Returns a parser that will take care of parsing the received byte
-    // buffers and forward them to the BodySubscriber.
-    // When the parser is done, it will call onComplete.
-    // If parsing was successful, the throwable parameter will be null.
-    // Otherwise it will be the exception that occurred
-    // Note: revisit: it might be better to use a CompletableFuture than
-    //       a completion handler.
-    BodyParser getBodyParser(Consumer<Throwable> onComplete)
-        throws IOException {
-        if (contentChunked()) {
-            return new ChunkedBodyParser(onComplete);
-        } else {
-            return new FixedLengthBodyParser(contentLength, onComplete);
-        }
-    }
-
-
-    static enum ChunkState {READING_LENGTH, READING_DATA, DONE}
-    class ChunkedBodyParser implements BodyParser {
-        final ByteBuffer READMORE = Utils.EMPTY_BYTEBUFFER;
-        final Consumer<Throwable> onComplete;
-        final System.Logger debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-        final String dbgTag = ResponseContent.this.dbgTag + "/ChunkedBodyParser";
-
-        volatile Throwable closedExceptionally;
-        volatile int partialChunklen = 0; // partially read chunk len
-        volatile int chunklen = -1;  // number of bytes in chunk
-        volatile int bytesremaining;  // number of bytes in chunk left to be read incl CRLF
-        volatile boolean cr = false;  // tryReadChunkLength has found CR
-        volatile int bytesToConsume;  // number of bytes that still need to be consumed before proceeding
-        volatile ChunkState state = ChunkState.READING_LENGTH; // current state
-        volatile AbstractSubscription sub;
-        ChunkedBodyParser(Consumer<Throwable> onComplete) {
-            this.onComplete = onComplete;
-        }
-
-        String dbgString() {
-            return dbgTag;
-        }
-
-        @Override
-        public void onSubscribe(AbstractSubscription sub) {
-            debug.log(Level.DEBUG, () ->  "onSubscribe: "
-                        + pusher.getClass().getName());
-            pusher.onSubscribe(this.sub = sub);
-        }
-
-        @Override
-        public void accept(ByteBuffer b) {
-            if (closedExceptionally != null) {
-                debug.log(Level.DEBUG, () ->  "already closed: "
-                            + closedExceptionally);
-                return;
-            }
-            boolean completed = false;
-            try {
-                List<ByteBuffer> out = new ArrayList<>();
-                do {
-                    if (tryPushOneHunk(b, out))  {
-                        // We're done! (true if the final chunk was parsed).
-                        if (!out.isEmpty()) {
-                            // push what we have and complete
-                            // only reduce demand if we actually push something.
-                            // we would not have come here if there was no
-                            // demand.
-                            boolean hasDemand = sub.demand().tryDecrement();
-                            assert hasDemand;
-                            pusher.onNext(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(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);
-                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));
-                }
-                if (unfulfilled == 0) {
-                    // We're done! All data has been received.
-                    assert closedExceptionally == null;
-                    onFinished.run();
-                    pusher.onComplete();
-                    completed = true;
-                    onComplete.accept(closedExceptionally); // should be null
-                } else {
-                    assert b.remaining() == 0;
-                }
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG, "Unexpected exception", t);
-                closedExceptionally = t;
-                if (!completed) {
-                    onComplete.accept(t);
-                }
-            }
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseSubscribers.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,628 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-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.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 jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Utils;
-
-class ResponseSubscribers {
-
-    static class ConsumerSubscriber implements HttpResponse.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();
-
-        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);
-        }
-
-    }
-
-    static class PathSubscriber implements HttpResponse.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;
-
-        PathSubscriber(Path file, OpenOption... options) {
-            this.file = file;
-            this.options = options;
-        }
-
-        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;
-        }
-    }
-
-    static class ByteArraySubscriber<T> implements HttpResponse.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;
-
-        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);
-            Utils.accumulateBuffers(received, 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.
-     */
-    static class HttpResponseInputStream extends InputStream
-        implements HttpResponse.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();
-
-        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 senAsync().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();
-            }
-        }
-
-    }
-
-    static class MultiSubscriberImpl<V>
-        implements HttpResponse.MultiSubscriber<MultiMapResult<V>,V>
-    {
-        private final MultiMapResult<V> results;
-        private final Function<HttpRequest,Optional<HttpResponse.BodyHandler<V>>> pushHandler;
-        private final Function<HttpRequest,HttpResponse.BodyHandler<V>> requestHandler;
-        private final boolean completion; // aggregate completes on last PP received or overall completion
-
-        MultiSubscriberImpl(
-                Function<HttpRequest,HttpResponse.BodyHandler<V>> requestHandler,
-                Function<HttpRequest,Optional<HttpResponse.BodyHandler<V>>> pushHandler, boolean completion) {
-            this.results = new MultiMapResult<>(new ConcurrentHashMap<>());
-            this.requestHandler = requestHandler;
-            this.pushHandler = pushHandler;
-            this.completion = completion;
-        }
-
-        @Override
-        public HttpResponse.BodyHandler<V> onRequest(HttpRequest request) {
-            CompletableFuture<HttpResponse<V>> cf = MinimalFuture.newMinimalFuture();
-            results.put(request, cf);
-            return requestHandler.apply(request);
-        }
-
-        @Override
-        public Optional<HttpResponse.BodyHandler<V>> onPushPromise(HttpRequest push) {
-            CompletableFuture<HttpResponse<V>> cf = MinimalFuture.newMinimalFuture();
-            results.put(push, cf);
-            return pushHandler.apply(push);
-        }
-
-        @Override
-        public void onResponse(HttpResponse<V> response) {
-            CompletableFuture<HttpResponse<V>> cf = results.get(response.request());
-            cf.complete(response);
-        }
-
-        @Override
-        public void onError(HttpRequest request, Throwable t) {
-            CompletableFuture<HttpResponse<V>> cf = results.get(request);
-            cf.completeExceptionally(t);
-        }
-
-        @Override
-        public CompletableFuture<MultiMapResult<V>> completion(
-                CompletableFuture<Void> onComplete, CompletableFuture<Void> onFinalPushPromise) {
-            if (completion)
-                return onComplete.thenApply((ignored)-> results);
-            else
-                return onFinalPushPromise.thenApply((ignored) -> results);
-        }
-    }
-
-    /**
-     * Currently this consumes all of the data and ignores it
-     */
-    static class NullSubscriber<T> implements HttpResponse.BodySubscriber<T> {
-
-        private final CompletableFuture<T> cf = new MinimalFuture<>();
-        private final Optional<T> result;
-        private final AtomicBoolean subscribed = new AtomicBoolean();
-
-        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}. */
-    static final class SubscriberAdapter<S extends Subscriber<? super List<ByteBuffer>>,R>
-        implements HttpResponse.BodySubscriber<R>
-    {
-        private final CompletableFuture<R> cf = new MinimalFuture<>();
-        private final S subscriber;
-        private final Function<S,R> finisher;
-        private volatile Subscription subscription;
-
-        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/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,489 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
-import javax.net.ssl.SSLEngineResult.Status;
-import javax.net.ssl.*;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-import static javax.net.ssl.SSLEngineResult.HandshakeStatus.*;
-
-/**
- * Implements the mechanics of SSL by managing an SSLEngine object.
- * <p>
- * This class is only used to implement the {@link
- * AbstractAsyncSSLConnection.SSLConnectionChannel} which is handed of
- * to RawChannelImpl when creating a WebSocket.
- */
-class SSLDelegate {
-
-    final SSLEngine engine;
-    final EngineWrapper wrapper;
-    final Lock handshaking = new ReentrantLock();
-    final SocketChannel chan;
-
-    SSLDelegate(SSLEngine eng, SocketChannel chan)
-    {
-        this.engine = eng;
-        this.chan = chan;
-        this.wrapper = new EngineWrapper(chan, engine);
-    }
-
-    // alpn[] may be null
-//    SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn, String sn)
-//        throws IOException
-//    {
-//        serverName = sn;
-//        SSLContext context = client.sslContext();
-//        engine = context.createSSLEngine();
-//        engine.setUseClientMode(true);
-//        SSLParameters sslp = client.sslParameters();
-//        sslParameters = Utils.copySSLParameters(sslp);
-//        if (sn != null) {
-//            SNIHostName sni = new SNIHostName(sn);
-//            sslParameters.setServerNames(List.of(sni));
-//        }
-//        if (alpn != null) {
-//            sslParameters.setApplicationProtocols(alpn);
-//            Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn));
-//        } else {
-//            Log.logSSL("SSLDelegate: No application protocols proposed");
-//        }
-//        engine.setSSLParameters(sslParameters);
-//        wrapper = new EngineWrapper(chan, engine);
-//        this.chan = chan;
-//        this.client = client;
-//    }
-
-//    SSLParameters getSSLParameters() {
-//        return sslParameters;
-//    }
-
-    static long countBytes(ByteBuffer[] buffers, int start, int number) {
-        long c = 0;
-        for (int i=0; i<number; i++) {
-            c+= buffers[start+i].remaining();
-        }
-        return c;
-    }
-
-
-    static class WrapperResult {
-        static WrapperResult createOK() {
-            WrapperResult r = new WrapperResult();
-            r.buf = null;
-            r.result = new SSLEngineResult(Status.OK, NOT_HANDSHAKING, 0, 0);
-            return r;
-        }
-        SSLEngineResult result;
-
-        ByteBuffer buf; // buffer containing result data
-    }
-
-    int app_buf_size;
-    int packet_buf_size;
-
-    enum BufType {
-        PACKET,
-        APPLICATION
-    }
-
-    ByteBuffer allocate (BufType type) {
-        return allocate (type, -1);
-    }
-
-    // TODO: Use buffer pool for this
-    ByteBuffer allocate (BufType type, int len) {
-        assert engine != null;
-        synchronized (this) {
-            int size;
-            if (type == BufType.PACKET) {
-                if (packet_buf_size == 0) {
-                    SSLSession sess = engine.getSession();
-                    packet_buf_size = sess.getPacketBufferSize();
-                }
-                if (len > packet_buf_size) {
-                    packet_buf_size = len;
-                }
-                size = packet_buf_size;
-            } else {
-                if (app_buf_size == 0) {
-                    SSLSession sess = engine.getSession();
-                    app_buf_size = sess.getApplicationBufferSize();
-                }
-                if (len > app_buf_size) {
-                    app_buf_size = len;
-                }
-                size = app_buf_size;
-            }
-            return ByteBuffer.allocate (size);
-        }
-    }
-
-    /* reallocates the buffer by :-
-     * 1. creating a new buffer double the size of the old one
-     * 2. putting the contents of the old buffer into the new one
-     * 3. set xx_buf_size to the new size if it was smaller than new size
-     *
-     * flip is set to true if the old buffer needs to be flipped
-     * before it is copied.
-     */
-    private ByteBuffer realloc (ByteBuffer b, boolean flip, BufType type) {
-        // TODO: there should be the linear growth, rather than exponential as
-        // we definitely know the maximum amount of space required to unwrap
-        synchronized (this) {
-            int nsize = 2 * b.capacity();
-            ByteBuffer n = allocate (type, nsize);
-            if (flip) {
-                b.flip();
-            }
-            n.put(b);
-            b = n;
-        }
-        return b;
-    }
-
-    /**
-     * This is a thin wrapper over SSLEngine and the SocketChannel, which
-     * guarantees the ordering of wraps/unwraps with respect to the underlying
-     * channel read/writes. It handles the UNDER/OVERFLOW status codes
-     * It does not handle the handshaking status codes, or the CLOSED status code
-     * though once the engine is closed, any attempt to read/write to it
-     * will get an exception.  The overall result is returned.
-     * It functions synchronously/blocking
-     */
-    class EngineWrapper {
-
-        SocketChannel chan;
-        SSLEngine engine;
-        final Object wrapLock;
-        final Object unwrapLock;
-        ByteBuffer unwrap_src, wrap_dst;
-        boolean closed = false;
-        int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
-
-        EngineWrapper (SocketChannel chan, SSLEngine engine) {
-            this.chan = chan;
-            this.engine = engine;
-            wrapLock = new Object();
-            unwrapLock = new Object();
-            unwrap_src = allocate(BufType.PACKET);
-            wrap_dst = allocate(BufType.PACKET);
-        }
-
-//        void close () throws IOException {
-//        }
-
-        WrapperResult wrapAndSend(ByteBuffer src, boolean ignoreClose)
-            throws IOException
-        {
-            ByteBuffer[] buffers = new ByteBuffer[1];
-            buffers[0] = src;
-            return wrapAndSend(buffers, 0, 1, ignoreClose);
-        }
-
-        /* try to wrap and send the data in src. Handles OVERFLOW.
-         * Might block if there is an outbound blockage or if another
-         * thread is calling wrap(). Also, might not send any data
-         * if an unwrap is needed.
-         */
-        WrapperResult wrapAndSend(ByteBuffer[] src,
-                                  int offset,
-                                  int len,
-                                  boolean ignoreClose)
-            throws IOException
-        {
-            if (closed && !ignoreClose) {
-                throw new IOException ("Engine is closed");
-            }
-            Status status;
-            WrapperResult r = new WrapperResult();
-            synchronized (wrapLock) {
-                wrap_dst.clear();
-                do {
-                    r.result = engine.wrap (src, offset, len, wrap_dst);
-                    status = r.result.getStatus();
-                    if (status == Status.BUFFER_OVERFLOW) {
-                        wrap_dst = realloc (wrap_dst, true, BufType.PACKET);
-                    }
-                } while (status == Status.BUFFER_OVERFLOW);
-                if (status == Status.CLOSED && !ignoreClose) {
-                    closed = true;
-                    return r;
-                }
-                if (r.result.bytesProduced() > 0) {
-                    wrap_dst.flip();
-                    int l = wrap_dst.remaining();
-                    assert l == r.result.bytesProduced();
-                    while (l>0) {
-                        l -= chan.write (wrap_dst);
-                    }
-                }
-            }
-            return r;
-        }
-
-        /* block until a complete message is available and return it
-         * in dst, together with the Result. dst may have been re-allocated
-         * so caller should check the returned value in Result
-         * If handshaking is in progress then, possibly no data is returned
-         */
-        WrapperResult recvAndUnwrap(ByteBuffer dst) throws IOException {
-            Status status;
-            WrapperResult r = new WrapperResult();
-            r.buf = dst;
-            if (closed) {
-                throw new IOException ("Engine is closed");
-            }
-            boolean needData;
-            if (u_remaining > 0) {
-                unwrap_src.compact();
-                unwrap_src.flip();
-                needData = false;
-            } else {
-                unwrap_src.clear();
-                needData = true;
-            }
-            synchronized (unwrapLock) {
-                int x;
-                do {
-                    if (needData) {
-                        x = chan.read (unwrap_src);
-                        if (x == -1) {
-                            throw new IOException ("connection closed for reading");
-                        }
-                        unwrap_src.flip();
-                    }
-                    r.result = engine.unwrap (unwrap_src, r.buf);
-                    status = r.result.getStatus();
-                    if (status == Status.BUFFER_UNDERFLOW) {
-                        if (unwrap_src.limit() == unwrap_src.capacity()) {
-                            /* buffer not big enough */
-                            unwrap_src = realloc (
-                                unwrap_src, false, BufType.PACKET
-                            );
-                        } else {
-                            /* Buffer not full, just need to read more
-                             * data off the channel. Reset pointers
-                             * for reading off SocketChannel
-                             */
-                            unwrap_src.position (unwrap_src.limit());
-                            unwrap_src.limit (unwrap_src.capacity());
-                        }
-                        needData = true;
-                    } else if (status == Status.BUFFER_OVERFLOW) {
-                        r.buf = realloc (r.buf, true, BufType.APPLICATION);
-                        needData = false;
-                    } else if (status == Status.CLOSED) {
-                        closed = true;
-                        r.buf.flip();
-                        return r;
-                    }
-                } while (status != Status.OK);
-            }
-            u_remaining = unwrap_src.remaining();
-            return r;
-        }
-    }
-
-//    WrapperResult sendData (ByteBuffer src) throws IOException {
-//        ByteBuffer[] buffers = new ByteBuffer[1];
-//        buffers[0] = src;
-//        return sendData(buffers, 0, 1);
-//    }
-
-    /**
-     * send the data in the given ByteBuffer. If a handshake is needed
-     * then this is handled within this method. When this call returns,
-     * all of the given user data has been sent and any handshake has been
-     * completed. Caller should check if engine has been closed.
-     */
-    WrapperResult sendData (ByteBuffer[] src, int offset, int len) throws IOException {
-        WrapperResult r = WrapperResult.createOK();
-        while (countBytes(src, offset, len) > 0) {
-            r = wrapper.wrapAndSend(src, offset, len, false);
-            Status status = r.result.getStatus();
-            if (status == Status.CLOSED) {
-                doClosure ();
-                return r;
-            }
-            HandshakeStatus hs_status = r.result.getHandshakeStatus();
-            if (hs_status != HandshakeStatus.FINISHED &&
-                hs_status != HandshakeStatus.NOT_HANDSHAKING)
-            {
-                doHandshake(hs_status);
-            }
-        }
-        return r;
-    }
-
-    /**
-     * read data thru the engine into the given ByteBuffer. If the
-     * given buffer was not large enough, a new one is allocated
-     * and returned. This call handles handshaking automatically.
-     * Caller should check if engine has been closed.
-     */
-    WrapperResult recvData (ByteBuffer dst) throws IOException {
-        /* we wait until some user data arrives */
-        int mark = dst.position();
-        WrapperResult r = null;
-        int pos = dst.position();
-        while (dst.position() == pos) {
-            r = wrapper.recvAndUnwrap (dst);
-            dst = (r.buf != dst) ? r.buf: dst;
-            Status status = r.result.getStatus();
-            if (status == Status.CLOSED) {
-                doClosure ();
-                return r;
-            }
-
-            HandshakeStatus hs_status = r.result.getHandshakeStatus();
-            if (hs_status != HandshakeStatus.FINISHED &&
-                hs_status != HandshakeStatus.NOT_HANDSHAKING)
-            {
-                doHandshake (hs_status);
-            }
-        }
-        Utils.flipToMark(dst, mark);
-        return r;
-    }
-
-    /* we've received a close notify. Need to call wrap to send
-     * the response
-     */
-    void doClosure () throws IOException {
-        try {
-            handshaking.lock();
-            ByteBuffer tmp = allocate(BufType.APPLICATION);
-            WrapperResult r;
-            do {
-                tmp.clear();
-                tmp.flip ();
-                r = wrapper.wrapAndSend(tmp, true);
-            } while (r.result.getStatus() != Status.CLOSED);
-        } finally {
-            handshaking.unlock();
-        }
-    }
-
-    /* do the (complete) handshake after acquiring the handshake lock.
-     * If two threads call this at the same time, then we depend
-     * on the wrapper methods being idempotent. eg. if wrapAndSend()
-     * is called with no data to send then there must be no problem
-     */
-    @SuppressWarnings("fallthrough")
-    void doHandshake (HandshakeStatus hs_status) throws IOException {
-        boolean wasBlocking;
-        try {
-            wasBlocking = chan.isBlocking();
-            handshaking.lock();
-            chan.configureBlocking(true);
-            ByteBuffer tmp = allocate(BufType.APPLICATION);
-            while (hs_status != HandshakeStatus.FINISHED &&
-                   hs_status != HandshakeStatus.NOT_HANDSHAKING)
-            {
-                WrapperResult r = null;
-                switch (hs_status) {
-                    case NEED_TASK:
-                        Runnable task;
-                        while ((task = engine.getDelegatedTask()) != null) {
-                            /* run in current thread, because we are already
-                             * running an external Executor
-                             */
-                            task.run();
-                        }
-                        /* fall thru - call wrap again */
-                    case NEED_WRAP:
-                        tmp.clear();
-                        tmp.flip();
-                        r = wrapper.wrapAndSend(tmp, false);
-                        break;
-
-                    case NEED_UNWRAP:
-                        tmp.clear();
-                        r = wrapper.recvAndUnwrap (tmp);
-                        if (r.buf != tmp) {
-                            tmp = r.buf;
-                        }
-                        assert tmp.position() == 0;
-                        break;
-                }
-                hs_status = r.result.getHandshakeStatus();
-            }
-            Log.logSSL(getSessionInfo());
-            if (!wasBlocking) {
-                chan.configureBlocking(false);
-            }
-        } finally {
-            handshaking.unlock();
-        }
-    }
-
-//    static void printParams(SSLParameters p) {
-//        System.out.println("SSLParameters:");
-//        if (p == null) {
-//            System.out.println("Null params");
-//            return;
-//        }
-//        for (String cipher : p.getCipherSuites()) {
-//                System.out.printf("cipher: %s\n", cipher);
-//        }
-//        // JDK 8 EXCL START
-//        for (String approto : p.getApplicationProtocols()) {
-//                System.out.printf("application protocol: %s\n", approto);
-//        }
-//        // JDK 8 EXCL END
-//        for (String protocol : p.getProtocols()) {
-//                System.out.printf("protocol: %s\n", protocol);
-//        }
-//        if (p.getServerNames() != null) {
-//            for (SNIServerName sname : p.getServerNames()) {
-//                System.out.printf("server name: %s\n", sname.toString());
-//            }
-//        }
-//    }
-
-    String getSessionInfo() {
-        StringBuilder sb = new StringBuilder();
-        String application = engine.getApplicationProtocol();
-        SSLSession sess = engine.getSession();
-        String cipher = sess.getCipherSuite();
-        String protocol = sess.getProtocol();
-        sb.append("Handshake complete alpn: ")
-                .append(application)
-                .append(", Cipher: ")
-                .append(cipher)
-                .append(", Protocol: ")
-                .append(protocol);
-        return sb.toString();
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SocketTube.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,956 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
-import jdk.incubator.http.internal.common.SequentialScheduler.RestartableTask;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * A SocketTube is a terminal tube plugged directly into the socket.
- * The read subscriber should call {@code subscribe} on the SocketTube before
- * the SocketTube can be subscribed to the write publisher.
- */
-final class SocketTube implements FlowTube {
-
-    static final boolean DEBUG = Utils.DEBUG; // revisit: temporary developer's flag
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-    static final AtomicLong IDS = new AtomicLong();
-
-    private final HttpClientImpl client;
-    private final SocketChannel channel;
-    private final Supplier<ByteBuffer> buffersSource;
-    private final Object lock = new Object();
-    private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
-    private final InternalReadPublisher readPublisher;
-    private final InternalWriteSubscriber writeSubscriber;
-    private final long id = IDS.incrementAndGet();
-
-    public SocketTube(HttpClientImpl client, SocketChannel channel,
-                      Supplier<ByteBuffer> buffersSource) {
-        this.client = client;
-        this.channel = channel;
-        this.buffersSource = buffersSource;
-        this.readPublisher = new InternalReadPublisher();
-        this.writeSubscriber = new InternalWriteSubscriber();
-    }
-
-//    private static Flow.Subscription nopSubscription() {
-//        return new Flow.Subscription() {
-//            @Override public void request(long n) { }
-//            @Override public void cancel() { }
-//        };
-//    }
-
-    /**
-     * Returns {@code true} if this flow is finished.
-     * This happens when this flow internal read subscription is completed,
-     * either normally (EOF reading) or exceptionally  (EOF writing, or
-     * underlying socket closed, or some exception occurred while reading or
-     * writing to the socket).
-     *
-     * @return {@code true} if this flow is finished.
-     */
-    public boolean isFinished() {
-        InternalReadPublisher.InternalReadSubscription subscription =
-                readPublisher.subscriptionImpl;
-        return subscription != null && subscription.completed
-                || subscription == null && errorRef.get() != null;
-    }
-
-    // ===================================================================== //
-    //                       Flow.Publisher                                  //
-    // ======================================================================//
-
-    /**
-     * {@inheritDoc }
-     * @apiNote This method should be called first. In particular, the caller
-     *          must ensure that this method must be called by the read
-     *          subscriber before the write publisher can call {@code onSubscribe}.
-     *          Failure to adhere to this contract may result in assertion errors.
-     */
-    @Override
-    public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
-        Objects.requireNonNull(s);
-        assert s instanceof TubeSubscriber : "Expected TubeSubscriber, got:" + s;
-        readPublisher.subscribe(s);
-    }
-
-
-    // ===================================================================== //
-    //                       Flow.Subscriber                                 //
-    // ======================================================================//
-
-    /**
-     * {@inheritDoc }
-     * @apiNote The caller must ensure that {@code subscribe} is called by
-     *          the read subscriber before {@code onSubscribe} is called by
-     *          the write publisher.
-     *          Failure to adhere to this contract may result in assertion errors.
-     */
-    @Override
-    public void onSubscribe(Flow.Subscription subscription) {
-        writeSubscriber.onSubscribe(subscription);
-    }
-
-    @Override
-    public void onNext(List<ByteBuffer> item) {
-        writeSubscriber.onNext(item);
-    }
-
-    @Override
-    public void onError(Throwable throwable) {
-        writeSubscriber.onError(throwable);
-    }
-
-    @Override
-    public void onComplete() {
-        writeSubscriber.onComplete();
-    }
-
-    // ===================================================================== //
-    //                           Events                                      //
-    // ======================================================================//
-
-    /**
-     * A restartable task used to process tasks in sequence.
-     */
-    private static class SocketFlowTask implements RestartableTask {
-        final Runnable task;
-        private final Object monitor = new Object();
-        SocketFlowTask(Runnable task) {
-            this.task = task;
-        }
-        @Override
-        public final void run(DeferredCompleter taskCompleter) {
-            try {
-                // non contentious synchronized for visibility.
-                synchronized(monitor) {
-                    task.run();
-                }
-            } finally {
-                taskCompleter.complete();
-            }
-        }
-    }
-
-    // This is best effort - there's no guarantee that the printed set
-    // of values is consistent. It should only be considered as
-    // weakly accurate - in particular in what concerns the events states,
-    // especially when displaying a read event state from a write event
-    // callback and conversely.
-    void debugState(String when) {
-        if (debug.isLoggable(Level.DEBUG)) {
-            StringBuilder state = new StringBuilder();
-
-            InternalReadPublisher.InternalReadSubscription sub =
-                    readPublisher.subscriptionImpl;
-            InternalReadPublisher.ReadEvent readEvent =
-                    sub == null ? null : sub.readEvent;
-            Demand rdemand = sub == null ? null : sub.demand;
-            InternalWriteSubscriber.WriteEvent writeEvent =
-                    writeSubscriber.writeEvent;
-            AtomicLong wdemand = writeSubscriber.writeDemand;
-            int rops = readEvent == null ? 0 : readEvent.interestOps();
-            long rd = rdemand == null ? 0 : rdemand.get();
-            int wops = writeEvent == null ? 0 : writeEvent.interestOps();
-            long wd = wdemand == null ? 0 : wdemand.get();
-
-            state.append(when).append(" Reading: [ops=")
-                    .append(rops).append(", demand=").append(rd)
-                    .append(", stopped=")
-                    .append((sub == null ? false : sub.readScheduler.isStopped()))
-                    .append("], Writing: [ops=").append(wops)
-                    .append(", demand=").append(wd)
-                    .append("]");
-            debug.log(Level.DEBUG, state.toString());
-        }
-    }
-
-    /**
-     * A repeatable event that can be paused or resumed by changing
-     * its interestOps.
-     * When the event is fired, it is first paused before being signaled.
-     * It is the responsibility of the code triggered by {@code signalEvent}
-     * to resume the event if required.
-     */
-    private static abstract class SocketFlowEvent extends AsyncEvent {
-        final SocketChannel channel;
-        final int defaultInterest;
-        volatile int interestOps;
-        volatile boolean registered;
-        SocketFlowEvent(int defaultInterest, SocketChannel channel) {
-            super(AsyncEvent.REPEATING);
-            this.defaultInterest = defaultInterest;
-            this.channel = channel;
-        }
-        final boolean registered() {return registered;}
-        final void resume() {
-            interestOps = defaultInterest;
-            registered = true;
-        }
-        final void pause() {interestOps = 0;}
-        @Override
-        public final SelectableChannel channel() {return channel;}
-        @Override
-        public final int interestOps() {return interestOps;}
-
-        @Override
-        public final void handle() {
-            pause();       // pause, then signal
-            signalEvent(); // won't be fired again until resumed.
-        }
-        @Override
-        public final void abort(IOException error) {
-            debug().log(Level.DEBUG, () -> "abort: " + error);
-            pause();              // pause, then signal
-            signalError(error);   // should not be resumed after abort (not checked)
-        }
-
-        protected abstract void signalEvent();
-        protected abstract void signalError(Throwable error);
-        abstract System.Logger debug();
-    }
-
-    // ===================================================================== //
-    //                              Writing                                  //
-    // ======================================================================//
-
-    // This class makes the assumption that the publisher will call
-    // onNext sequentially, and that onNext won't be called if the demand
-    // has not been incremented by request(1).
-    // It has a 'queue of 1' meaning that it will call request(1) in
-    // onSubscribe, and then only after its 'current' buffer list has been
-    // fully written and current set to null;
-    private final class InternalWriteSubscriber
-            implements Flow.Subscriber<List<ByteBuffer>> {
-
-        volatile Flow.Subscription subscription;
-        volatile List<ByteBuffer> current;
-        volatile boolean completed;
-        final WriteEvent writeEvent = new WriteEvent(channel, this);
-        final AtomicLong writeDemand = new AtomicLong();
-
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) {
-            Flow.Subscription previous = this.subscription;
-            this.subscription = subscription;
-            debug.log(Level.DEBUG, "subscribed for writing");
-            if (current == null) {
-                if (previous == subscription || previous == null) {
-                    if (writeDemand.compareAndSet(0, 1)) {
-                        subscription.request(1);
-                    }
-                } else {
-                    writeDemand.set(1);
-                    subscription.request(1);
-                }
-            }
-        }
-
-        @Override
-        public void onNext(List<ByteBuffer> bufs) {
-            assert current == null; // this is a queue of 1.
-            assert subscription != null;
-            current = bufs;
-            tryFlushCurrent(client.isSelectorThread()); // may be in selector thread
-            // For instance in HTTP/2, a received SETTINGS frame might trigger
-            // the sending of a SETTINGS frame in turn which might cause
-            // onNext to be called from within the same selector thread that the
-            // original SETTINGS frames arrived on. If rs is the read-subscriber
-            // and ws is the write-subscriber then the following can occur:
-            // ReadEvent -> rs.onNext(bytes) -> process server SETTINGS -> write
-            // client SETTINGS -> ws.onNext(bytes) -> tryFlushCurrent
-            debugState("leaving w.onNext");
-        }
-
-        // we don't use a SequentialScheduler here: we rely on
-        // onNext() being called sequentially, and not being called
-        // if we haven't call request(1)
-        // onNext is usually called from within a user/executor thread.
-        // we will perform the initial writing in that thread.
-        // if for some reason, not all data can be written, the writeEvent
-        // will be resumed, and the rest of the data will be written from
-        // the selector manager thread when the writeEvent is fired.
-        // If we are in the selector manager thread, then we will use the executor
-        // to call request(1), ensuring that onNext() won't be called from
-        // within the selector thread.
-        // If we are not in the selector manager thread, then we don't care.
-        void tryFlushCurrent(boolean inSelectorThread) {
-            List<ByteBuffer> bufs = current;
-            if (bufs == null) return;
-            try {
-                assert inSelectorThread == client.isSelectorThread() :
-                       "should " + (inSelectorThread ? "" : "not ")
-                        + " be in the selector thread";
-                long remaining = Utils.remaining(bufs);
-                debug.log(Level.DEBUG, "trying to write: %d", remaining);
-                long written = writeAvailable(bufs);
-                debug.log(Level.DEBUG, "wrote: %d", remaining);
-                if (written == -1) {
-                    signalError(new EOFException("EOF reached while writing"));
-                    return;
-                }
-                assert written <= remaining;
-                if (remaining - written == 0) {
-                    current = null;
-                    writeDemand.decrementAndGet();
-                    Runnable requestMore = this::requestMore;
-                    if (inSelectorThread) {
-                        assert client.isSelectorThread();
-                        client.theExecutor().execute(requestMore);
-                    } else {
-                        assert !client.isSelectorThread();
-                        requestMore.run();
-                    }
-                } else {
-                    resumeWriteEvent(inSelectorThread);
-                }
-            } catch (Throwable t) {
-                signalError(t);
-                subscription.cancel();
-            }
-        }
-
-        void requestMore() {
-            try {
-                if (completed) return;
-                long d =  writeDemand.get();
-                if (writeDemand.compareAndSet(0,1)) {
-                    debug.log(Level.DEBUG, "write: requesting more...");
-                    subscription.request(1);
-                } else {
-                    debug.log(Level.DEBUG, "write: no need to request more: %d", d);
-                }
-            } catch (Throwable t) {
-                debug.log(Level.DEBUG, () ->
-                        "write: error while requesting more: " + t);
-                signalError(t);
-                subscription.cancel();
-            } finally {
-                debugState("leaving requestMore: ");
-            }
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            signalError(throwable);
-        }
-
-        @Override
-        public void onComplete() {
-            completed = true;
-            // no need to pause the write event here: the write event will
-            // be paused if there is nothing more to write.
-            List<ByteBuffer> bufs = current;
-            long remaining = bufs == null ? 0 : Utils.remaining(bufs);
-            debug.log(Level.DEBUG,  "write completed, %d yet to send", remaining);
-            debugState("InternalWriteSubscriber::onComplete");
-        }
-
-        void resumeWriteEvent(boolean inSelectorThread) {
-            debug.log(Level.DEBUG, "scheduling write event");
-            resumeEvent(writeEvent, this::signalError);
-        }
-
-//        void pauseWriteEvent() {
-//            debug.log(Level.DEBUG, "pausing write event");
-//            pauseEvent(writeEvent, this::signalError);
-//        }
-
-        void signalWritable() {
-            debug.log(Level.DEBUG, "channel is writable");
-            tryFlushCurrent(true);
-        }
-
-        void signalError(Throwable error) {
-            debug.log(Level.DEBUG, () -> "write error: " + error);
-            completed = true;
-            readPublisher.signalError(error);
-        }
-
-        // A repeatable WriteEvent which is paused after firing and can
-        // be resumed if required - see SocketFlowEvent;
-        final class WriteEvent extends SocketFlowEvent {
-            final InternalWriteSubscriber sub;
-            WriteEvent(SocketChannel channel, InternalWriteSubscriber sub) {
-                super(SelectionKey.OP_WRITE, channel);
-                this.sub = sub;
-            }
-            @Override
-            protected final void signalEvent() {
-                try {
-                    client.eventUpdated(this);
-                    sub.signalWritable();
-                } catch(Throwable t) {
-                    sub.signalError(t);
-                }
-            }
-
-            @Override
-            protected void signalError(Throwable error) {
-                sub.signalError(error);
-            }
-
-            @Override
-            System.Logger debug() {
-                return debug;
-            }
-
-        }
-
-    }
-
-    // ===================================================================== //
-    //                              Reading                                  //
-    // ===================================================================== //
-
-    // The InternalReadPublisher uses a SequentialScheduler to ensure that
-    // onNext/onError/onComplete are called sequentially on the caller's
-    // subscriber.
-    // However, it relies on the fact that the only time where
-    // runOrSchedule() is called from a user/executor thread is in signalError,
-    // right after the errorRef has been set.
-    // Because the sequential scheduler's task always checks for errors first,
-    // and always terminate the scheduler on error, then it is safe to assume
-    // that if it reaches the point where it reads from the channel, then
-    // it is running in the SelectorManager thread. This is because all
-    // other invocation of runOrSchedule() are triggered from within a
-    // ReadEvent.
-    //
-    // When pausing/resuming the event, some shortcuts can then be taken
-    // when we know we're running in the selector manager thread
-    // (in that case there's no need to call client.eventUpdated(readEvent);
-    //
-    private final class InternalReadPublisher
-            implements Flow.Publisher<List<ByteBuffer>> {
-        private final InternalReadSubscription subscriptionImpl
-                = new InternalReadSubscription();
-        AtomicReference<ReadSubscription> pendingSubscription = new AtomicReference<>();
-        private volatile ReadSubscription subscription;
-
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> s) {
-            Objects.requireNonNull(s);
-
-            TubeSubscriber sub = FlowTube.asTubeSubscriber(s);
-            ReadSubscription target = new ReadSubscription(subscriptionImpl, sub);
-            ReadSubscription previous = pendingSubscription.getAndSet(target);
-
-            if (previous != null && previous != target) {
-                debug.log(Level.DEBUG,
-                        () -> "read publisher: dropping pending subscriber: "
-                        + previous.subscriber);
-                previous.errorRef.compareAndSet(null, errorRef.get());
-                previous.signalOnSubscribe();
-                if (subscriptionImpl.completed) {
-                    previous.signalCompletion();
-                } else {
-                    previous.subscriber.dropSubscription();
-                }
-            }
-
-            debug.log(Level.DEBUG, "read publisher got subscriber");
-            subscriptionImpl.signalSubscribe();
-            debugState("leaving read.subscribe: ");
-        }
-
-        void signalError(Throwable error) {
-            if (!errorRef.compareAndSet(null, error)) {
-                return;
-            }
-            subscriptionImpl.handleError();
-        }
-
-        final class ReadSubscription implements Flow.Subscription {
-            final InternalReadSubscription impl;
-            final TubeSubscriber  subscriber;
-            final AtomicReference<Throwable> errorRef = new AtomicReference<>();
-            volatile boolean subscribed;
-            volatile boolean cancelled;
-            volatile boolean completed;
-
-            public ReadSubscription(InternalReadSubscription impl,
-                                    TubeSubscriber subscriber) {
-                this.impl = impl;
-                this.subscriber = subscriber;
-            }
-
-            @Override
-            public void cancel() {
-                cancelled = true;
-            }
-
-            @Override
-            public void request(long n) {
-                if (!cancelled) {
-                    impl.request(n);
-                } else {
-                    debug.log(Level.DEBUG,
-                              "subscription cancelled, ignoring request %d", n);
-                }
-            }
-
-            void signalCompletion() {
-                assert subscribed || cancelled;
-                if (completed || cancelled) return;
-                synchronized (this) {
-                    if (completed) return;
-                    completed = true;
-                }
-                Throwable error = errorRef.get();
-                if (error != null) {
-                    debug.log(Level.DEBUG, () ->
-                        "forwarding error to subscriber: "
-                        + error);
-                    subscriber.onError(error);
-                } else {
-                    debug.log(Level.DEBUG, "completing subscriber");
-                    subscriber.onComplete();
-                }
-            }
-
-            void signalOnSubscribe() {
-                if (subscribed || cancelled) return;
-                synchronized (this) {
-                    if (subscribed || cancelled) return;
-                    subscribed = true;
-                }
-                subscriber.onSubscribe(this);
-                debug.log(Level.DEBUG, "onSubscribe called");
-                if (errorRef.get() != null) {
-                    signalCompletion();
-                }
-            }
-        }
-
-        final class InternalReadSubscription implements Flow.Subscription {
-
-            private final Demand demand = new Demand();
-            final SequentialScheduler readScheduler;
-            private volatile boolean completed;
-            private final ReadEvent readEvent;
-            private final AsyncEvent subscribeEvent;
-
-            InternalReadSubscription() {
-                readScheduler = new SequentialScheduler(new SocketFlowTask(this::read));
-                subscribeEvent = new AsyncTriggerEvent(this::signalError,
-                                                       this::handleSubscribeEvent);
-                readEvent = new ReadEvent(channel, this);
-            }
-
-            /*
-             * This method must be invoked before any other method of this class.
-             */
-            final void signalSubscribe() {
-                if (readScheduler.isStopped() || completed) {
-                    // if already completed or stopped we can handle any
-                    // pending connection directly from here.
-                    debug.log(Level.DEBUG,
-                              "handling pending subscription while completed");
-                    handlePending();
-                } else {
-                    try {
-                        debug.log(Level.DEBUG,
-                                  "registering subscribe event");
-                        client.registerEvent(subscribeEvent);
-                    } catch (Throwable t) {
-                        signalError(t);
-                        handlePending();
-                    }
-                }
-            }
-
-            final void handleSubscribeEvent() {
-                assert client.isSelectorThread();
-                debug.log(Level.DEBUG, "subscribe event raised");
-                readScheduler.runOrSchedule();
-                if (readScheduler.isStopped() || completed) {
-                    // if already completed or stopped we can handle any
-                    // pending connection directly from here.
-                    debug.log(Level.DEBUG,
-                              "handling pending subscription when completed");
-                    handlePending();
-                }
-            }
-
-
-            /*
-             * Although this method is thread-safe, the Reactive-Streams spec seems
-             * to not require it to be as such. It's a responsibility of the
-             * subscriber to signal demand in a thread-safe manner.
-             *
-             * https://github.com/reactive-streams/reactive-streams-jvm/blob/dd24d2ab164d7de6c316f6d15546f957bec29eaa/README.md
-             * (rules 2.7 and 3.4)
-             */
-            @Override
-            public final void request(long n) {
-                if (n > 0L) {
-                    boolean wasFulfilled = demand.increase(n);
-                    if (wasFulfilled) {
-                        debug.log(Level.DEBUG, "got some demand for reading");
-                        resumeReadEvent();
-                        // if demand has been changed from fulfilled
-                        // to unfulfilled register read event;
-                    }
-                } else {
-                    signalError(new IllegalArgumentException("non-positive request"));
-                }
-                debugState("leaving request("+n+"): ");
-            }
-
-            @Override
-            public final void cancel() {
-                pauseReadEvent();
-                readScheduler.stop();
-            }
-
-            private void resumeReadEvent() {
-                debug.log(Level.DEBUG, "resuming read event");
-                resumeEvent(readEvent, this::signalError);
-            }
-
-            private void pauseReadEvent() {
-                debug.log(Level.DEBUG, "pausing read event");
-                pauseEvent(readEvent, this::signalError);
-            }
-
-
-            final void handleError() {
-                assert errorRef.get() != null;
-                readScheduler.runOrSchedule();
-            }
-
-            final void signalError(Throwable error) {
-                if (!errorRef.compareAndSet(null, error)) {
-                    return;
-                }
-                debug.log(Level.DEBUG, () -> "got read error: " + error);
-                readScheduler.runOrSchedule();
-            }
-
-            final void signalReadable() {
-                readScheduler.runOrSchedule();
-            }
-
-            /** The body of the task that runs in SequentialScheduler. */
-            final void read() {
-                // It is important to only call pauseReadEvent() when stopping
-                // the scheduler. The event is automatically paused before
-                // firing, and trying to pause it again could cause a race
-                // condition between this loop, which calls tryDecrementDemand(),
-                // and the thread that calls request(n), which will try to resume
-                // reading.
-                try {
-                    while(!readScheduler.isStopped()) {
-                        if (completed) return;
-
-                        // make sure we have a subscriber
-                        if (handlePending()) {
-                            debug.log(Level.DEBUG, "pending subscriber subscribed");
-                            return;
-                        }
-
-                        // If an error was signaled, we might not be in the
-                        // the selector thread, and that is OK, because we
-                        // will just call onError and return.
-                        ReadSubscription current = subscription;
-                        TubeSubscriber subscriber = current.subscriber;
-                        Throwable error = errorRef.get();
-                        if (error != null) {
-                            completed = true;
-                            // safe to pause here because we're finished anyway.
-                            pauseReadEvent();
-                            debug.log(Level.DEBUG, () -> "Sending error " + error
-                                  + " to subscriber " + subscriber);
-                            current.errorRef.compareAndSet(null, error);
-                            current.signalCompletion();
-                            readScheduler.stop();
-                            debugState("leaving read() loop with error: ");
-                            return;
-                        }
-
-                        // If we reach here then we must be in the selector thread.
-                        assert client.isSelectorThread();
-                        if (demand.tryDecrement()) {
-                            // we have demand.
-                            try {
-                                List<ByteBuffer> bytes = readAvailable();
-                                if (bytes == EOF) {
-                                    if (!completed) {
-                                        debug.log(Level.DEBUG, "got read EOF");
-                                        completed = true;
-                                        // safe to pause here because we're finished
-                                        // anyway.
-                                        pauseReadEvent();
-                                        current.signalCompletion();
-                                        readScheduler.stop();
-                                    }
-                                    debugState("leaving read() loop after EOF: ");
-                                    return;
-                                } else if (Utils.remaining(bytes) > 0) {
-                                    // the subscriber is responsible for offloading
-                                    // to another thread if needed.
-                                    debug.log(Level.DEBUG, () -> "read bytes: "
-                                            + Utils.remaining(bytes));
-                                    assert !current.completed;
-                                    subscriber.onNext(bytes);
-                                    // we could continue looping until the demand
-                                    // reaches 0. However, that would risk starving
-                                    // other connections (bound to other socket
-                                    // channels) - as other selected keys activated
-                                    // by the selector manager thread might be
-                                    // waiting for this event to terminate.
-                                    // So resume the read event and return now...
-                                    resumeReadEvent();
-                                    debugState("leaving read() loop after onNext: ");
-                                    return;
-                                } else {
-                                    // nothing available!
-                                    debug.log(Level.DEBUG, "no more bytes available");
-                                    // re-increment the demand and resume the read
-                                    // event. This ensures that this loop is
-                                    // executed again when the socket becomes
-                                    // readable again.
-                                    demand.increase(1);
-                                    resumeReadEvent();
-                                    debugState("leaving read() loop with no bytes");
-                                    return;
-                                }
-                            } catch (Throwable x) {
-                                signalError(x);
-                                continue;
-                            }
-                        } else {
-                            debug.log(Level.DEBUG, "no more demand for reading");
-                            // the event is paused just after firing, so it should
-                            // still be paused here, unless the demand was just
-                            // incremented from 0 to n, in which case, the
-                            // event will be resumed, causing this loop to be
-                            // invoked again when the socket becomes readable:
-                            // This is what we want.
-                            // Trying to pause the event here would actually
-                            // introduce a race condition between this loop and
-                            // request(n).
-                            debugState("leaving read() loop with no demand");
-                            break;
-                        }
-                    }
-                } catch (Throwable t) {
-                    debug.log(Level.DEBUG, "Unexpected exception in read loop", t);
-                    signalError(t);
-                } finally {
-                    handlePending();
-                }
-            }
-
-            boolean handlePending() {
-                ReadSubscription pending = pendingSubscription.getAndSet(null);
-                if (pending == null) return false;
-                debug.log(Level.DEBUG, "handling pending subscription for %s",
-                          pending.subscriber);
-                ReadSubscription current = subscription;
-                if (current != null && current != pending && !completed) {
-                    current.subscriber.dropSubscription();
-                }
-                debug.log(Level.DEBUG, "read demand reset to 0");
-                subscriptionImpl.demand.reset(); // subscriber will increase demand if it needs to.
-                pending.errorRef.compareAndSet(null, errorRef.get());
-                if (!readScheduler.isStopped()) {
-                    subscription = pending;
-                } else {
-                    debug.log(Level.DEBUG, "socket tube is already stopped");
-                }
-                debug.log(Level.DEBUG, "calling onSubscribe");
-                pending.signalOnSubscribe();
-                if (completed) {
-                    pending.errorRef.compareAndSet(null, errorRef.get());
-                    pending.signalCompletion();
-                }
-                return true;
-            }
-        }
-
-
-        // A repeatable ReadEvent which is paused after firing and can
-        // be resumed if required - see SocketFlowEvent;
-        final class ReadEvent extends SocketFlowEvent {
-            final InternalReadSubscription sub;
-            ReadEvent(SocketChannel channel, InternalReadSubscription sub) {
-                super(SelectionKey.OP_READ, channel);
-                this.sub = sub;
-            }
-            @Override
-            protected final void signalEvent() {
-                try {
-                    client.eventUpdated(this);
-                    sub.signalReadable();
-                } catch(Throwable t) {
-                    sub.signalError(t);
-                }
-            }
-
-            @Override
-            protected final void signalError(Throwable error) {
-                sub.signalError(error);
-            }
-
-            @Override
-            System.Logger debug() {
-                return debug;
-            }
-        }
-
-    }
-
-    // ===================================================================== //
-    //                   Socket Channel Read/Write                           //
-    // ===================================================================== //
-    static final int MAX_BUFFERS = 3;
-    static final List<ByteBuffer> EOF = List.of();
-
-    private List<ByteBuffer> readAvailable() throws IOException {
-        ByteBuffer buf = buffersSource.get();
-        assert buf.hasRemaining();
-
-        int read;
-        int pos = buf.position();
-        List<ByteBuffer> list = null;
-        while (buf.hasRemaining()) {
-            while ((read = channel.read(buf)) > 0) {
-               if (!buf.hasRemaining()) break;
-            }
-
-            // nothing read;
-            if (buf.position() == pos) {
-                // An empty list signal the end of data, and should only be
-                // returned if read == -1.
-                // If we already read some data, then we must return what we have
-                // read, and -1 will be returned next time the caller attempts to
-                // read something.
-                if (list == null && read == -1) {  // eof
-                    list = EOF;
-                    break;
-                }
-            }
-            buf.limit(buf.position());
-            buf.position(pos);
-            if (list == null) {
-                list = List.of(buf);
-            } else {
-                if (!(list instanceof ArrayList)) {
-                    list = new ArrayList<>(list);
-                }
-                list.add(buf);
-            }
-            if (read <= 0 || list.size() == MAX_BUFFERS) break;
-            buf = buffersSource.get();
-            pos = buf.position();
-            assert buf.hasRemaining();
-        }
-        return list;
-    }
-
-    private long writeAvailable(List<ByteBuffer> bytes) throws IOException {
-        ByteBuffer[] srcs = bytes.toArray(Utils.EMPTY_BB_ARRAY);
-        final long remaining = Utils.remaining(srcs);
-        long written = 0;
-        while (remaining > written) {
-            long w = channel.write(srcs);
-            if (w == -1 && written == 0) return -1;
-            if (w == 0) break;
-            written += w;
-        }
-        return written;
-    }
-
-    private void resumeEvent(SocketFlowEvent event,
-                             Consumer<Throwable> errorSignaler) {
-        boolean registrationRequired;
-        synchronized(lock) {
-            registrationRequired = !event.registered();
-            event.resume();
-        }
-        try {
-            if (registrationRequired) {
-                client.registerEvent(event);
-             } else {
-                client.eventUpdated(event);
-            }
-        } catch(Throwable t) {
-            errorSignaler.accept(t);
-        }
-   }
-
-    private void pauseEvent(SocketFlowEvent event,
-                            Consumer<Throwable> errorSignaler) {
-        synchronized(lock) {
-            event.pause();
-        }
-        try {
-            client.eventUpdated(event);
-        } catch(Throwable t) {
-            errorSignaler.accept(t);
-        }
-    }
-
-    @Override
-    public void connectFlows(TubePublisher writePublisher,
-                             TubeSubscriber readSubscriber) {
-        debug.log(Level.DEBUG, "connecting flows");
-        this.subscribe(readSubscriber);
-        writePublisher.subscribe(this);
-    }
-
-
-    @Override
-    public String toString() {
-        return dbgString();
-    }
-
-    final String dbgString() {
-        return "SocketTube("+id+")";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Stream.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1117 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-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 jdk.incubator.http.HttpResponse.BodySubscriber;
-import jdk.incubator.http.internal.common.*;
-import jdk.incubator.http.internal.frame.*;
-import jdk.incubator.http.internal.hpack.DecodingCallback;
-
-/**
- * Http/2 Stream handling.
- *
- * REQUESTS
- *
- * sendHeadersOnly() -- assembles HEADERS frame and puts on connection outbound Q
- *
- * sendRequest() -- sendHeadersOnly() + sendBody()
- *
- * sendBodyAsync() -- calls sendBody() in an executor thread.
- *
- * sendHeadersAsync() -- calls sendHeadersOnly() which does not block
- *
- * sendRequestAsync() -- calls sendRequest() in an executor thread
- *
- * RESPONSES
- *
- * Multiple responses can be received per request. Responses are queued up on
- * a LinkedList of CF<HttpResponse> and the the first one on the list is completed
- * with the next response
- *
- * getResponseAsync() -- queries list of response CFs and returns first one
- *               if one exists. Otherwise, creates one and adds it to list
- *               and returns it. Completion is achieved through the
- *               incoming() upcall from connection reader thread.
- *
- * getResponse() -- calls getResponseAsync() and waits for CF to complete
- *
- * responseBodyAsync() -- calls responseBody() in an executor thread.
- *
- * incoming() -- entry point called from connection reader thread. Frames are
- *               either handled immediately without blocking or for data frames
- *               placed on the stream's inputQ which is consumed by the stream's
- *               reader thread.
- *
- * PushedStream sub class
- * ======================
- * Sending side methods are not used because the request comes from a PUSH_PROMISE
- * frame sent by the server. When a PUSH_PROMISE is received the PushedStream
- * is created. PushedStream does not use responseCF list as there can be only
- * one response. The CF is created when the object created and when the response
- * HEADERS frame is received the object is completed.
- */
-class Stream<T> extends ExchangeImpl<T> {
-
-    final static boolean DEBUG = Utils.DEBUG; // Revisit: temporary developer's flag
-    final System.Logger  debug = Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    final ConcurrentLinkedQueue<Http2Frame> inputQ = new ConcurrentLinkedQueue<>();
-    final SequentialScheduler sched =
-            SequentialScheduler.synchronizedScheduler(this::schedule);
-    final SubscriptionBase userSubscription = new SubscriptionBase(sched, this::cancel);
-
-    /**
-     * This stream's identifier. Assigned lazily by the HTTP2Connection before
-     * the stream's first frame is sent.
-     */
-    protected volatile int streamid;
-
-    long requestContentLen;
-
-    final Http2Connection connection;
-    final HttpRequestImpl request;
-    final DecodingCallback rspHeadersConsumer;
-    HttpHeadersImpl responseHeaders;
-    final HttpHeadersImpl requestPseudoHeaders;
-    volatile HttpResponse.BodySubscriber<T> responseSubscriber;
-    final HttpRequest.BodyPublisher requestPublisher;
-    volatile RequestSubscriber requestSubscriber;
-    volatile int responseCode;
-    volatile Response response;
-    volatile Throwable failed; // The exception with which this stream was canceled.
-    final CompletableFuture<Void> requestBodyCF = new MinimalFuture<>();
-    volatile CompletableFuture<T> responseBodyCF;
-
-    /** True if END_STREAM has been seen in a frame received on this stream. */
-    private volatile boolean remotelyClosed;
-    private volatile boolean closed;
-    private volatile boolean endStreamSent;
-
-    // state flags
-    private boolean requestSent, responseReceived;
-
-    /**
-     * A reference to this Stream's connection Send Window controller. The
-     * stream MUST acquire the appropriate amount of Send Window before
-     * sending any data. Will be null for PushStreams, as they cannot send data.
-     */
-    private final WindowController windowController;
-    private final WindowUpdateSender windowUpdater;
-
-    @Override
-    HttpConnection connection() {
-        return connection.connection;
-    }
-
-    /**
-     * Invoked either from incoming() -> {receiveDataFrame() or receiveResetFrame() }
-     * of after user subscription window has re-opened, from SubscriptionBase.request()
-     */
-    private void schedule() {
-        if (responseSubscriber == null)
-            // can't process anything yet
-            return;
-
-        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;
-            }
-        }
-        Throwable t = failed;
-        if (t != null) {
-            sched.stop();
-            responseSubscriber.onError(t);
-            close();
-        }
-    }
-
-    // Callback invoked after the Response BodySubscriber has consumed the
-    // buffers contained in a DataFrame.
-    // Returns true if END_STREAM is reached, false otherwise.
-    private boolean consumed(DataFrame df) {
-        // RFC 7540 6.1:
-        // The entire DATA frame payload is included in flow control,
-        // including the Pad Length and Padding fields if present
-        int len = df.payloadLength();
-        connection.windowUpdater.update(len);
-
-        if (!df.getFlag(DataFrame.END_STREAM)) {
-            // Don't send window update on a stream which is
-            // closed or half closed.
-            windowUpdater.update(len);
-            return false; // more data coming
-        }
-        return true; // end of stream
-    }
-
-    @Override
-    CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler,
-                                       boolean returnConnectionToPool,
-                                       Executor executor)
-    {
-        Log.logTrace("Reading body on stream {0}", streamid);
-        BodySubscriber<T> bodySubscriber = handler.apply(responseCode, responseHeaders);
-        CompletableFuture<T> cf = receiveData(bodySubscriber);
-
-        PushGroup<?,?> pg = exchange.getPushGroup();
-        if (pg != null) {
-            // if an error occurs make sure it is recorded in the PushGroup
-            cf = cf.whenComplete((t,e) -> pg.pushError(e));
-        }
-        return cf;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("streamid: ")
-                .append(streamid);
-        return sb.toString();
-    }
-
-    private void receiveDataFrame(DataFrame df) {
-        inputQ.add(df);
-        sched.runOrSchedule();
-    }
-
-    /** Handles a RESET frame. RESET is always handled inline in the queue. */
-    private void receiveResetFrame(ResetFrame frame) {
-        inputQ.add(frame);
-        sched.runOrSchedule();
-    }
-
-    // pushes entire response body into response subscriber
-    // blocking when required by local or remote flow control
-    CompletableFuture<T> receiveData(BodySubscriber<T> bodySubscriber) {
-        responseBodyCF = MinimalFuture.of(bodySubscriber.getBody());
-
-        if (isCanceled()) {
-            Throwable t = getCancelCause();
-            responseBodyCF.completeExceptionally(t);
-        } else {
-            bodySubscriber.onSubscribe(userSubscription);
-        }
-        // Set the responseSubscriber field now that onSubscribe has been called.
-        // This effectively allows the scheduler to start invoking the callbacks.
-        responseSubscriber = bodySubscriber;
-        sched.runOrSchedule(); // in case data waiting already to be processed
-        return responseBodyCF;
-    }
-
-    @Override
-    CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
-        return sendBodyImpl().thenApply( v -> this);
-    }
-
-    @SuppressWarnings("unchecked")
-    Stream(Http2Connection connection,
-           Exchange<T> e,
-           WindowController windowController)
-    {
-        super(e);
-        this.connection = connection;
-        this.windowController = windowController;
-        this.request = e.request();
-        this.requestPublisher = request.requestPublisher;  // may be null
-        responseHeaders = new HttpHeadersImpl();
-        rspHeadersConsumer = (name, value) -> {
-            responseHeaders.addHeader(name.toString(), value.toString());
-            if (Log.headers() && Log.trace()) {
-                Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}",
-                             streamid, name, value);
-            }
-        };
-        this.requestPseudoHeaders = new HttpHeadersImpl();
-        // NEW
-        this.windowUpdater = new StreamWindowUpdateSender(connection);
-    }
-
-    /**
-     * Entry point from Http2Connection reader thread.
-     *
-     * Data frames will be removed by response body thread.
-     */
-    void incoming(Http2Frame frame) throws IOException {
-        debug.log(Level.DEBUG, "incoming: %s", frame);
-        if ((frame instanceof HeaderFrame)) {
-            HeaderFrame hframe = (HeaderFrame)frame;
-            if (hframe.endHeaders()) {
-                Log.logTrace("handling response (streamid={0})", streamid);
-                handleResponse();
-                if (hframe.getFlag(HeaderFrame.END_STREAM)) {
-                    receiveDataFrame(new DataFrame(streamid, DataFrame.END_STREAM, List.of()));
-                }
-            }
-        } else if (frame instanceof DataFrame) {
-            receiveDataFrame((DataFrame)frame);
-        } else {
-            otherFrame(frame);
-        }
-    }
-
-    void otherFrame(Http2Frame frame) throws IOException {
-        switch (frame.type()) {
-            case WindowUpdateFrame.TYPE:
-                incoming_windowUpdate((WindowUpdateFrame) frame);
-                break;
-            case ResetFrame.TYPE:
-                incoming_reset((ResetFrame) frame);
-                break;
-            case PriorityFrame.TYPE:
-                incoming_priority((PriorityFrame) frame);
-                break;
-            default:
-                String msg = "Unexpected frame: " + frame.toString();
-                throw new IOException(msg);
-        }
-    }
-
-    // The Hpack decoder decodes into one of these consumers of name,value pairs
-
-    DecodingCallback rspHeadersConsumer() {
-        return rspHeadersConsumer;
-    }
-
-    protected void handleResponse() throws IOException {
-        responseCode = (int)responseHeaders
-                .firstValueAsLong(":status")
-                .orElseThrow(() -> new IOException("no statuscode in response"));
-
-        response = new Response(
-                request, exchange, responseHeaders,
-                responseCode, HttpClient.Version.HTTP_2);
-
-        /* TODO: review if needs to be removed
-           the value is not used, but in case `content-length` doesn't parse as
-           long, there will be NumberFormatException. If left as is, make sure
-           code up the stack handles NFE correctly. */
-        responseHeaders.firstValueAsLong("content-length");
-
-        if (Log.headers()) {
-            StringBuilder sb = new StringBuilder("RESPONSE HEADERS:\n");
-            Log.dumpHeaders(sb, "    ", responseHeaders);
-            Log.logHeaders(sb.toString());
-        }
-
-        completeResponse(response);
-    }
-
-    void incoming_reset(ResetFrame frame) {
-        Log.logTrace("Received RST_STREAM on stream {0}", streamid);
-        if (endStreamReceived()) {
-            Log.logTrace("Ignoring RST_STREAM frame received on remotely closed stream {0}", streamid);
-        } else if (closed) {
-            Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
-        } else {
-            // put it in the input queue in order to read all
-            // pending data frames first. Indeed, a server may send
-            // RST_STREAM after sending END_STREAM, in which case we should
-            // ignore it. However, we won't know if we have received END_STREAM
-            // or not until all pending data frames are read.
-            receiveResetFrame(frame);
-            // RST_STREAM was pushed to the queue. It will be handled by
-            // asyncReceive after all pending data frames have been
-            // processed.
-            Log.logTrace("RST_STREAM pushed in queue for stream {0}", streamid);
-        }
-    }
-
-    void handleReset(ResetFrame frame) {
-        Log.logTrace("Handling RST_STREAM on stream {0}", streamid);
-        if (!closed) {
-            close();
-            int error = frame.getErrorCode();
-            completeResponseExceptionally(new IOException(ErrorFrame.stringForCode(error)));
-        } else {
-            Log.logTrace("Ignoring RST_STREAM frame received on closed stream {0}", streamid);
-        }
-    }
-
-    void incoming_priority(PriorityFrame frame) {
-        // TODO: implement priority
-        throw new UnsupportedOperationException("Not implemented");
-    }
-
-    private void incoming_windowUpdate(WindowUpdateFrame frame)
-        throws IOException
-    {
-        int amount = frame.getUpdate();
-        if (amount <= 0) {
-            Log.logTrace("Resetting stream: {0} %d, Window Update amount: %d\n",
-                         streamid, streamid, amount);
-            connection.resetStream(streamid, ResetFrame.FLOW_CONTROL_ERROR);
-        } else {
-            assert streamid != 0;
-            boolean success = windowController.increaseStreamWindow(amount, streamid);
-            if (!success) {  // overflow
-                connection.resetStream(streamid, ResetFrame.FLOW_CONTROL_ERROR);
-            }
-        }
-    }
-
-    void incoming_pushPromise(HttpRequestImpl pushReq,
-                              PushedStream<?,T> pushStream)
-        throws IOException
-    {
-        if (Log.requests()) {
-            Log.logRequest("PUSH_PROMISE: " + pushReq.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;
-        }
-
-        HttpResponse.MultiSubscriber<?,T> proc = pushGroup.subscriber();
-
-        CompletableFuture<HttpResponse<T>> cf = pushStream.responseCF();
-
-        Optional<HttpResponse.BodyHandler<T>> bpOpt =
-                pushGroup.handlerForPushRequest(pushReq);
-
-        if (!bpOpt.isPresent()) {
-            IOException ex = new IOException("Stream "
-                 + streamid + " cancelled by user");
-            if (Log.trace()) {
-                Log.logTrace("No body subscriber for {0}: {1}", pushReq,
-                            ex.getMessage());
-            }
-            pushStream.cancelImpl(ex);
-            cf.completeExceptionally(ex);
-            return;
-        }
-
-        pushGroup.addPush();
-        pushStream.requestSent();
-        pushStream.setPushHandler(bpOpt.get());
-        // setup housekeeping for when the push is received
-        // TODO: deal with ignoring of CF anti-pattern
-        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);
-                proc.onError(pushReq, t);
-            } else {
-                proc.onResponse(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();
-        OutgoingHeaders<Stream<T>> f = new OutgoingHeaders<>(h, request.getUserHeaders(), this);
-        if (contentLength == 0) {
-            f.setFlag(HeadersFrame.END_STREAM);
-            endStreamSent = true;
-        }
-        return f;
-    }
-
-    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<U,T> extends Stream<T> {
-        final PushGroup<U,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;
-        final CompletableFuture<HttpResponse<T>> responseCF;
-        final HttpRequestImpl pushReq;
-        HttpResponse.BodyHandler<T> pushHandler;
-
-        PushedStream(PushGroup<U,T> pushGroup,
-                     Http2Connection connection,
-                     Exchange<T> pushReq) {
-            // ## no request body possible, null window controller
-            super(connection, pushReq, null);
-            this.pushGroup = pushGroup;
-            this.pushReq = pushReq.request();
-            this.pushCF = new MinimalFuture<>();
-            this.responseCF = new MinimalFuture<>();
-        }
-
-        CompletableFuture<HttpResponse<T>> responseCF() {
-            return responseCF;
-        }
-
-        synchronized void setPushHandler(HttpResponse.BodyHandler<T> pushHandler) {
-            this.pushHandler = pushHandler;
-        }
-
-        synchronized HttpResponse.BodyHandler<T> getPushHandler() {
-            // ignored parameters to function can be used as BodyHandler
-            return this.pushHandler;
-        }
-
-        // Following methods call the super class but in case of
-        // error record it in the PushGroup. The error method is called
-        // with a null value when no error occurred (is a no-op)
-        @Override
-        CompletableFuture<ExchangeImpl<T>> sendBodyAsync() {
-            return super.sendBodyAsync()
-                        .whenComplete((ExchangeImpl<T> v, Throwable t)
-                                -> pushGroup.pushError(Utils.getCompletionCause(t)));
-        }
-
-        @Override
-        CompletableFuture<ExchangeImpl<T>> sendHeadersAsync() {
-            return super.sendHeadersAsync()
-                        .whenComplete((ExchangeImpl<T> ex, Throwable t)
-                                -> pushGroup.pushError(Utils.getCompletionCause(t)));
-        }
-
-        @Override
-        CompletableFuture<Response> getResponseAsync(Executor executor) {
-            CompletableFuture<Response> cf = pushCF.whenComplete(
-                    (v, t) -> pushGroup.pushError(Utils.getCompletionCause(t)));
-            if(executor!=null && !cf.isDone()) {
-                cf  = cf.thenApplyAsync( r -> r, executor);
-            }
-            return cf;
-        }
-
-        @Override
-        CompletableFuture<T> readBodyAsync(
-                HttpResponse.BodyHandler<T> handler,
-                boolean returnConnectionToPool,
-                Executor executor)
-        {
-            return super.readBodyAsync(handler, returnConnectionToPool, executor)
-                        .whenComplete((v, t) -> pushGroup.pushError(t));
-        }
-
-        @Override
-        void completeResponse(Response r) {
-            Log.logResponse(r::toString);
-            pushCF.complete(r); // not strictly required for push API
-            // start reading the body using the obtained BodySubscriber
-            CompletableFuture<Void> start = new MinimalFuture<>();
-            start.thenCompose( v -> readBodyAsync(getPushHandler(), false, getExchange().executor()))
-                .whenComplete((T body, Throwable t) -> {
-                    if (t != null) {
-                        responseCF.completeExceptionally(t);
-                    } else {
-                        HttpResponseImpl<T> resp =
-                                new HttpResponseImpl<>(r.request, r, null, body, getExchange());
-                        responseCF.complete(resp);
-                    }
-                });
-            start.completeAsync(() -> null, getExchange().executor());
-        }
-
-        @Override
-        void completeResponseExceptionally(Throwable t) {
-            pushCF.completeExceptionally(t);
-        }
-
-//        @Override
-//        synchronized void responseReceived() {
-//            super.responseReceived();
-//        }
-
-        // create and return the PushResponseImpl
-        @Override
-        protected void handleResponse() {
-            responseCode = (int)responseHeaders
-                .firstValueAsLong(":status")
-                .orElse(-1);
-
-            if (responseCode == -1) {
-                completeResponseExceptionally(new IOException("No status code"));
-            }
-
-            this.response = new Response(
-                pushReq, exchange, responseHeaders,
-                responseCode, HttpClient.Version.HTTP_2);
-
-            /* TODO: review if needs to be removed
-               the value is not used, but in case `content-length` doesn't parse
-               as long, there will be NumberFormatException. If left as is, make
-               sure code up the stack handles NFE correctly. */
-            responseHeaders.firstValueAsLong("content-length");
-
-            if (Log.headers()) {
-                StringBuilder sb = new StringBuilder("RESPONSE HEADERS");
-                sb.append(" (streamid=").append(streamid).append("): ");
-                Log.dumpHeaders(sb, "    ", responseHeaders);
-                Log.logHeaders(sb.toString());
-            }
-
-            // different implementations for normal streams and pushed streams
-            completeResponse(response);
-        }
-    }
-
-    final class StreamWindowUpdateSender extends WindowUpdateSender {
-
-        StreamWindowUpdateSender(Http2Connection connection) {
-            super(connection);
-        }
-
-        @Override
-        int getStreamId() {
-            return streamid;
-        }
-    }
-
-    /**
-     * Returns true if this exchange was canceled.
-     * @return true if this exchange was canceled.
-     */
-    synchronized boolean isCanceled() {
-        return failed != null;
-    }
-
-    /**
-     * Returns the cause for which this exchange was canceled, if available.
-     * @return the cause for which this exchange was canceled, if available.
-     */
-    synchronized Throwable getCancelCause() {
-        return failed;
-    }
-
-    final String dbgString() {
-        return connection.dbgString() + "/Stream("+streamid+")";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/TimeoutEvent.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Timeout event notified by selector thread. Executes the given handler if
- * the timer not canceled first.
- *
- * Register with {@link HttpClientImpl#registerTimer(TimeoutEvent)}.
- *
- * Cancel with {@link HttpClientImpl#cancelTimer(TimeoutEvent)}.
- */
-abstract class TimeoutEvent implements Comparable<TimeoutEvent> {
-
-    private static final AtomicLong COUNTER = new AtomicLong();
-    // we use id in compareTo to make compareTo consistent with equals
-    // see TimeoutEvent::compareTo below;
-    private final long id = COUNTER.incrementAndGet();
-    private final Instant deadline;
-
-    TimeoutEvent(Duration duration) {
-        deadline = Instant.now().plus(duration);
-    }
-
-    public abstract void handle();
-
-    public Instant deadline() {
-        return deadline;
-    }
-
-    @Override
-    public int compareTo(TimeoutEvent other) {
-        if (other == this) return 0;
-        // if two events have the same deadline, but are not equals, then the
-        // smaller is the one that was created before (has the smaller id).
-        // This is arbitrary and we don't really care which is smaller or
-        // greater, but we need a total order, so two events with the
-        // same deadline cannot compare == 0 if they are not equals.
-        final int compareDeadline = this.deadline.compareTo(other.deadline);
-        if (compareDeadline == 0 && !this.equals(other)) {
-            long diff = this.id - other.id; // should take care of wrap around
-            if (diff < 0) return -1;
-            else if (diff > 0) return 1;
-            else assert false : "Different events with same id and deadline";
-        }
-        return compareDeadline;
-    }
-
-    @Override
-    public String toString() {
-        return "TimeoutEvent[id=" + id + ", deadline=" + deadline + "]";
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocket.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,718 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.time.Duration;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-
-/**
- * A WebSocket client.
- * {@Incubating}
- *
- * <p> To create a {@code WebSocket} use the {@link HttpClient#newWebSocketBuilder}
- * method. To close a {@code WebSocket} use one of the {@code sendClose} or
- * {@code abort} methods.
- *
- * <p> WebSocket messages are sent through a {@code WebSocket} and received
- * through the {@code WebSocket}'s {@code Listener}. Messages can be sent until
- * the output is closed, and received until the input is closed.
- * A {@code WebSocket} whose output and input are both closed may be considered
- * itself closed. To check these states use {@link #isOutputClosed()} and
- * {@link #isInputClosed()}.
- *
- * <p> Methods that send messages return {@code CompletableFuture} which
- * completes normally if the message is sent or completes exceptionally if an
- * error occurs.
- *
- * <p> To receive a message, first request it. If {@code n} messages are
- * requested, the listener will receive up to {@code n} more invocations of the
- * designated methods from the {@code WebSocket}. To request messages use
- * {@link #request(long)}. Request is an additive operation, that is
- * {@code request(n)} followed by {@code request(m)} is equivalent to
- * {@code request(n + m)}.
- *
- * <p> When sending or receiving a message in parts, a whole message is
- * transferred as a sequence of one or more invocations where the last
- * invocation is identified via an additional method argument.
- *
- * <p> Unless otherwise stated, {@code null} arguments will cause methods
- * of {@code WebSocket} to throw {@code NullPointerException}, similarly,
- * {@code WebSocket} will not pass {@code null} arguments to methods of
- * {@code Listener}.
- *
- * @implSpec Methods of {@code WebSocket} are failure-atomic in respect to
- * {@code NullPointerException}, {@code IllegalArgumentException} and
- * {@code IllegalStateException}. That is, if a method throws said exception, or
- * a returned {@code CompletableFuture} completes exceptionally with said
- * exception, the {@code WebSocket} will behave as if the method has not been
- * invoked at all.
- *
- * <p> A {@code WebSocket} invokes methods of its listener in a thread-safe
- * manner.
- *
- * <p> {@code WebSocket} handles Ping and Close messages automatically (as per
- * RFC 6455) by replying with Pong and Close messages respectively. If the
- * listener receives Ping or Close messages, no mandatory actions from the
- * listener are required.
- *
- * @since 9
- */
-public interface WebSocket {
-
-    /**
-     * The WebSocket Close message status code (<code>{@value}</code>),
-     * indicating normal closure, meaning that the purpose for which the
-     * connection was established has been fulfilled.
-     *
-     * @see #sendClose(int, String)
-     * @see Listener#onClose(WebSocket, int, String)
-     */
-    int NORMAL_CLOSURE = 1000;
-
-    /**
-     * A builder for creating {@code WebSocket} instances.
-     * {@Incubating}
-     *
-     * <p> To obtain a {@code WebSocket} configure a builder as required by
-     * calling intermediate methods (the ones that return the builder itself),
-     * then call {@code buildAsync()}. If an intermediate method is not called,
-     * an appropriate default value (or behavior) will be assumed.
-     *
-     * <p> Unless otherwise stated, {@code null} arguments will cause methods of
-     * {@code Builder} to throw {@code NullPointerException}.
-     *
-     * @since 9
-     */
-    interface Builder {
-
-        /**
-         * Adds the given name-value pair to the list of additional HTTP headers
-         * sent during the opening handshake.
-         *
-         * <p> Headers defined in WebSocket Protocol are illegal. If this method
-         * is not invoked, no additional HTTP headers will be sent.
-         *
-         * @param name
-         *         the header name
-         * @param value
-         *         the header value
-         *
-         * @return this builder
-         */
-        Builder header(String name, String value);
-
-        /**
-         * Sets a timeout for establishing a WebSocket connection.
-         *
-         * <p> If the connection is not established within the specified
-         * duration then building of the {@code WebSocket} will fail with
-         * {@link HttpTimeoutException}. If this method is not invoked then the
-         * infinite timeout is assumed.
-         *
-         * @param timeout
-         *         the timeout, non-{@linkplain Duration#isNegative() negative},
-         *         non-{@linkplain Duration#ZERO ZERO}
-         *
-         * @return this builder
-         */
-        Builder connectTimeout(Duration timeout);
-
-        /**
-         * Sets a request for the given subprotocols.
-         *
-         * <p> After the {@code WebSocket} has been built, the actual
-         * subprotocol can be queried via
-         * {@link WebSocket#getSubprotocol WebSocket.getSubprotocol()}.
-         *
-         * <p> Subprotocols are specified in the order of preference. The most
-         * preferred subprotocol is specified first. If there are any additional
-         * subprotocols they are enumerated from the most preferred to the least
-         * preferred.
-         *
-         * <p> Subprotocols not conforming to the syntax of subprotocol
-         * identifiers are illegal. If this method is not invoked then no
-         * subprotocols will be requested.
-         *
-         * @param mostPreferred
-         *         the most preferred subprotocol
-         * @param lesserPreferred
-         *         the lesser preferred subprotocols
-         *
-         * @return this builder
-         */
-        Builder subprotocols(String mostPreferred, String... lesserPreferred);
-
-        /**
-         * Builds a {@link WebSocket} connected to the given {@code URI} and
-         * associated with the given {@code Listener}.
-         *
-         * <p> Returns a {@code CompletableFuture} which will either complete
-         * normally with the resulting {@code WebSocket} or complete
-         * exceptionally with one of the following errors:
-         * <ul>
-         * <li> {@link IOException} -
-         *          if an I/O error occurs
-         * <li> {@link WebSocketHandshakeException} -
-         *          if the opening handshake fails
-         * <li> {@link HttpTimeoutException} -
-         *          if the opening handshake does not complete within
-         *          the timeout
-         * <li> {@link InterruptedException} -
-         *          if the operation is interrupted
-         * <li> {@link SecurityException} -
-         *          if a security manager has been installed and it denies
-         *          {@link java.net.URLPermission access} to {@code uri}.
-         *          <a href="HttpRequest.html#securitychecks">Security checks</a>
-         *          contains more information relating to the security context
-         *          in which the the listener is invoked.
-         * <li> {@link IllegalArgumentException} -
-         *          if any of the arguments of this builder's methods are
-         *          illegal
-         * </ul>
-         *
-         * @param uri
-         *         the WebSocket URI
-         * @param listener
-         *         the listener
-         *
-         * @return a {@code CompletableFuture} with the {@code WebSocket}
-         */
-        CompletableFuture<WebSocket> buildAsync(URI uri, Listener listener);
-    }
-
-    /**
-     * The receiving interface of {@code WebSocket}.
-     * {@Incubating}
-     *
-     * <p> A {@code WebSocket} invokes methods on its listener when it receives
-     * messages or encounters events. The invoking {@code WebSocket} is passed
-     * as an argument to {@code Listener}'s methods. A {@code WebSocket} invokes
-     * methods on its listener in a thread-safe manner.
-     *
-     * <p> Unless otherwise stated if a listener's method throws an exception or
-     * a {@code CompletionStage} returned from a method completes exceptionally,
-     * the {@code WebSocket} will invoke {@code onError} with this exception.
-     *
-     * <p> If a listener's method returns {@code null} rather than a
-     * {@code CompletionStage}, {@code WebSocket} will behave as if the listener
-     * returned a {@code CompletionStage} that is already completed normally.
-     *
-     * @since 9
-     */
-    interface Listener {
-
-        /**
-         * A {@code WebSocket} has been connected.
-         *
-         * <p> This is the first invocation and it is made at most once. This
-         * method is typically used to make an initial request for messages.
-         *
-         * @implSpec The default implementation of this method behaves as if:
-         *
-         * <pre>{@code
-         *     webSocket.request(1);
-         * }</pre>
-         *
-         * @param webSocket
-         *         the WebSocket that has been connected
-         */
-        default void onOpen(WebSocket webSocket) { webSocket.request(1); }
-
-        /**
-         * A Text message has been received.
-         *
-         * <p> If a whole message has been received, this method will be invoked
-         * with {@code MessagePart.WHOLE} marker. Otherwise, it will be invoked
-         * with {@code FIRST}, possibly a number of times with {@code PART} and,
-         * finally, with {@code LAST} markers. If this message is partial, it
-         * may be an incomplete UTF-16 sequence. However, the concatenation of
-         * all messages through the last will be a complete UTF-16 sequence.
-         *
-         * <p> Return a {@code CompletionStage} which will be used by the
-         * {@code WebSocket} as a signal it may reclaim the
-         * {@code CharSequence}. Do not access the {@code CharSequence} after
-         * this {@ode CompletionStage} has completed.
-         *
-         * @implSpec The default implementation of this method behaves as if:
-         *
-         * <pre>{@code
-         *     webSocket.request(1);
-         *     return null;
-         * }</pre>
-         *
-         * @implNote This method is always invoked with character sequences
-         * which are complete UTF-16 sequences.
-         *
-         * @param webSocket
-         *         the WebSocket on which the message has been received
-         * @param message
-         *         the message
-         * @param part
-         *         the part
-         *
-         * @return a {@code CompletionStage} which completes when the
-         * {@code CharSequence} may be reclaimed; or {@code null} if it may be
-         * reclaimed immediately
-         */
-        default CompletionStage<?> onText(WebSocket webSocket,
-                                          CharSequence message,
-                                          MessagePart part) {
-            webSocket.request(1);
-            return null;
-        }
-
-        /**
-         * A Binary message has been received.
-         *
-         * <p> If a whole message has been received, this method will be invoked
-         * with {@code MessagePart.WHOLE} marker. Otherwise, it will be invoked
-         * with {@code FIRST}, possibly a number of times with {@code PART} and,
-         * finally, with {@code LAST} markers.
-         *
-         * <p> This message consists of bytes from the buffer's position to
-         * its limit.
-         *
-         * <p> Return a {@code CompletionStage} which will be used by the
-         * {@code WebSocket} as a signal it may reclaim the
-         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
-         * this {@ode CompletionStage} has completed.
-         *
-         * @implSpec The default implementation of this method behaves as if:
-         *
-         * <pre>{@code
-         *     webSocket.request(1);
-         *     return null;
-         * }</pre>
-         *
-         * @param webSocket
-         *         the WebSocket on which the message has been received
-         * @param message
-         *         the message
-         * @param part
-         *         the part
-         *
-         * @return a {@code CompletionStage} which completes when the
-         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
-         * reclaimed immediately
-         */
-        default CompletionStage<?> onBinary(WebSocket webSocket,
-                                            ByteBuffer message,
-                                            MessagePart part) {
-            webSocket.request(1);
-            return null;
-        }
-
-        /**
-         * A Ping message has been received.
-         *
-         * <p> The message consists of not more than {@code 125} bytes from
-         * the buffer's position to its limit.
-         *
-         * <p> Return a {@code CompletionStage} which will be used by the
-         * {@code WebSocket} as a signal it may reclaim the
-         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
-         * this {@ode CompletionStage} has completed.
-         *
-         * @implSpec The default implementation of this method behaves as if:
-         *
-         * <pre>{@code
-         *     webSocket.request(1);
-         *     return null;
-         * }</pre>
-         *
-         * @param webSocket
-         *         the WebSocket on which the message has been received
-         * @param message
-         *         the message
-         *
-         * @return a {@code CompletionStage} which completes when the
-         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
-         * reclaimed immediately
-         */
-        default CompletionStage<?> onPing(WebSocket webSocket,
-                                          ByteBuffer message) {
-            webSocket.request(1);
-            return null;
-        }
-
-        /**
-         * A Pong message has been received.
-         *
-         * <p> The message consists of not more than {@code 125} bytes from
-         * the buffer's position to its limit.
-         *
-         * <p> Return a {@code CompletionStage} which will be used by the
-         * {@code WebSocket} as a signal it may reclaim the
-         * {@code ByteBuffer}. Do not access the {@code ByteBuffer} after
-         * this {@ode CompletionStage} has completed.
-         *
-         * @implSpec The default implementation of this method behaves as if:
-         *
-         * <pre>{@code
-         *     webSocket.request(1);
-         *     return null;
-         * }</pre>
-         *
-         * @param webSocket
-         *         the WebSocket on which the message has been received
-         * @param message
-         *         the message
-         *
-         * @return a {@code CompletionStage} which completes when the
-         * {@code ByteBuffer} may be reclaimed; or {@code null} if it may be
-         * reclaimed immediately
-         */
-        default CompletionStage<?> onPong(WebSocket webSocket,
-                                          ByteBuffer message) {
-            webSocket.request(1);
-            return null;
-        }
-
-        /**
-         * A Close message has been received.
-         *
-         * <p> This is the last invocation from the {@code WebSocket}. By the
-         * time this invocation begins the {@code WebSocket}'s input will have
-         * been closed. Be prepared to receive this invocation at any time after
-         * {@code onOpen} regardless of whether or not any messages have been
-         * requested from the {@code WebSocket}.
-         *
-         * <p> A Close message consists of a status code and a reason for
-         * closing. The status code is an integer from the range
-         * {@code 1000 <= code <= 65535}. The {@code reason} is a string which
-         * has an UTF-8 representation not longer than {@code 123} bytes.
-         *
-         * <p> Return a {@code CompletionStage} that will be used by the
-         * {@code WebSocket} as a signal that it may close the output. The
-         * {@code WebSocket} will close the output at the earliest of completion
-         * of the returned {@code CompletionStage} or invoking a
-         * {@link WebSocket#sendClose(int, String) sendClose} method.
-         *
-         * <p> If an exception is thrown from this method or a
-         * {@code CompletionStage} returned from it completes exceptionally,
-         * the resulting behaviour is undefined.
-         *
-         * @param webSocket
-         *         the WebSocket on which the message has been received
-         * @param statusCode
-         *         the status code
-         * @param reason
-         *         the reason
-         *
-         * @return a {@code CompletionStage} which completes when the
-         * {@code WebSocket} may be closed; or {@code null} if it may be
-         * closed immediately
-         */
-        default CompletionStage<?> onClose(WebSocket webSocket,
-                                           int statusCode,
-                                           String reason) {
-            return null;
-        }
-
-        /**
-         * An unrecoverable error has occurred.
-         *
-         * <p> This is the last invocation from the {@code WebSocket}. By the
-         * time this invocation begins both {@code WebSocket}'s input and output
-         * will have been closed. Be prepared to receive this invocation at any
-         * time after {@code onOpen} regardless of whether or not any messages
-         * have been requested from the {@code WebSocket}.
-         *
-         * <p> If an exception is thrown from this method, resulting behavior is
-         * undefined.
-         *
-         * @param webSocket
-         *         the WebSocket on which the error has occurred
-         * @param error
-         *         the error
-         */
-        default void onError(WebSocket webSocket, Throwable error) { }
-    }
-
-    /**
-     * A marker used by {@link WebSocket.Listener} for identifying partial
-     * messages.
-     * {@Incubating}
-     *
-     * @since 9
-     */
-    enum MessagePart {
-
-        /**
-         * The first part of a message.
-         */
-        FIRST,
-
-        /**
-         * A middle part of a message.
-         */
-        PART,
-
-        /**
-         * The last part of a message.
-         */
-        LAST,
-
-        /**
-         * A whole message consisting of a single part.
-         */
-        WHOLE
-    }
-
-    /**
-     * Sends a Text message with characters from the given {@code CharSequence}.
-     *
-     * <p> To send a Text message invoke this method only after the previous
-     * Text or Binary message has been sent. The character sequence must not be
-     * modified until the {@code CompletableFuture} returned from this method
-     * has completed.
-     *
-     * <p> A {@code CompletableFuture} returned from this method can
-     * complete exceptionally with:
-     * <ul>
-     * <li> {@link IllegalArgumentException} -
-     *          if {@code message} is a malformed UTF-16 sequence
-     * <li> {@link IllegalStateException} -
-     *          if {@code sendClose} has been invoked
-     *          or if the previous message has not been sent yet
-     *          or if a previous Binary message was sent with
-     *          {@code isLast == false}
-     * <li> {@link IOException} -
-     *          if an I/O error occurs
-     * </ul>
-     *
-     * @implNote If a partial UTF-16 sequence is passed to this method, a
-     * {@code CompletableFuture} returned will complete exceptionally with
-     * {@code IOException}.
-     *
-     * @param message
-     *         the message
-     * @param isLast
-     *         {@code true} if this is the last part of the message,
-     *         {@code false} otherwise
-     *
-     * @return a {@code CompletableFuture} that completes, with this
-     * {@code WebSocket}, when the message has been sent
-     */
-    CompletableFuture<WebSocket> sendText(CharSequence message, boolean isLast);
-
-    /**
-     * Sends a Binary message with bytes from the given {@code ByteBuffer}.
-     *
-     * <p> To send a Binary message invoke this method only after the previous
-     * Text or Binary message has been sent. The message consists of bytes from
-     * the buffer's position to its limit. Upon normal completion of a
-     * {@code CompletableFuture} returned from this method the buffer will have
-     * no remaining bytes. The buffer must not be accessed until after that.
-     *
-     * <p> The {@code CompletableFuture} returned from this method can
-     * complete exceptionally with:
-     * <ul>
-     * <li> {@link IllegalStateException} -
-     *          if {@code sendClose} has been invoked
-     *          or if the previous message has not been sent yet
-     *          or if a previous Text message was sent with
-     *              {@code isLast == false}
-     * <li> {@link IOException} -
-     *          if an I/O error occurs
-     * </ul>
-     *
-     * @param message
-     *         the message
-     * @param isLast
-     *         {@code true} if this is the last part of the message,
-     *         {@code false} otherwise
-     *
-     * @return a {@code CompletableFuture} that completes, with this
-     * {@code WebSocket}, when the message has been sent
-     */
-    CompletableFuture<WebSocket> sendBinary(ByteBuffer message, boolean isLast);
-
-    /**
-     * Sends a Ping message with bytes from the given {@code ByteBuffer}.
-     *
-     * <p> The message consists of not more than {@code 125} bytes from the
-     * buffer's position to its limit. Upon normal completion of a
-     * {@code CompletableFuture} returned from this method the buffer will
-     * have no remaining bytes. The buffer must not be accessed until after that.
-     *
-     * <p> The {@code CompletableFuture} returned from this method can
-     * complete exceptionally with:
-     * <ul>
-     * <li> {@link IllegalArgumentException} -
-     *          if the message is too long
-     * <li> {@link IllegalStateException} -
-     *          if {@code sendClose} has been invoked
-     * <li> {@link IOException} -
-     *          if an I/O error occurs
-     * </ul>
-     *
-     * @param message
-     *         the message
-     *
-     * @return a {@code CompletableFuture} that completes, with this
-     * {@code WebSocket}, when the Ping message has been sent
-     */
-    CompletableFuture<WebSocket> sendPing(ByteBuffer message);
-
-    /**
-     * Sends a Pong message with bytes from the given {@code ByteBuffer}.
-     *
-     * <p> The message consists of not more than {@code 125} bytes from the
-     * buffer's position to its limit. Upon normal completion of a
-     * {@code CompletableFuture} returned from this method the buffer will have
-     * no remaining bytes. The buffer must not be accessed until after that.
-     *
-     * <p> The {@code CompletableFuture} returned from this method can
-     * complete exceptionally with:
-     * <ul>
-     * <li> {@link IllegalArgumentException} -
-     *          if the message is too long
-     * <li> {@link IllegalStateException} -
-     *          if {@code sendClose} has been invoked
-     * <li> {@link IOException} -
-     *          if an I/O error occurs
-     * </ul>
-     *
-     * @param message
-     *         the message
-     *
-     * @return a {@code CompletableFuture} that completes, with this
-     * {@code WebSocket}, when the Pong message has been sent
-     */
-    CompletableFuture<WebSocket> sendPong(ByteBuffer message);
-
-    /**
-     * Sends a Close message with the given status code and the reason,
-     * initiating an orderly closure.
-     *
-     * <p> When this method returns the output will have been closed.
-     *
-     * <p> The {@code statusCode} is an integer from the range
-     * {@code 1000 <= code <= 4999}. Status codes {@code 1002}, {@code 1003},
-     * {@code 1006}, {@code 1007}, {@code 1009}, {@code 1010}, {@code 1012},
-     * {@code 1013} and {@code 1015} are illegal. Behaviour in respect to other
-     * status codes is implementation-specific. The {@code reason} is a string
-     * that has an UTF-8 representation not longer than {@code 123} bytes.
-     *
-     * <p> Use the provided integer constant {@link #NORMAL_CLOSURE} as a status
-     * code and an empty string as a reason in a typical case.
-     *
-     * <p> A {@code CompletableFuture} returned from this method can
-     * complete exceptionally with:
-     * <ul>
-     * <li> {@link IllegalArgumentException} -
-     *          if {@code statusCode} or {@code reason} are illegal
-     * <li> {@link IOException} -
-     *          if an I/O error occurs
-     * </ul>
-     *
-     * @implSpec An endpoint sending a Close message might not receive a
-     * complementing Close message in a timely manner for a variety of reasons.
-     * The {@code WebSocket} implementation is responsible for providing a
-     * closure mechanism that guarantees that once {@code sendClose} method has
-     * been invoked the {@code WebSocket} will close regardless of whether or
-     * not a Close frame has been received and without further intervention from
-     * the user of this API. Method {@code sendClose} is designed to be,
-     * possibly, the last call from the user of this API.
-     *
-     * @param statusCode
-     *         the status code
-     * @param reason
-     *         the reason
-     *
-     * @return a {@code CompletableFuture} that completes, with this
-     * {@code WebSocket}, when the Close message has been sent
-     */
-    CompletableFuture<WebSocket> sendClose(int statusCode, String reason);
-
-    /**
-     * Requests {@code n} more messages from this {@code WebSocket}.
-     *
-     * <p> This {@code WebSocket} will invoke its listener's {@code onText},
-     * {@code onBinary}, {@code onPing}, {@code onPong} or {@code onClose}
-     * methods up to {@code n} more times.
-     *
-     * <p> This method may be invoked at any time.
-     *
-     * @param n
-     *         the number of messages requested
-     *
-     * @throws IllegalArgumentException
-     *         if {@code n <= 0}
-     */
-    void request(long n);
-
-    /**
-     * Returns the subprotocol for this {@code WebSocket}.
-     *
-     * <p> This method may be invoked at any time.
-     *
-     * @return the subprotocol for this {@code WebSocket}, or an empty
-     * {@code String} if there's no subprotocol
-     */
-    String getSubprotocol();
-
-    /**
-     * Tells whether or not this {@code WebSocket} is permanently closed
-     * for sending messages.
-     *
-     * <p> If this method returns {@code true}, subsequent invocations will also
-     * return {@code true}. This method may be invoked at any time.
-     *
-     * @return {@code true} if closed, {@code false} otherwise
-     */
-    boolean isOutputClosed();
-
-    /**
-     * Tells whether or not this {@code WebSocket} is permanently closed
-     * for receiving messages.
-     *
-     * <p> If this method returns {@code true}, subsequent invocations will also
-     * return {@code true}. This method may be invoked at any time.
-     *
-     * @return {@code true} if closed, {@code false} otherwise
-     */
-    boolean isInputClosed();
-
-    /**
-     * Closes this {@code WebSocket} abruptly.
-     *
-     * <p> When this method returns both the input and output will have been
-     * closed. This method may be invoked at any time. Subsequent invocations
-     * have no effect.
-     *
-     * @apiNote Depending on its implementation, the state (for example, whether
-     * or not a message is being transferred at the moment) and possible errors
-     * while releasing associated resources, this {@code WebSocket} may invoke
-     * its listener's {@code onError}.
-     */
-    void abort();
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WebSocketHandshakeException.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-
-/**
- * An exception used to signal the opening handshake failed.
- * {@Incubating}
- *
- * @since 9
- */
-public final class WebSocketHandshakeException extends IOException {
-
-    private static final long serialVersionUID = 1L;
-
-    private final transient HttpResponse<?> response;
-
-    public WebSocketHandshakeException(HttpResponse<?> response) {
-        this.response = response;
-    }
-
-    /**
-     * Returns the server's counterpart of the opening handshake.
-     *
-     * <p> The value may be unavailable ({@code null}) if this exception has
-     * been serialized and then read back in.
-     *
-     * @return server response
-     */
-    public HttpResponse<?> getResponse() {
-        return response;
-    }
-
-    @Override
-    public WebSocketHandshakeException initCause(Throwable cause) {
-        return (WebSocketHandshakeException) super.initCause(cause);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowController.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,320 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.lang.System.Logger.Level;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
-import jdk.incubator.http.internal.common.Utils;
-
-/**
- * A Send Window Flow-Controller that is used to control outgoing Connection
- * and Stream flows, per HTTP/2 connection.
- *
- * A Http2Connection has its own unique single instance of a WindowController
- * that it shares with its Streams. Each stream must acquire the appropriate
- * amount of Send Window from the controller before sending data.
- *
- * WINDOW_UPDATE frames, both connection and stream specific, must notify the
- * controller of their increments. SETTINGS frame's INITIAL_WINDOW_SIZE must
- * notify the controller so that it can adjust the active stream's window size.
- */
-final class WindowController {
-
-    static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary developer's flag
-    static final System.Logger DEBUG_LOGGER =
-            Utils.getDebugLogger("WindowController"::toString, DEBUG);
-
-    /**
-     * Default initial connection Flow-Control Send Window size, as per HTTP/2.
-     */
-    private static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024 - 1;
-
-    /** The connection Send Window size. */
-    private int connectionWindowSize;
-    /** A Map of the active streams, where the key is the stream id, and the
-     *  value is the stream's Send Window size, which may be negative. */
-    private final Map<Integer,Integer> streams = new HashMap<>();
-    /** A Map of streams awaiting Send Window. The key is the stream id. The
-     * value is a pair of the Stream ( representing the key's stream id ) and
-     * the requested amount of send Window. */
-    private final Map<Integer, Map.Entry<Stream<?>, Integer>> pending
-            = new LinkedHashMap<>();
-
-    private final ReentrantLock controllerLock = new ReentrantLock();
-
-    /** A Controller with the default initial window size. */
-    WindowController() {
-        connectionWindowSize = DEFAULT_INITIAL_WINDOW_SIZE;
-    }
-
-//    /** A Controller with the given initial window size. */
-//    WindowController(int initialConnectionWindowSize) {
-//        connectionWindowSize = initialConnectionWindowSize;
-//    }
-
-    /** Registers the given stream with this controller. */
-    void registerStream(int streamid, int initialStreamWindowSize) {
-        controllerLock.lock();
-        try {
-            Integer old = streams.put(streamid, initialStreamWindowSize);
-            if (old != null)
-                throw new InternalError("Unexpected entry ["
-                        + old + "] for streamid: " + streamid);
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /** Removes/De-registers the given stream with this controller. */
-    void removeStream(int streamid) {
-        controllerLock.lock();
-        try {
-            Integer old = streams.remove(streamid);
-            // Odd stream numbers (client streams) should have been registered.
-            // Even stream numbers (server streams - aka Push Streams) should
-            // not be registered
-            final boolean isClientStream = (streamid % 2) == 1;
-            if (old == null && isClientStream) {
-                throw new InternalError("Expected entry for streamid: " + streamid);
-            } else if (old != null && !isClientStream) {
-                throw new InternalError("Unexpected entry for streamid: " + streamid);
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /**
-     * Attempts to acquire the requested amount of Send Window for the given
-     * stream.
-     *
-     * The actual amount of Send Window available may differ from the requested
-     * amount. The actual amount, returned by this method, is the minimum of,
-     * 1) the requested amount, 2) the stream's Send Window, and 3) the
-     * connection's Send Window.
-     *
-     * A negative or zero value is returned if there's no window available.
-     * When the result is negative or zero, this method arranges for the
-     * given stream's {@link Stream#signalWindowUpdate()} method to be invoke at
-     * a later time when the connection and/or stream window's have been
-     * increased. The {@code tryAcquire} method should then be invoked again to
-     * attempt to acquire the available window.
-     */
-    int tryAcquire(int requestAmount, int streamid, Stream<?> stream) {
-        controllerLock.lock();
-        try {
-            Integer streamSize = streams.get(streamid);
-            if (streamSize == null)
-                throw new InternalError("Expected entry for streamid: "
-                                        + streamid);
-            int x = Math.min(requestAmount,
-                             Math.min(streamSize, connectionWindowSize));
-
-            if (x <= 0)  { // stream window size may be negative
-                DEBUG_LOGGER.log(Level.DEBUG,
-                        "Stream %d requesting %d but only %d available (stream: %d, connection: %d)",
-                      streamid, requestAmount, Math.min(streamSize, connectionWindowSize),
-                      streamSize, connectionWindowSize);
-                // If there's not enough window size available, put the
-                // caller in a pending list.
-                pending.put(streamid, Map.entry(stream, requestAmount));
-                return x;
-            }
-
-            // Remove the caller from the pending list ( if was waiting ).
-            pending.remove(streamid);
-
-            // Update window sizes and return the allocated amount to the caller.
-            streamSize -= x;
-            streams.put(streamid, streamSize);
-            connectionWindowSize -= x;
-            DEBUG_LOGGER.log(Level.DEBUG,
-                  "Stream %d amount allocated %d, now %d available (stream: %d, connection: %d)",
-                  streamid, x, Math.min(streamSize, connectionWindowSize),
-                  streamSize, connectionWindowSize);
-            return x;
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /**
-     * Increases the Send Window size for the connection.
-     *
-     * A number of awaiting requesters, from unfulfilled tryAcquire requests,
-     * may have their stream's {@link Stream#signalWindowUpdate()} method
-     * scheduled to run ( i.e. awake awaiters ).
-     *
-     * @return false if, and only if, the addition of the given amount would
-     *         cause the Send Window to exceed 2^31-1 (overflow), otherwise true
-     */
-    boolean increaseConnectionWindow(int amount) {
-        List<Stream<?>> candidates = null;
-        controllerLock.lock();
-        try {
-            int size = connectionWindowSize;
-            size += amount;
-            if (size < 0)
-                return false;
-            connectionWindowSize = size;
-            DEBUG_LOGGER.log(Level.DEBUG, "Connection window size is now %d", size);
-
-            // Notify waiting streams, until the new increased window size is
-            // effectively exhausted.
-            Iterator<Map.Entry<Integer,Map.Entry<Stream<?>,Integer>>> iter =
-                    pending.entrySet().iterator();
-
-            while (iter.hasNext() && size > 0) {
-                Map.Entry<Integer,Map.Entry<Stream<?>,Integer>> item = iter.next();
-                Integer streamSize = streams.get(item.getKey());
-                if (streamSize == null) {
-                    iter.remove();
-                } else {
-                    Map.Entry<Stream<?>,Integer> e = item.getValue();
-                    int requestedAmount = e.getValue();
-                    // only wakes up the pending streams for which there is
-                    // at least 1 byte of space in both windows
-                    int minAmount = 1;
-                    if (size >= minAmount && streamSize >= minAmount) {
-                        size -= Math.min(streamSize, requestedAmount);
-                        iter.remove();
-                        if (candidates == null)
-                            candidates = new ArrayList<>();
-                        candidates.add(e.getKey());
-                    }
-                }
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-        if (candidates != null) {
-            candidates.forEach(Stream::signalWindowUpdate);
-        }
-        return true;
-    }
-
-    /**
-     * Increases the Send Window size for the given stream.
-     *
-     * If the given stream is awaiting window size, from an unfulfilled
-     * tryAcquire request, it will have its stream's {@link
-     * Stream#signalWindowUpdate()} method scheduled to run ( i.e. awoken ).
-     *
-     * @return false if, and only if, the addition of the given amount would
-     *         cause the Send Window to exceed 2^31-1 (overflow), otherwise true
-     */
-    boolean increaseStreamWindow(int amount, int streamid) {
-        Stream<?> s = null;
-        controllerLock.lock();
-        try {
-            Integer size = streams.get(streamid);
-            if (size == null)
-                throw new InternalError("Expected entry for streamid: " + streamid);
-            size += amount;
-            if (size < 0)
-                return false;
-            streams.put(streamid, size);
-            DEBUG_LOGGER.log(Level.DEBUG,
-                             "Stream %s window size is now %s", streamid, size);
-
-            Map.Entry<Stream<?>,Integer> p = pending.get(streamid);
-            if (p != null) {
-                int minAmount = 1;
-                // only wakes up the pending stream if there is at least
-                // 1 byte of space in both windows
-                if (size >= minAmount
-                        && connectionWindowSize >= minAmount) {
-                     pending.remove(streamid);
-                     s = p.getKey();
-                }
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-
-        if (s != null)
-            s.signalWindowUpdate();
-
-        return true;
-    }
-
-    /**
-     * Adjusts, either increases or decreases, the active streams registered
-     * with this controller.  May result in a stream's Send Window size becoming
-     * negative.
-     */
-    void adjustActiveStreams(int adjustAmount) {
-        assert adjustAmount != 0;
-
-        controllerLock.lock();
-        try {
-            for (Map.Entry<Integer,Integer> entry : streams.entrySet()) {
-                int streamid = entry.getKey();
-                // the API only supports sending on Streams initialed by
-                // the client, i.e. odd stream numbers
-                if (streamid != 0 && (streamid % 2) != 0) {
-                    Integer size = entry.getValue();
-                    size += adjustAmount;
-                    streams.put(streamid, size);
-                    DEBUG_LOGGER.log(Level.DEBUG,
-                        "Stream %s window size is now %s", streamid, size);
-                }
-            }
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-    /** Returns the Send Window size for the connection. */
-    int connectionWindowSize() {
-        controllerLock.lock();
-        try {
-            return connectionWindowSize;
-        } finally {
-            controllerLock.unlock();
-        }
-    }
-
-//    /** Returns the Send Window size for the given stream. */
-//    int streamWindowSize(int streamid) {
-//        controllerLock.lock();
-//        try {
-//            Integer size = streams.get(streamid);
-//            if (size == null)
-//                throw new InternalError("Expected entry for streamid: " + streamid);
-//            return size;
-//        } finally {
-//            controllerLock.unlock();
-//        }
-//    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/WindowUpdateSender.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http;
-
-import java.lang.System.Logger.Level;
-import jdk.incubator.http.internal.frame.SettingsFrame;
-import jdk.incubator.http.internal.frame.WindowUpdateFrame;
-import jdk.incubator.http.internal.common.Utils;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-abstract class WindowUpdateSender {
-
-    final static boolean DEBUG = Utils.DEBUG;
-    final System.Logger debug =
-            Utils.getDebugLogger(this::dbgString, DEBUG);
-
-    final int limit;
-    final Http2Connection connection;
-    final AtomicInteger received = new AtomicInteger(0);
-
-    WindowUpdateSender(Http2Connection connection) {
-        this(connection, connection.clientSettings.getParameter(SettingsFrame.INITIAL_WINDOW_SIZE));
-    }
-
-    WindowUpdateSender(Http2Connection connection, int initWindowSize) {
-        this(connection, connection.getMaxReceiveFrameSize(), initWindowSize);
-    }
-
-    WindowUpdateSender(Http2Connection connection, int maxFrameSize, int initWindowSize) {
-        this.connection = connection;
-        int v0 = Math.max(0, initWindowSize - maxFrameSize);
-        int v1 = (initWindowSize + (maxFrameSize - 1)) / maxFrameSize;
-        v1 = v1 * maxFrameSize / 2;
-        // send WindowUpdate heuristic:
-        // - we got data near half of window size
-        //   or
-        // - remaining window size reached max frame size.
-        limit = Math.min(v0, v1);
-        debug.log(Level.DEBUG, "maxFrameSize=%d, initWindowSize=%d, limit=%d",
-                maxFrameSize, initWindowSize, limit);
-    }
-
-    abstract int getStreamId();
-
-    void update(int delta) {
-        debug.log(Level.DEBUG, "update: %d", delta);
-        if (received.addAndGet(delta) > limit) {
-            synchronized (this) {
-                int tosend = received.get();
-                if( tosend > limit) {
-                    received.getAndAdd(-tosend);
-                    sendWindowUpdate(tosend);
-                }
-            }
-        }
-    }
-
-    void sendWindowUpdate(int delta) {
-        debug.log(Level.DEBUG, "sending window update: %d", delta);
-        connection.sendUnorderedFrame(new WindowUpdateFrame(getStreamId(), delta));
-    }
-
-    String dbgString() {
-        return "WindowUpdateSender(stream: " + getStreamId() + ")";
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/ByteBufferPool.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/ByteBufferReference.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/ConnectionExpiredException.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/DebugLogger.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,251 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Demand.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/FlowTube.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/HttpHeadersImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.common;
-
-import jdk.incubator.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();
-        Set<String> keys = headers.keySet();
-        for (String key : keys) {
-            List<String> vals = headers.get(key);
-            List<String> vals1 = new ArrayList<>(vals);
-            h1.headers.put(key, vals1);
-        }
-        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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Log.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,302 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.common;
-
-import jdk.incubator.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.incubator.http.internal.frame.DataFrame;
-import jdk.incubator.http.internal.frame.Http2Frame;
-import jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/MinimalFuture.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,174 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.common;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-
-import static java.util.Objects.requireNonNull;
-import java.util.concurrent.atomic.AtomicLong;
-
-/*
- * 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;
-    }
-
-    final static AtomicLong TOKENS = new AtomicLong();
-    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 static <U> CompletableFuture<U> supply(ExceptionalSupplier<U> supplier, Executor executor) {
-        CompletableFuture<U> cf = new MinimalFuture<>();
-        cf.completeAsync( () -> {
-            try {
-                return supplier.get();
-            } catch (Throwable ex) {
-                throw new CompletionException(ex);
-            }
-        }, executor);
-        return cf;
-    }
-
-    public MinimalFuture() {
-        super();
-        this.id = TOKENS.incrementAndGet();
-    }
-
-    /**
-     * Creates a defensive copy of the given {@code CompletionStage}.
-     *
-     * <p> Might be useful both for producers and consumers of {@code
-     * CompletionStage}s.
-     *
-     * <p> Producers are protected from possible uncontrolled modifications
-     * (cancellation, completion, obtrusion, etc.) as well as from executing
-     * unknown potentially lengthy or faulty dependants in the given {@code
-     * CompletionStage}'s default execution facility or synchronously.
-     *
-     * <p> Consumers are protected from some of the aspects of misbehaving
-     * implementations (e.g. accepting results, applying functions, running
-     * tasks, etc. more than once or escape of a reference to their private
-     * executor, etc.) by providing a reliable proxy they use instead.
-     *
-     * @param src
-     *         the {@code CompletionStage} to make a copy from
-     * @param executor
-     *         the executor used to propagate the completion
-     * @param <T>
-     *         the type of the {@code CompletionStage}'s result
-     *
-     * @return a copy of the given stage
-     */
-    public static <T> MinimalFuture<T> copy(CompletionStage<T> src,
-                                            Executor executor) {
-        MinimalFuture<T> copy = new MinimalFuture<>();
-        BiConsumer<T, Throwable> relay =
-                (result, error) -> {
-                    if (error != null) {
-                        copy.completeExceptionally(error);
-                    } else {
-                        copy.complete(result);
-                    }
-                };
-
-        if (src.getClass() == CompletableFuture.class) {
-            // No subclasses! Strictly genuine CompletableFuture.
-            src.whenCompleteAsync(relay, executor);
-            return copy;
-        } else {
-            // Don't give our executor away to an unknown CS!
-            src.whenComplete(relay);
-            return (MinimalFuture<T>)
-                    copy.thenApplyAsync(Function.identity(), executor);
-        }
-    }
-
-    public static <U> MinimalFuture<U> newMinimalFuture() {
-        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 = newMinimalFuture();
-        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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Pair.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SSLFlowDelegate.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,903 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.common;
-
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-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 jdk.incubator.http.internal.common.SubscriberWrapper.SchedulingAction;
-
-/**
- * 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
-    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);
-        this.cf = CompletableFuture.allOf(reader.completion(), writer.completion())
-                                   .thenRun(this::normalStop);
-        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) {
-                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(() -> {
-            handshakeState.getAndUpdate((current) -> current | DOING_TASKS);
-            List<Runnable> nextTasks = tasks;
-            do {
-                try {
-                    nextTasks.forEach((r) -> {
-                        r.run();
-                    });
-                    if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
-                        nextTasks = obtainTasks();
-                    } else break;
-                } catch (Throwable t) {
-                    handleError(t);
-                }
-            } while(true);
-            handshakeState.getAndUpdate((current) -> current & ~DOING_TASKS);
-            writer.addData(HS_TRIGGER);
-            resumeActivity();
-        });
-    }
-
-
-    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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SSLTube.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,586 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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 jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SequentialScheduler.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,364 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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 complete restartable task is one which is simple and self-contained.
-     * It 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 RestartableTask that runs its main loop within a
-     * synchronized block to place a memory barrier around it.
-     * Because the main loop can't run concurrently in two treads,
-     * then 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;
-
-    /**
-     * A simple task that can be pushed on an executor to execute
-     * {@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);
-    }
-
-    /**
-     * Runs or schedules the task to be run 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 deferOrSchedule(null)} is strictly equivalent to calling
-     * {@code runOrSchedule()}.
-     */
-    public void deferOrSchedule(Executor executor) { // TODO: why this name? why not runOrSchedule?
-        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 do 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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SubscriberWrapper.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,460 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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;
-        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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SubscriptionBase.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,660 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.common;
-
-import jdk.incubator.http.HttpHeaders;
-import sun.net.NetProperties;
-import sun.net.util.IPAddressUtil;
-
-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.List;
-import java.util.Set;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-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 = Set.of(
-            "authorization", "connection", "cookie", "content-length",
-            "date", "expect", "from", "host", "origin", "proxy-authorization",
-            "referer", "user-agent", "upgrade", "via", "warning");
-
-    public static final Predicate<String>
-        ALLOWED_HEADERS = header -> !Utils.DISALLOWED_HEADERS_SET.contains(header);
-
-    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();
-        return cause == null ? x : 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 encoding = headers.firstValue("Content-encoding")
-                .orElse("UTF_8");
-        try {
-            return Charset.forName(encoding);
-        } catch (IllegalArgumentException e) {
-            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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/ContinuationFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/DataFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.frame;
-
-import jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/ErrorFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/FramesDecoder.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http.internal.frame;
-
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.Utils;
-
-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.util.Queue;
-
-/**
- * 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);
-                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));
-        }
-        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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/FramesEncoder.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,293 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/GoAwayFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.frame;
-
-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;
-    }
-
-    @Override
-    public int type() {
-        return TYPE;
-    }
-
-    @Override
-    int length() {
-        return 8 + debugData.length;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + " Debugdata: " + new String(debugData);
-    }
-
-    public int getLastStream() {
-        return this.lastStream;
-    }
-
-    public byte[] getDebugData() {
-        return debugData;
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/HeaderFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.frame;
-
-import jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/HeadersFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/Http2Frame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/MalformedFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/OutgoingHeaders.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.frame;
-
-import jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/PingFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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;
-    }
-
-    @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;
-    }
-
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/PriorityFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/PushPromiseFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/ResetFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/SettingsFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/frame/WindowUpdateFrame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/BinaryRepresentationWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.hpack;
-
-import java.nio.ByteBuffer;
-
-interface BinaryRepresentationWriter {
-
-    boolean write(HeaderTable table, ByteBuffer destination);
-
-    BinaryRepresentationWriter reset();
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/BulkSizeUpdateWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/Decoder.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,594 +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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.hpack;
-
-import jdk.incubator.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 jdk.incubator.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/DecodingCallback.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,295 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/Encoder.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,525 +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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.hpack;
-
-import jdk.incubator.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 jdk.incubator.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/HPACK.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,174 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.hpack;
-
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.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 jdk.incubator.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static jdk.incubator.http.internal.hpack.HPACK.Logger.Level.NONE;
-import static jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/HeaderTable.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,546 +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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.hpack;
-
-import jdk.incubator.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 jdk.incubator.http.internal.hpack.HPACK.Logger.Level.EXTRA;
-import static jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/Huffman.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,681 +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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/ISO_8859_1.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IndexNameValueWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IndexedWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IntegerReader.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/IntegerWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/LiteralNeverIndexedWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.hpack;
-
-final class LiteralNeverIndexedWriter extends IndexNameValueWriter {
-
-    LiteralNeverIndexedWriter() {
-        super(0b0001_0000, 4);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/LiteralWithIndexingWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/LiteralWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.internal.hpack;
-
-final class LiteralWriter extends IndexNameValueWriter {
-
-    LiteralWriter() {
-        super(0b0000_0000, 4);
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/SizeUpdateWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/StringReader.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/StringWriter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/package-info.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-/**
- * 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.incubator.http.internal.hpack.Decoder}
- * and {@link jdk.incubator.http.internal.hpack.Encoder} respectively.
- *
- * <p> Instances of these classes are not safe for use by multiple threads.
- */
-package jdk.incubator.http.internal.hpack;
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/BuilderImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.WebSocket;
-import jdk.incubator.http.WebSocket.Builder;
-import jdk.incubator.http.WebSocket.Listener;
-import jdk.incubator.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 jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/CheckFailedException.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/FailWebSocketException.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import static jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Frame.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,499 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.internal.vm.annotation.Stable;
-
-import java.nio.ByteBuffer;
-
-import static jdk.incubator.http.internal.common.Utils.dump;
-import static jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/FrameConsumer.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,269 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.WebSocket.MessagePart;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.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 jdk.incubator.http.internal.common.Utils.dump;
-import static jdk.incubator.http.internal.websocket.StatusCodes.NO_STATUS_CODE;
-import static jdk.incubator.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 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) {
-        Log.logTrace("Reading fin: {0}", value);
-        fin = value;
-    }
-
-    @Override
-    public void rsv1(boolean value) {
-        Log.logTrace("Reading rsv1: {0}", value);
-        if (value) {
-            throw new FailWebSocketException("Unexpected rsv1 bit");
-        }
-    }
-
-    @Override
-    public void rsv2(boolean value) {
-        Log.logTrace("Reading rsv2: {0}", value);
-        if (value) {
-            throw new FailWebSocketException("Unexpected rsv2 bit");
-        }
-    }
-
-    @Override
-    public void rsv3(boolean value) {
-        Log.logTrace("Reading rsv3: {0}", value);
-        if (value) {
-            throw new FailWebSocketException("Unexpected rsv3 bit");
-        }
-    }
-
-    @Override
-    public void opcode(Opcode v) {
-        Log.logTrace("Reading opcode: {0}", 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("Unknown opcode " + v);
-        }
-    }
-
-    @Override
-    public void mask(boolean value) {
-        Log.logTrace("Reading mask: {0}", value);
-        if (value) {
-            throw new FailWebSocketException("Masked frame received");
-        }
-    }
-
-    @Override
-    public void payloadLen(long value) {
-        Log.logTrace("Reading payloadLen: {0}", 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) {
-        Log.logTrace("Reading payloadData: data={0}", 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 (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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/MessageStreamConsumer.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/OpeningHandshake.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,392 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpClient.Version;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.WebSocketHandshakeException;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Pair;
-import jdk.incubator.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 jdk.incubator.http.internal.common.Utils.isValidName;
-import static jdk.incubator.http.internal.common.Utils.permissionForProxy;
-import static jdk.incubator.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.<Void>discard(null))
-                      .thenCompose(this::resultFrom);
-        return AccessController.doPrivileged(pa);
-    }
-
-    /*
-     * The result of the opening handshake.
-     */
-    static final class Result {
-
-        final String subprotocol;
-        final TransportSupplier transport;
-
-        private Result(String subprotocol, TransportSupplier 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 TransportSupplier(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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/OutgoingMessage.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,296 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.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 jdk.incubator.http.internal.common.Utils.EMPTY_BYTEBUFFER;
-import static jdk.incubator.http.internal.websocket.Frame.MAX_HEADER_SIZE_BYTES;
-import static jdk.incubator.http.internal.websocket.Frame.Opcode.BINARY;
-import static jdk.incubator.http.internal.websocket.Frame.Opcode.CLOSE;
-import static jdk.incubator.http.internal.websocket.Frame.Opcode.CONTINUATION;
-import static jdk.incubator.http.internal.websocket.Frame.Opcode.PING;
-import static jdk.incubator.http.internal.websocket.Frame.Opcode.PONG;
-import static jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/RawChannel.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Receiver.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-
-/*
- * Receives incoming data from the channel on demand and converts it into a
- * stream of WebSocket messages which are then delivered to the supplied message
- * consumer in a strict sequential order and non-recursively. In other words,
- *
- *     onText()
- *     onText()
- *     onBinary()
- *     ...
- *
- * instead of
- *
- *     onText()
- *       onText()
- *         onBinary()
- *     ...
- *
- * even if `request(long n)` is called from inside these invocations.
- */
-public class Receiver {
-
-    private final MessageStreamConsumer messageConsumer;
-    private final RawChannel channel;
-    private final FrameConsumer frameConsumer;
-    private final Frame.Reader reader = new Frame.Reader();
-    private final RawChannel.RawEvent event = createHandler();
-    protected final Demand demand = new Demand(); /* Exposed for testing purposes */
-    private final SequentialScheduler pushScheduler;
-
-    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;
-
-    public Receiver(MessageStreamConsumer messageConsumer, RawChannel channel) {
-        this.messageConsumer = messageConsumer;
-        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 `handler` invokes `pushContinuously`
-        // the following assignment is done last:
-        pushScheduler = createScheduler();
-    }
-
-    /* Exposed for testing purposes */
-    protected SequentialScheduler createScheduler() {
-        return new SequentialScheduler(new PushContinuouslyTask());
-    }
-
-    private RawChannel.RawEvent createHandler() {
-        return new RawChannel.RawEvent() {
-
-            @Override
-            public int interestOps() {
-                return SelectionKey.OP_READ;
-            }
-
-            @Override
-            public void handle() {
-                state = AVAILABLE;
-                pushScheduler.runOrSchedule();
-            }
-        };
-    }
-
-    public void request(long n) {
-        if (demand.increase(n)) {
-            pushScheduler.runOrSchedule();
-        }
-    }
-
-    /*
-     * 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 acknowledge() {
-        long x = demand.decreaseAndGet(1);
-        if (x < 0) {
-            throw new InternalError(String.valueOf(x));
-        }
-    }
-
-    /*
-     * Stops the machinery from reading and delivering messages permanently,
-     * regardless of the current demand and data availability.
-     */
-    public void close() throws IOException {
-        pushScheduler.stop();
-        channel.shutdownInput();
-    }
-
-    private class PushContinuouslyTask
-        extends SequentialScheduler.CompleteRestartableTask
-    {
-        @Override
-        public void run() {
-            while (!pushScheduler.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) {
-                            pushScheduler.stop();
-                            messageConsumer.onError(e);
-                        }
-                        continue;
-                    }
-                    break;
-                }
-                switch (state) {
-                    case WAITING:
-                        return;
-                    case UNREGISTERED:
-                        try {
-                            state = WAITING;
-                            channel.registerEvent(event);
-                        } catch (Throwable e) {
-                            pushScheduler.stop();
-                            messageConsumer.onError(e);
-                        }
-                        return;
-                    case AVAILABLE:
-                        try {
-                            data = channel.read();
-                        } catch (Throwable e) {
-                            pushScheduler.stop();
-                            messageConsumer.onError(e);
-                            return;
-                        }
-                        if (data == null) { // EOF
-                            pushScheduler.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));
-                }
-            }
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/StatusCodes.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/Transmitter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import java.io.IOException;
-import java.nio.channels.SelectionKey;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import static java.util.Objects.requireNonNull;
-
-/*
- * Sends messages one at a time, in an asynchronous and non-blocking fashion.
- *
- * No matter whether the message has been fully sent or an error has occurred,
- * the transmitter reports the outcome to the supplied handler and becomes ready
- * to accept a new message. Until then, the transmitter is considered "busy" and
- * an IllegalStateException will be thrown on each attempt to invoke send.
- */
-public class Transmitter {
-
-    /* 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 event;
-
-    public Transmitter(RawChannel channel) {
-        this.channel = channel;
-        this.event = createHandler();
-    }
-
-    /**
-     * 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);
-    }
-
-    public void close() throws IOException {
-        channel.shutdownOutput();
-    }
-
-    private RawChannel.RawEvent createHandler() {
-        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(event);
-                } catch (IOException e) {
-                    this.message = null;
-                    this.completionHandler = null;
-                    busy.set(false);
-                    handler.accept(e);
-                }
-            }
-        } catch (IOException e) {
-            busy.set(false);
-            handler.accept(e);
-        }
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/TransportSupplier.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import java.io.IOException;
-
-/*
- * Abstracts out I/O channel for the WebSocket implementation. The latter then
- * deals with input and output streams of messages and does not have to
- * understand the state machine of channels (e.g. how exactly they are closed).
- * Mocking this type will allow testing WebSocket message exchange in isolation.
- */
-public class TransportSupplier {
-
-    protected final RawChannel channel; /* Exposed for testing purposes */
-    private final Object lock = new Object();
-    private Transmitter transmitter;
-    private Receiver receiver;
-    private boolean receiverShutdown;
-    private boolean transmitterShutdown;
-    private boolean closed;
-
-    public TransportSupplier(RawChannel channel) {
-        this.channel = channel;
-    }
-
-    public Receiver receiver(MessageStreamConsumer consumer) {
-        synchronized (lock) {
-            if (receiver == null) {
-                receiver = newReceiver(consumer);
-            }
-            return receiver;
-        }
-    }
-
-    public Transmitter transmitter() {
-        synchronized (lock) {
-            if (transmitter == null) {
-                transmitter = newTransmitter();
-            }
-            return transmitter;
-        }
-    }
-
-    protected Receiver newReceiver(MessageStreamConsumer consumer) {
-        return new Receiver(consumer, channel) {
-            @Override
-            public void close() throws IOException {
-                synchronized (lock) {
-                    if (!closed) {
-                        try {
-                            super.close();
-                        } finally {
-                            receiverShutdown = true;
-                            if (transmitterShutdown) {
-                                closed = true;
-                                channel.close();
-                            }
-                        }
-                    }
-                }
-            }
-        };
-    }
-
-    protected Transmitter newTransmitter() {
-        return new Transmitter(channel) {
-            @Override
-            public void close() throws IOException {
-                synchronized (lock) {
-                    if (!closed) {
-                        try {
-                            super.close();
-                        } finally {
-                            transmitterShutdown = true;
-                            if (receiverShutdown) {
-                                closed = true;
-                                channel.close();
-                            }
-                        }
-                    }
-                }
-            }
-        };
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/UTF8AccumulatingDecoder.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.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 jdk.incubator.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/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,605 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.WebSocket;
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.Log;
-import jdk.incubator.http.internal.common.MinimalFuture;
-import jdk.incubator.http.internal.common.Pair;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.websocket.OpeningHandshake.Result;
-import jdk.incubator.http.internal.websocket.OutgoingMessage.Binary;
-import jdk.incubator.http.internal.websocket.OutgoingMessage.Close;
-import jdk.incubator.http.internal.websocket.OutgoingMessage.Context;
-import jdk.incubator.http.internal.websocket.OutgoingMessage.Ping;
-import jdk.incubator.http.internal.websocket.OutgoingMessage.Pong;
-import jdk.incubator.http.internal.websocket.OutgoingMessage.Text;
-
-import java.io.IOException;
-import java.lang.ref.Reference;
-import java.net.ProtocolException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Queue;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import static java.util.Objects.requireNonNull;
-import static jdk.incubator.http.internal.common.MinimalFuture.failedFuture;
-import static jdk.incubator.http.internal.common.Pair.pair;
-import static jdk.incubator.http.internal.websocket.StatusCodes.CLOSED_ABNORMALLY;
-import static jdk.incubator.http.internal.websocket.StatusCodes.NO_STATUS_CODE;
-import static jdk.incubator.http.internal.websocket.StatusCodes.isLegalToSendFromClient;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.BINARY;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.CLOSE;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.ERROR;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.IDLE;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.OPEN;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.PING;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.PONG;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.State.TEXT;
-import static jdk.incubator.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 SequentialScheduler sendScheduler = new SequentialScheduler(new SendTask());
-    private final Queue<Pair<OutgoingMessage, CompletableFuture<WebSocket>>>
-            queue = new ConcurrentLinkedQueue<>();
-    private final Context context = new OutgoingMessage.Context();
-    private final Transmitter transmitter;
-    private final Receiver receiver;
-    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 WebSocket newInstance(URI uri,
-                                 String subprotocol,
-                                 Listener listener,
-                                 TransportSupplier 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,
-                          TransportSupplier transport) {
-        this.uri = requireNonNull(uri);
-        this.subprotocol = requireNonNull(subprotocol);
-        this.listener = requireNonNull(listener);
-        this.transmitter = transport.transmitter();
-        this.receiver = transport.receiver(new SignallingMessageConsumer());
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendText(CharSequence message, boolean isLast) {
-        return enqueueExclusively(new Text(message, isLast));
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendBinary(ByteBuffer message, boolean isLast) {
-        return enqueueExclusively(new Binary(message, isLast));
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendPing(ByteBuffer message) {
-        return enqueue(new Ping(message));
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendPong(ByteBuffer message) {
-        return enqueue(new Pong(message));
-    }
-
-    @Override
-    public CompletableFuture<WebSocket> sendClose(int statusCode, String reason) {
-        if (!isLegalToSendFromClient(statusCode)) {
-            return failedFuture(
-                    new IllegalArgumentException("statusCode: " + statusCode));
-        }
-        Close msg;
-        try {
-            msg = new Close(statusCode, reason);
-        } catch (IllegalArgumentException e) {
-            return failedFuture(e);
-        }
-        outputClosed = true;
-        return enqueueClose(msg);
-    }
-
-    /*
-     * Sends a Close message, then shuts down the transmitter since no more
-     * messages are expected to be sent after this.
-     */
-    private CompletableFuture<WebSocket> enqueueClose(Close m) {
-        // TODO: MUST be a CF created once and shared across sendClose, otherwise
-        // a second sendClose may prematurely close the channel
-        return enqueue(m)
-                .orTimeout(60, TimeUnit.SECONDS)
-                .whenComplete((r, error) -> {
-                    try {
-                        transmitter.close();
-                    } catch (IOException e) {
-                        Log.logError(e);
-                    }
-                    if (error instanceof TimeoutException) {
-                        try {
-                            receiver.close();
-                        } catch (IOException e) {
-                            Log.logError(e);
-                        }
-                    }
-                });
-    }
-
-    /*
-     * Accepts the given message into the outgoing queue in a mutually-exclusive
-     * fashion in respect to other messages accepted through this method. No
-     * further messages will be accepted until the returned CompletableFuture
-     * completes. This method is used to enforce "one outstanding send
-     * operation" policy.
-     */
-    private CompletableFuture<WebSocket> enqueueExclusively(OutgoingMessage m) {
-        if (!outstandingSend.compareAndSet(false, true)) {
-            return failedFuture(new IllegalStateException("Send pending"));
-        }
-        return enqueue(m).whenComplete((r, e) -> outstandingSend.set(false));
-    }
-
-    private CompletableFuture<WebSocket> enqueue(OutgoingMessage m) {
-        CompletableFuture<WebSocket> 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(DeferredCompleter taskCompleter) {
-            Pair<OutgoingMessage, CompletableFuture<WebSocket>> p = queue.poll();
-            if (p == null) {
-                taskCompleter.complete();
-                return;
-            }
-            OutgoingMessage message = p.first;
-            CompletableFuture<WebSocket> cf = p.second;
-            try {
-                if (!message.contextualize(context)) { // Do not send the message
-                    cf.complete(null);
-                    repeat(taskCompleter);
-                    return;
-                }
-                Consumer<Exception> h = e -> {
-                    if (e == null) {
-                        cf.complete(WebSocketImpl.this);
-                    } else {
-                        cf.completeExceptionally(e);
-                    }
-                    repeat(taskCompleter);
-                };
-                transmitter.send(message, h);
-            } catch (Throwable t) {
-                cf.completeExceptionally(t);
-                repeat(taskCompleter);
-            }
-        }
-
-        private void repeat(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();
-        }
-    }
-
-    @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 {
-
-        // Receiver 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)) {
-                                receiver.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 {
-            receiver.close();
-            receiveScheduler.stop();
-            Throwable err = error.get();
-            if (err instanceof FailWebSocketException) {
-                int code1 = ((FailWebSocketException) err).getStatusCode();
-                err = new ProtocolException().initCause(err);
-                enqueueClose(new Close(code1, ""))
-                        .whenComplete(
-                                (r, e) -> {
-                                    if (e != null) {
-                                        Log.logError(e);
-                                    }
-                                });
-            }
-            listener.onError(WebSocketImpl.this, err);
-        }
-
-        private void processClose() throws IOException {
-            receiver.close();
-            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) -> {
-                enqueueClose(new Close(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 = enqueue(new Pong(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 {
-                receiver.close();
-            } finally {
-                transmitter.close();
-            }
-        } 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 {
-                receiver.close();
-            } catch (Throwable t) {
-                Log.logError(t);
-            }
-        }
-    }
-
-    private class SignallingMessageConsumer implements MessageStreamConsumer {
-
-        @Override
-        public void onText(CharSequence data, MessagePart part) {
-            receiver.acknowledge();
-            text = data;
-            WebSocketImpl.this.part = part;
-            tryChangeState(WAITING, TEXT);
-        }
-
-        @Override
-        public void onBinary(ByteBuffer data, MessagePart part) {
-            receiver.acknowledge();
-            binaryData = data;
-            WebSocketImpl.this.part = part;
-            tryChangeState(WAITING, BINARY);
-        }
-
-        @Override
-        public void onPing(ByteBuffer data) {
-            receiver.acknowledge();
-            binaryData = data;
-            tryChangeState(WAITING, PING);
-        }
-
-        @Override
-        public void onPong(ByteBuffer data) {
-            receiver.acknowledge();
-            binaryData = data;
-            tryChangeState(WAITING, PONG);
-        }
-
-        @Override
-        public void onClose(int statusCode, CharSequence reason) {
-            receiver.acknowledge();
-            signalClose(statusCode, reason.toString());
-        }
-
-        @Override
-        public void onComplete() {
-            receiver.acknowledge();
-            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;
-    }
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/websocket/WebSocketRequest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.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);
-}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/package-info.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/**
- * <h2>High level HTTP and WebSocket API</h2>
- * {@Incubating}
- *
- * <p> Provides high-level client interfaces to HTTP (versions 1.1 and 2) and
- * WebSocket. The main types defined are:
- *
- * <ul>
- *    <li>{@link jdk.incubator.http.HttpClient}</li>
- *    <li>{@link jdk.incubator.http.HttpRequest}</li>
- *    <li>{@link jdk.incubator.http.HttpResponse}</li>
- *    <li>{@link jdk.incubator.http.WebSocket}</li>
- * </ul>
- *
- * <p> The API functions asynchronously (using {@link java.util.concurrent.CompletableFuture})
- * and work is done on the threads supplied by the client's {@link java.util.concurrent.Executor}
- * where practical.
- *
- * <p> {@code HttpClient} also provides a simple synchronous mode, where all
- * work may be done on the calling thread.
- *
- * <p> {@code CompletableFuture}s returned by this API will throw {@link java.lang.UnsupportedOperationException}
- * for their {@link java.util.concurrent.CompletableFuture#obtrudeValue(Object) obtrudeValue}
- * and {@link java.util.concurrent.CompletableFuture#obtrudeException(Throwable) obtrudeException}
- * methods. Invoking the {@link java.util.concurrent.CompletableFuture#cancel cancel}
- * method on a {@code CompletableFuture} returned by this API will not interrupt
- * the underlying operation, but may be useful to complete, exceptionally,
- * dependent stages that have not already completed.
- *
- * <p> Unless otherwise stated, {@code null} parameter values will cause methods
- * of all classes in this package to throw {@code NullPointerException}.
- *
- * @since 9
- */
-package jdk.incubator.http;
--- a/src/jdk.incubator.httpclient/share/classes/module-info.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/**
- * Defines the high-level HTTP and WebSocket API.
- * {@Incubating}
- *
- * @moduleGraph
- * @since 9
- */
-module jdk.incubator.httpclient {
-    exports jdk.incubator.http;
-}
--- a/test/jdk/ProblemList.txt	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/ProblemList.txt	Tue Apr 17 08:54:17 2018 -0700
@@ -537,8 +537,6 @@
 
 java/net/DatagramSocket/SendDatagramToBadAddress.java           7143960 macosx-all
 
-java/net/httpclient/SplitResponseSSL.java                       8194151 windows-all
-
 ############################################################################
 
 # jdk_nio
--- a/test/jdk/java/net/httpclient/AbstractNoBody.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/AbstractNoBody.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,6 +23,7 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -32,7 +33,7 @@
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsServer;
-import jdk.incubator.http.HttpClient;
+import java.net.http.HttpClient;
 import javax.net.ssl.SSLContext;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.AfterTest;
@@ -91,6 +92,11 @@
                 .build();
     }
 
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
     @BeforeTest
     public void setup() throws Exception {
         printStamp(START, "setup");
@@ -101,39 +107,37 @@
         // HTTP/1.1
         HttpHandler h1_fixedLengthNoBodyHandler = new HTTP1_FixedLengthNoBodyHandler();
         HttpHandler h1_chunkNoBodyHandler = new HTTP1_ChunkedNoBodyHandler();
-        InetSocketAddress sa = new InetSocketAddress("localhost", 0);
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         httpTestServer = HttpServer.create(sa, 0);
         httpTestServer.setExecutor(serverExecutor);
         httpTestServer.createContext("/http1/noBodyFixed", h1_fixedLengthNoBodyHandler);
         httpTestServer.createContext("/http1/noBodyChunk", h1_chunkNoBodyHandler);
-        httpURI_fixed = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/noBodyFixed";
-        httpURI_chunk = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/noBodyChunk";
+        httpURI_fixed = "http://" + serverAuthority(httpTestServer) + "/http1/noBodyFixed";
+        httpURI_chunk = "http://" + serverAuthority(httpTestServer) + "/http1/noBodyChunk";
 
         httpsTestServer = HttpsServer.create(sa, 0);
         httpsTestServer.setExecutor(serverExecutor);
         httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
         httpsTestServer.createContext("/https1/noBodyFixed", h1_fixedLengthNoBodyHandler);
         httpsTestServer.createContext("/https1/noBodyChunk", h1_chunkNoBodyHandler);
-        httpsURI_fixed = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/noBodyFixed";
-        httpsURI_chunk = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/noBodyChunk";
+        httpsURI_fixed = "https://" + serverAuthority(httpsTestServer) + "/https1/noBodyFixed";
+        httpsURI_chunk = "https://" + serverAuthority(httpsTestServer) + "/https1/noBodyChunk";
 
         // HTTP/2
         Http2Handler h2_fixedLengthNoBodyHandler = new HTTP2_FixedLengthNoBodyHandler();
         Http2Handler h2_chunkedNoBodyHandler = new HTTP2_ChunkedNoBodyHandler();
 
-        http2TestServer = new Http2TestServer("127.0.0.1", false, 0, serverExecutor, null);
+        http2TestServer = new Http2TestServer("localhost", false, 0, serverExecutor, null);
         http2TestServer.addHandler(h2_fixedLengthNoBodyHandler, "/http2/noBodyFixed");
         http2TestServer.addHandler(h2_chunkedNoBodyHandler, "/http2/noBodyChunk");
-        int port = http2TestServer.getAddress().getPort();
-        http2URI_fixed = "http://127.0.0.1:" + port + "/http2/noBodyFixed";
-        http2URI_chunk = "http://127.0.0.1:" + port + "/http2/noBodyChunk";
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/noBodyFixed";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/noBodyChunk";
 
-        https2TestServer = new Http2TestServer("127.0.0.1", true, 0, serverExecutor, sslContext);
+        https2TestServer = new Http2TestServer("localhost", true, 0, serverExecutor, sslContext);
         https2TestServer.addHandler(h2_fixedLengthNoBodyHandler, "/https2/noBodyFixed");
         https2TestServer.addHandler(h2_chunkedNoBodyHandler, "/https2/noBodyChunk");
-        port = https2TestServer.getAddress().getPort();
-        https2URI_fixed = "https://127.0.0.1:" + port + "/https2/noBodyFixed";
-        https2URI_chunk = "https://127.0.0.1:" + port + "/https2/noBodyChunk";
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/noBodyFixed";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/noBodyChunk";
 
         httpTestServer.start();
         httpsTestServer.start();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/AsFileDownloadTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,375 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Basic test for ofFileDownload
+ * @bug 8196965
+ * @modules java.base/sun.net.www.http
+ *          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 /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @build jdk.test.lib.Platform
+ * @build jdk.test.lib.util.FileUtils
+ * @run testng/othervm AsFileDownloadTest
+ * @run testng/othervm/java.security.policy=AsFileDownloadTest.policy AsFileDownloadTest
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import jdk.test.lib.util.FileUtils;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.net.http.HttpResponse.BodyHandlers.ofFileDownload;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.StandardOpenOption.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class AsFileDownloadTest {
+
+    SSLContext sslContext;
+    HttpServer httpTestServer;         // HTTP/1.1    [ 4 servers ]
+    HttpsServer httpsTestServer;       // HTTPS/1.1
+    Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
+    Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    Path tempDir;
+
+    static final String[][] contentDispositionValues = new String[][] {
+          // URI query     Content-Type header value         Expected filename
+            { "001", "Attachment; filename=example001.html", "example001.html" },
+            { "002", "attachment; filename=example002.html", "example002.html" },
+            { "003", "ATTACHMENT; filename=example003.html", "example003.html" },
+            { "004", "attAChment; filename=example004.html", "example004.html" },
+            { "005", "attachmeNt; filename=example005.html", "example005.html" },
+
+            { "006", "attachment; Filename=example006.html", "example006.html" },
+            { "007", "attachment; FILENAME=example007.html", "example007.html" },
+            { "008", "attachment; fileName=example008.html", "example008.html" },
+            { "009", "attachment; fIlEnAMe=example009.html", "example009.html" },
+
+            { "010", "attachment; filename=Example010.html", "Example010.html" },
+            { "011", "attachment; filename=EXAMPLE011.html", "EXAMPLE011.html" },
+            { "012", "attachment; filename=eXample012.html", "eXample012.html" },
+            { "013", "attachment; filename=example013.HTML", "example013.HTML" },
+            { "014", "attachment; filename  =eXaMpLe014.HtMl", "eXaMpLe014.HtMl"},
+
+            { "015", "attachment; filename=a",               "a"               },
+            { "016", "attachment; filename= b",              "b"               },
+            { "017", "attachment; filename=  c",             "c"               },
+            { "018", "attachment; filename=    d",           "d"               },
+            { "019", "attachment; filename=e  ; filename*=utf-8''eee.txt",  "e"},
+            { "020", "attachment; filename*=utf-8''fff.txt; filename=f",    "f"},
+            { "021", "attachment;  filename=g",              "g"               },
+            { "022", "attachment;   filename= h",            "h"               },
+
+            { "023", "attachment; filename=\"space name\"",                       "space name" },
+            { "024", "attachment; filename=me.txt; filename*=utf-8''you.txt",     "me.txt"     },
+            { "025", "attachment; filename=\"m y.txt\"; filename*=utf-8''you.txt", "m y.txt"   },
+
+            { "030", "attachment; filename=foo/file1.txt",        "file1.txt" },
+            { "031", "attachment; filename=foo/bar/file2.txt",    "file2.txt" },
+            { "032", "attachment; filename=baz\\file3.txt",       "file3.txt" },
+            { "033", "attachment; filename=baz\\bar\\file4.txt",  "file4.txt" },
+            { "034", "attachment; filename=x/y\\file5.txt",       "file5.txt" },
+            { "035", "attachment; filename=x/y\\file6.txt",       "file6.txt" },
+            { "036", "attachment; filename=x/y\\z/file7.txt",     "file7.txt" },
+            { "037", "attachment; filename=x/y\\z/\\x/file8.txt", "file8.txt" },
+            { "038", "attachment; filename=/root/file9.txt",      "file9.txt" },
+            { "039", "attachment; filename=../file10.txt",        "file10.txt" },
+            { "040", "attachment; filename=..\\file11.txt",       "file11.txt" },
+            { "041", "attachment; filename=foo/../../file12.txt", "file12.txt" },
+    };
+
+    @DataProvider(name = "positive")
+    public Object[][] positive() {
+        List<Object[]> list = new ArrayList<>();
+
+        Arrays.asList(contentDispositionValues).stream()
+                .map(e -> new Object[] {httpURI +  "?" + e[0], e[1], e[2]})
+                .forEach(list::add);
+        Arrays.asList(contentDispositionValues).stream()
+                .map(e -> new Object[] {httpsURI +  "?" + e[0], e[1], e[2]})
+                .forEach(list::add);
+        Arrays.asList(contentDispositionValues).stream()
+                .map(e -> new Object[] {http2URI +  "?" + e[0], e[1], e[2]})
+                .forEach(list::add);
+        Arrays.asList(contentDispositionValues).stream()
+                .map(e -> new Object[] {https2URI +  "?" + e[0], e[1], e[2]})
+                .forEach(list::add);
+
+        return list.stream().toArray(Object[][]::new);
+    }
+
+    @Test(dataProvider = "positive")
+    void test(String uriString, String contentDispositionValue, String expectedFilename)
+        throws Exception
+    {
+        out.printf("test(%s, %s, %s): starting", uriString, contentDispositionValue, expectedFilename);
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri)
+                .POST(BodyPublishers.ofString("May the luck of the Irish be with you!"))
+                .build();
+
+        BodyHandler bh = ofFileDownload(tempDir.resolve(uri.getPath().substring(1)),
+                                        CREATE, TRUNCATE_EXISTING, WRITE);
+        HttpResponse<Path> response = client.send(request, bh);
+
+        out.println("Got response: " + response);
+        out.println("Got body Path: " + response.body());
+        String fileContents = new String(Files.readAllBytes(response.body()), UTF_8);
+        out.println("Got body: " + fileContents);
+
+        assertEquals(response.statusCode(),200);
+        assertEquals(response.body().getFileName().toString(), expectedFilename);
+        assertTrue(response.headers().firstValue("Content-Disposition").isPresent());
+        assertEquals(response.headers().firstValue("Content-Disposition").get(),
+                     contentDispositionValue);
+        assertEquals(fileContents, "May the luck of the Irish be with you!");
+    }
+
+    // --- Negative
+
+    static final String[][] contentDispositionBADValues = new String[][] {
+            // URI query     Content-Type header value
+            { "100", ""                                    },  // empty
+            { "101", "filename=example.html"               },  // no attachment
+            { "102", "attachment; filename=space name"     },  // unquoted with space
+            { "103", "attachment; filename="               },  // empty filename param
+            { "104", "attachment; filename=\""             },  // single quote
+            { "105", "attachment; filename=\"\""           },  // empty quoted
+            { "106", "attachment; filename=."              },  // dot
+            { "107", "attachment; filename=.."             },  // dot dot
+            { "108", "attachment; filename=\".."           },  // badly quoted dot dot
+            { "109", "attachment; filename=\"..\""         },  // quoted dot dot
+            { "110", "attachment; filename=\"bad"          },  // badly quoted
+            { "111", "attachment; filename=\"bad;"         },  // badly quoted with ';'
+            { "112", "attachment; filename=\"bad ;"        },  // badly quoted with ' ;'
+            { "113", "attachment; filename*=utf-8''xx.txt "},  // no "filename" param
+
+            { "120", "<<NOT_PRESENT>>"                     },  // header not present
+
+    };
+
+    @DataProvider(name = "negative")
+    public Object[][] negative() {
+        List<Object[]> list = new ArrayList<>();
+
+        Arrays.asList(contentDispositionBADValues).stream()
+                .map(e -> new Object[] {httpURI +  "?" + e[0], e[1]})
+                .forEach(list::add);
+        Arrays.asList(contentDispositionBADValues).stream()
+                .map(e -> new Object[] {httpsURI +  "?" + e[0], e[1]})
+                .forEach(list::add);
+        Arrays.asList(contentDispositionBADValues).stream()
+                .map(e -> new Object[] {http2URI +  "?" + e[0], e[1]})
+                .forEach(list::add);
+        Arrays.asList(contentDispositionBADValues).stream()
+                .map(e -> new Object[] {https2URI +  "?" + e[0], e[1]})
+                .forEach(list::add);
+
+        return list.stream().toArray(Object[][]::new);
+    }
+
+    @Test(dataProvider = "negative")
+    void negativeTest(String uriString, String contentDispositionValue)
+            throws Exception
+    {
+        out.printf("negativeTest(%s, %s): starting", uriString, contentDispositionValue);
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri)
+                .POST(BodyPublishers.ofString("Does not matter"))
+                .build();
+
+        BodyHandler bh = ofFileDownload(tempDir, CREATE, TRUNCATE_EXISTING, WRITE);
+        try {
+            HttpResponse<Path> response = client.send(request, bh);
+            fail("UNEXPECTED response: " + response + ", path:" + response.body());
+        } catch (UncheckedIOException | IOException ioe) {
+            System.out.println("Caught expected: " + ioe);
+        }
+    }
+
+    // -- Infrastructure
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        tempDir = Paths.get("asFileDownloadTest.tmp.dir");
+        if (Files.exists(tempDir))
+            throw new AssertionError("Unexpected test work dir existence: " + tempDir.toString());
+
+        Files.createDirectory(tempDir);
+        // Unique dirs per test run, based on the URI path
+        Files.createDirectories(tempDir.resolve("http1/afdt/"));
+        Files.createDirectories(tempDir.resolve("https1/afdt/"));
+        Files.createDirectories(tempDir.resolve("http2/afdt/"));
+        Files.createDirectories(tempDir.resolve("https2/afdt/"));
+
+        // HTTP/1.1 server logging in case of security exceptions ( uncomment if needed )
+        //Logger logger = Logger.getLogger("com.sun.net.httpserver");
+        //ConsoleHandler ch = new ConsoleHandler();
+        //logger.setLevel(Level.ALL);
+        //ch.setLevel(Level.ALL);
+        //logger.addHandler(ch);
+
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpServer.create(sa, 0);
+        httpTestServer.createContext("/http1/afdt", new Http1FileDispoHandler());
+        httpURI = "http://" + serverAuthority(httpTestServer) + "/http1/afdt";
+
+        httpsTestServer = HttpsServer.create(sa, 0);
+        httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer.createContext("/https1/afdt", new Http1FileDispoHandler());
+        httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1/afdt";
+
+        http2TestServer = new Http2TestServer("localhost", false, 0);
+        http2TestServer.addHandler(new Http2FileDispoHandler(), "/http2/afdt");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/afdt";
+
+        https2TestServer = new Http2TestServer("localhost", true, 0);
+        https2TestServer.addHandler(new Http2FileDispoHandler(), "/https2/afdt");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/afdt";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop(0);
+        httpsTestServer.stop(0);
+        http2TestServer.stop();
+        https2TestServer.stop();
+
+        if (System.getSecurityManager() == null && Files.exists(tempDir)) {
+            // clean up before next run with security manager
+            FileUtils.deleteFileTreeWithRetry(tempDir);
+        }
+    }
+
+    static String contentDispositionValueFromURI(URI uri) {
+        String queryIndex = uri.getQuery();
+        String[][] values;
+        if (queryIndex.startsWith("0"))  // positive tests start with '0'
+            values = contentDispositionValues;
+        else if (queryIndex.startsWith("1"))  // negative tests start with '1'
+            values = contentDispositionBADValues;
+        else
+            throw new AssertionError("SERVER: UNEXPECTED query:" + queryIndex);
+
+        return Arrays.asList(values).stream()
+                .filter(e -> e[0].equals(queryIndex))
+                .map(e -> e[1])
+                .findFirst()
+                .orElseThrow();
+    }
+
+    static class Http1FileDispoHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange t) throws IOException {
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                byte[] bytes = is.readAllBytes();
+
+                String value = contentDispositionValueFromURI(t.getRequestURI());
+                if (!value.equals("<<NOT_PRESENT>>"))
+                    t.getResponseHeaders().set("Content-Disposition", value);
+
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+
+    static class Http2FileDispoHandler implements Http2Handler {
+        @Override
+        public void handle(Http2TestExchange t) throws IOException {
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                byte[] bytes = is.readAllBytes();
+
+                String value = contentDispositionValueFromURI(t.getRequestURI());
+                if (!value.equals("<<NOT_PRESENT>>"))
+                    t.getResponseHeaders().addHeader("Content-Disposition", value);
+
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/AsFileDownloadTest.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,65 @@
+//
+// 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.
+//
+
+// for JTwork/classes/0/lib/testlibrary/jdk/testlibrary/SimpleSSLContext.class
+grant codeBase "file:${test.classes}/../../../../lib/testlibrary/-" {
+    permission java.util.PropertyPermission "test.src.path", "read";
+    permission java.io.FilePermission "${test.src}/../../../lib/testlibrary/jdk/testlibrary/testkeys", "read";
+};
+
+// for JTwork//classes/0/java/net/httpclient/http2/server/*
+grant codeBase "file:${test.classes}/../../../../java/net/httpclient/http2/server/*" {
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.common";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.frame";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.hpack";
+    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www.http";
+
+    permission java.net.SocketPermission "localhost:*", "accept,resolve";
+    permission java.lang.RuntimePermission "modifyThread";
+};
+
+grant codeBase "file:${test.classes}/*" {
+    permission java.io.FilePermission "${user.dir}${/}asFileDownloadTest.tmp.dir", "read,write";
+    permission java.io.FilePermission "${user.dir}${/}asFileDownloadTest.tmp.dir/-", "read,write";
+
+    permission java.net.URLPermission "http://localhost:*/http1/afdt",   "POST";
+    permission java.net.URLPermission "https://localhost:*/https1/afdt", "POST";
+    permission java.net.URLPermission "http://localhost:*/http2/afdt",   "POST";
+    permission java.net.URLPermission "https://localhost:*/https2/afdt", "POST";
+
+
+    // needed to grant permission to the HTTP/2 server
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.common";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.frame";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.hpack";
+    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www.http";
+
+    // for HTTP/1.1 server logging
+    permission java.util.logging.LoggingPermission "control";
+
+    // needed to grant the HTTP servers
+    permission java.net.SocketPermission "localhost:*", "accept,resolve";
+
+    permission java.util.PropertyPermission "*", "read";
+    permission java.lang.RuntimePermission "modifyThread";
+};
--- a/test/jdk/java/net/httpclient/BasicAuthTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/BasicAuthTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /**
  * @test
  * @bug 8087112
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          jdk.httpserver
  * @run main/othervm BasicAuthTest
  * @summary Basic Authentication Test
@@ -38,15 +38,18 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.PasswordAuthentication;
 import java.net.URI;
-import jdk.incubator.http.*;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
 
 public class BasicAuthTest {
 
@@ -55,7 +58,8 @@
     static final String POST_BODY = "This is the POST body 123909090909090";
 
     public static void main(String[] args) throws Exception {
-        HttpServer server = HttpServer.create(new InetSocketAddress(0), 10);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(),0);
+        HttpServer server = HttpServer.create(addr, 10);
         ExecutorService e = Executors.newCachedThreadPool();
         Handler h = new Handler();
         HttpContext serverContext = server.createContext("/test", h);
@@ -72,10 +76,10 @@
                                       .build();
 
         try {
-            URI uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/test/foo");
+            URI uri = new URI("http://localhost:" + Integer.toString(port) + "/test/foo");
             HttpRequest req = HttpRequest.newBuilder(uri).GET().build();
 
-            HttpResponse resp = client.send(req, asString());
+            HttpResponse resp = client.send(req, BodyHandlers.ofString());
             ok = resp.statusCode() == 200 && resp.body().equals(RESPONSE);
 
             if (!ok || ca.count != 1)
@@ -83,7 +87,7 @@
 
             // repeat same request, should succeed but no additional authenticator calls
 
-            resp = client.send(req, asString());
+            resp = client.send(req, BodyHandlers.ofString());
             ok = resp.statusCode() == 200 && resp.body().equals(RESPONSE);
 
             if (!ok || ca.count != 1)
@@ -92,9 +96,9 @@
             // try a POST
 
             req = HttpRequest.newBuilder(uri)
-                             .POST(fromString(POST_BODY))
+                             .POST(BodyPublishers.ofString(POST_BODY))
                              .build();
-            resp = client.send(req, asString());
+            resp = client.send(req, BodyHandlers.ofString());
             ok = resp.statusCode() == 200;
 
             if (!ok || ca.count != 1)
@@ -156,6 +160,5 @@
                 ok = true;
             }
         }
-
    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/BasicRedirectTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,349 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Basic test for redirect and redirect policies
+ * @modules java.base/sun.net.www.http
+ *          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 /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm
+ *       -Djdk.httpclient.HttpClient.log=trace,headers,requests
+ *       BasicRedirectTest
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Redirect;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+public class BasicRedirectTest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;        // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;       // HTTPS/1.1
+    HttpTestServer http2TestServer;       // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;      // HTTP/2 ( h2  )
+    String httpURI;
+    String httpURIToMoreSecure; // redirects HTTP to HTTPS
+    String httpsURI;
+    String httpsURIToLessSecure; // redirects HTTPS to HTTP
+    String http2URI;
+    String http2URIToMoreSecure; // redirects HTTP to HTTPS
+    String https2URI;
+    String https2URIToLessSecure; // redirects HTTPS to HTTP
+
+    static final String MESSAGE = "Is fearr Gaeilge briste, na Bearla cliste";
+    static final int ITERATIONS = 3;
+
+    @DataProvider(name = "positive")
+    public Object[][] positive() {
+        return new Object[][] {
+                { httpURI,               Redirect.ALWAYS        },
+                { httpsURI,              Redirect.ALWAYS        },
+                { http2URI,              Redirect.ALWAYS        },
+                { https2URI,             Redirect.ALWAYS        },
+                { httpURIToMoreSecure,   Redirect.ALWAYS        },
+                { http2URIToMoreSecure,  Redirect.ALWAYS        },
+                { httpsURIToLessSecure,  Redirect.ALWAYS        },
+                { https2URIToLessSecure, Redirect.ALWAYS        },
+
+                { httpURI,               Redirect.NORMAL        },
+                { httpsURI,              Redirect.NORMAL        },
+                { http2URI,              Redirect.NORMAL        },
+                { https2URI,             Redirect.NORMAL        },
+                { httpURIToMoreSecure,   Redirect.NORMAL        },
+                { http2URIToMoreSecure,  Redirect.NORMAL        },
+        };
+    }
+
+    @Test(dataProvider = "positive")
+    void test(String uriString, Redirect redirectPolicy) throws Exception {
+        out.printf("%n---- starting positive (%s, %s) ----%n", uriString, redirectPolicy);
+        HttpClient client = HttpClient.newBuilder()
+                .followRedirects(redirectPolicy)
+                .sslContext(sslContext)
+                .build();
+
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri).build();
+        out.println("Initial request: " + request.uri());
+
+        for (int i=0; i< ITERATIONS; i++) {
+            out.println("iteration: " + i);
+            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+
+            out.println("  Got response: " + response);
+            out.println("  Got body Path: " + response.body());
+            out.println("  Got response.request: " + response.request());
+
+            assertEquals(response.statusCode(), 200);
+            assertEquals(response.body(), MESSAGE);
+            // asserts redirected URI in response.request().uri()
+            assertTrue(response.uri().getPath().endsWith("message"));
+            assertPreviousRedirectResponses(request, response);
+        }
+    }
+
+    static void assertPreviousRedirectResponses(HttpRequest initialRequest,
+                                                HttpResponse<?> finalResponse) {
+        // there must be at least one previous response
+        finalResponse.previousResponse()
+                .orElseThrow(() -> new RuntimeException("no previous response"));
+
+        HttpResponse<?> response = finalResponse;
+        do {
+            URI uri = response.uri();
+            response = response.previousResponse().get();
+            assertTrue(300 <= response.statusCode() && response.statusCode() <= 309,
+                       "Expected 300 <= code <= 309, got:" + response.statusCode());
+            assertEquals(response.body(), null, "Unexpected body: " + response.body());
+            String locationHeader = response.headers().firstValue("Location")
+                      .orElseThrow(() -> new RuntimeException("no previous Location"));
+            assertTrue(uri.toString().endsWith(locationHeader),
+                      "URI: " + uri + ", Location: " + locationHeader);
+
+        } while (response.previousResponse().isPresent());
+
+        // initial
+        assertEquals(initialRequest, response.request(),
+                String.format("Expected initial request [%s] to equal last prev req [%s]",
+                              initialRequest, response.request()));
+    }
+
+    // --  negatives
+
+    @DataProvider(name = "negative")
+    public Object[][] negative() {
+        return new Object[][] {
+                { httpURI,               Redirect.NEVER         },
+                { httpsURI,              Redirect.NEVER         },
+                { http2URI,              Redirect.NEVER         },
+                { https2URI,             Redirect.NEVER         },
+                { httpURIToMoreSecure,   Redirect.NEVER         },
+                { http2URIToMoreSecure,  Redirect.NEVER         },
+                { httpsURIToLessSecure,  Redirect.NEVER         },
+                { https2URIToLessSecure, Redirect.NEVER         },
+
+                { httpsURIToLessSecure,  Redirect.NORMAL        },
+                { https2URIToLessSecure, Redirect.NORMAL        },
+        };
+    }
+
+    @Test(dataProvider = "negative")
+    void testNegatives(String uriString,Redirect redirectPolicy) throws Exception {
+        out.printf("%n---- starting negative (%s, %s) ----%n", uriString, redirectPolicy);
+        HttpClient client = HttpClient.newBuilder()
+                .followRedirects(redirectPolicy)
+                .sslContext(sslContext)
+                .build();
+
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri).build();
+        out.println("Initial request: " + request.uri());
+
+        for (int i=0; i< ITERATIONS; i++) {
+            out.println("iteration: " + i);
+            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+
+            out.println("  Got response: " + response);
+            out.println("  Got body Path: " + response.body());
+            out.println("  Got response.request: " + response.request());
+
+            assertEquals(response.statusCode(), 302);
+            assertEquals(response.body(), "XY");
+            // asserts original URI in response.request().uri()
+            assertTrue(response.uri().equals(uri));
+            assertFalse(response.previousResponse().isPresent());
+        }
+    }
+
+
+    // -- Infrastructure
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(new BasicHttpRedirectHandler(), "/http1/same/");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/same/redirect";
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(new BasicHttpRedirectHandler(),"/https1/same/");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/same/redirect";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(new BasicHttpRedirectHandler(), "/http2/same/");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/same/redirect";
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(new BasicHttpRedirectHandler(), "/https2/same/");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/same/redirect";
+
+
+        // HTTP to HTTPS redirect handler
+        httpTestServer.addHandler(new ToSecureHttpRedirectHandler(httpsURI), "/http1/toSecure/");
+        httpURIToMoreSecure = "http://" + httpTestServer.serverAuthority()+ "/http1/toSecure/redirect";
+        // HTTP2 to HTTP2S redirect handler
+        http2TestServer.addHandler(new ToSecureHttpRedirectHandler(https2URI), "/http2/toSecure/");
+        http2URIToMoreSecure = "http://" + http2TestServer.serverAuthority() + "/http2/toSecure/redirect";
+
+        // HTTPS to HTTP redirect handler
+        httpsTestServer.addHandler(new ToLessSecureRedirectHandler(httpURI), "/https1/toLessSecure/");
+        httpsURIToLessSecure = "https://" + httpsTestServer.serverAuthority() + "/https1/toLessSecure/redirect";
+        // HTTPS2 to HTTP2 redirect handler
+        https2TestServer.addHandler(new ToLessSecureRedirectHandler(http2URI), "/https2/toLessSecure/");
+        https2URIToLessSecure = "https://" + https2TestServer.serverAuthority() + "/https2/toLessSecure/redirect";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    // Redirects to same protocol
+    static class BasicHttpRedirectHandler implements HttpTestHandler {
+        // flip-flop between chunked/variable and fixed length redirect responses
+        volatile int count;
+
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            System.out.println("BasicHttpRedirectHandler for: " + t.getRequestURI());
+            readAllRequestData(t);
+
+            if (t.getRequestURI().getPath().endsWith("redirect")) {
+                String url = t.getRequestURI().resolve("message").toString();
+                t.getResponseHeaders().addHeader("Location", url);
+                int len = count % 2 == 0 ? 2 : -1;
+                t.sendResponseHeaders(302, len);
+                try (OutputStream os = t.getResponseBody()) {
+                    os.write(new byte[]{'X', 'Y'});  // stuffing some response body
+                }
+            } else {
+                try (OutputStream os = t.getResponseBody()) {
+                    byte[] bytes = MESSAGE.getBytes(UTF_8);
+                    t.sendResponseHeaders(200, bytes.length);
+                    os.write(bytes);
+                }
+            }
+        }
+    }
+
+    // Redirects to a, possibly, more secure protocol, (HTTP to HTTPS)
+    static class ToSecureHttpRedirectHandler implements HttpTestHandler {
+        final String targetURL;
+        ToSecureHttpRedirectHandler(String targetURL) {
+            this.targetURL = targetURL;
+        }
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            System.out.println("ToSecureHttpRedirectHandler for: " + t.getRequestURI());
+            readAllRequestData(t);
+
+            if (t.getRequestURI().getPath().endsWith("redirect")) {
+                t.getResponseHeaders().addHeader("Location", targetURL);
+                System.out.println("ToSecureHttpRedirectHandler redirecting to: " + targetURL);
+                t.sendResponseHeaders(302, 2); // fixed-length
+                try (OutputStream os = t.getResponseBody()) {
+                    os.write(new byte[]{'X', 'Y'});
+                }
+            } else {
+                Throwable ex = new RuntimeException("Unexpected request");
+                ex.printStackTrace();
+                t.sendResponseHeaders(500, 0);
+            }
+        }
+    }
+
+    // Redirects to a, possibly, less secure protocol (HTTPS to HTTP)
+    static class ToLessSecureRedirectHandler implements HttpTestHandler {
+        final String targetURL;
+        ToLessSecureRedirectHandler(String targetURL) {
+            this.targetURL = targetURL;
+        }
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            System.out.println("ToLessSecureRedirectHandler for: " + t.getRequestURI());
+            readAllRequestData(t);
+
+            if (t.getRequestURI().getPath().endsWith("redirect")) {
+                t.getResponseHeaders().addHeader("Location", targetURL);
+                System.out.println("ToLessSecureRedirectHandler redirecting to: " + targetURL);
+                t.sendResponseHeaders(302, -1);  // chunked/variable
+                try (OutputStream os = t.getResponseBody()) {
+                    os.write(new byte[]{'X', 'Y'});
+                }
+            } else {
+                Throwable ex = new RuntimeException("Unexpected request");
+                ex.printStackTrace();
+                t.sendResponseHeaders(500, 0);
+            }
+        }
+    }
+
+    static void readAllRequestData(HttpTestExchange t) throws IOException {
+        try (InputStream is = t.getRequestBody()) {
+            is.readAllBytes();
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -21,27 +21,19 @@
  * questions.
  */
 
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import java.nio.ByteBuffer;
+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.BodyHandlers;
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 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.Flow;
 import java.util.stream.Stream;
 import static java.lang.System.err;
 
@@ -95,14 +87,14 @@
         // This example shows how to return an InputStream that can be used to
         // start reading the response body before the response is fully received.
         // In comparison, the snipet below (which uses
-        // HttpResponse.BodyHandler.asString()) obviously will not return before the
+        // HttpResponse.BodyHandlers.ofString()) obviously will not return before the
         // response body is fully read:
         //
         // System.out.println(
-        //    client.sendAsync(request, HttpResponse.BodyHandler.asString()).get().body());
+        //    client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).get().body());
 
         CompletableFuture<HttpResponse<InputStream>> handle =
-            client.sendAsync(request, HttpResponse.BodyHandler.asInputStream());
+            client.sendAsync(request, BodyHandlers.ofInputStream());
         if (DEBUG) err.println("Request sent");
 
         HttpResponse<InputStream> pending = handle.get();
--- a/test/jdk/java/net/httpclient/BufferingSubscriberCancelTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/BufferingSubscriberCancelTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -31,7 +31,7 @@
 import java.util.concurrent.SubmissionPublisher;
 import java.util.function.IntSupplier;
 import java.util.stream.IntStream;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscriber;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static java.lang.Long.MAX_VALUE;
@@ -39,7 +39,7 @@
 import static java.lang.System.out;
 import static java.nio.ByteBuffer.wrap;
 import static java.util.concurrent.TimeUnit.SECONDS;
-import static jdk.incubator.http.HttpResponse.BodySubscriber.buffering;
+import static java.net.http.HttpResponse.BodySubscribers.buffering;
 import static org.testng.Assert.*;
 
 /*
--- a/test/jdk/java/net/httpclient/BufferingSubscriberErrorCompleteTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/BufferingSubscriberErrorCompleteTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -25,24 +25,19 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletionStage;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Flow.Subscription;
 import java.util.concurrent.Phaser;
 import java.util.concurrent.SubmissionPublisher;
-import java.util.function.IntSupplier;
 import java.util.stream.IntStream;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscriber;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static java.lang.Long.MAX_VALUE;
 import static java.lang.Long.MIN_VALUE;
-import static java.lang.System.out;
 import static java.nio.ByteBuffer.wrap;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static jdk.incubator.http.HttpResponse.BodySubscriber.buffering;
+import static java.net.http.HttpResponse.BodySubscribers.buffering;
 import static org.testng.Assert.*;
 
 /*
--- a/test/jdk/java/net/httpclient/BufferingSubscriberTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/BufferingSubscriberTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -33,8 +33,10 @@
 import java.util.concurrent.Flow.Subscription;
 import java.util.concurrent.SubmissionPublisher;
 import java.util.function.BiConsumer;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
 import jdk.test.lib.RandomFactory;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -87,8 +89,8 @@
     public void subscriberThrowsIAE(int bufferSize) {
         printStamp(START, "subscriberThrowsIAE(%d)", bufferSize);
         try {
-            BodySubscriber<?> bp = BodySubscriber.asByteArray();
-            BodySubscriber.buffering(bp, bufferSize);
+            BodySubscriber<?> bp = BodySubscribers.ofByteArray();
+            BodySubscribers.buffering(bp, bufferSize);
         } finally {
             printStamp(END, "subscriberThrowsIAE(%d)", bufferSize);
         }
@@ -98,8 +100,8 @@
     public void handlerThrowsIAE(int bufferSize) {
         printStamp(START, "handlerThrowsIAE(%d)", bufferSize);
         try {
-            BodyHandler<?> bp = BodyHandler.asByteArray();
-            BodyHandler.buffering(bp, bufferSize);
+            BodyHandler<?> bp = BodyHandlers.ofByteArray();
+            BodyHandlers.buffering(bp, bufferSize);
         } finally {
             printStamp(END, "handlerThrowsIAE(%d)", bufferSize);
         }
@@ -242,7 +244,7 @@
                                                 delay,
                                                 expectedTotalSize,
                                                 requestAmount);
-            return BodySubscriber.buffering(s, bufferSize);
+            return BodySubscribers.buffering(s, bufferSize);
         }
 
         private void requestMore() {
--- a/test/jdk/java/net/httpclient/CancelledResponse.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/CancelledResponse.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -21,12 +21,11 @@
  * questions.
  */
 
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpClient.Version;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.jshell.spi.ExecutionControl;
+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 jdk.testlibrary.SimpleSSLContext;
 
 import javax.net.ServerSocketFactory;
@@ -46,18 +45,20 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodySubscriber;
 
 import static java.lang.String.format;
 import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
 
 /**
  * @test
  * @bug 8087112
  * @library /lib/testlibrary
+ * @modules java.net.http/jdk.internal.net.http.common
  * @build jdk.testlibrary.SimpleSSLContext
- * @build MockServer
+ * @build MockServer ReferenceTracker
  * @run main/othervm  CancelledResponse
  * @run main/othervm  CancelledResponse SSL
  */
@@ -74,7 +75,9 @@
         if (!serverKeepalive)
             sb.append("Connection: Close\r\n");
 
-        sb.append("Content-length: ").append(body.length()).append("\r\n");
+        sb.append("Content-length: ")
+                .append(body.getBytes(ISO_8859_1).length)
+                .append("\r\n");
         sb.append("\r\n");
         sb.append(body);
         return sb.toString();
@@ -86,6 +89,7 @@
         "aliquip ex ea commodo consequat."
     };
 
+    static final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
     final ServerSocketFactory factory;
     final SSLContext context;
     final boolean useSSL;
@@ -106,7 +110,7 @@
         } else {
             client = HttpClient.newHttpClient();
         }
-        return client;
+        return TRACKER.track(client);
     }
 
     public static void main(String[] args) throws Exception {
@@ -116,15 +120,32 @@
         }
         CancelledResponse sp = new CancelledResponse(useSSL);
 
-        for (Version version : Version.values()) {
-            for (boolean serverKeepalive : new boolean[]{ true, false }) {
-                // Note: the mock server doesn't support Keep-Alive, but
-                // pretending that it might exercises code paths in and out of
-                // the connection pool, and retry logic
-                for (boolean async : new boolean[]{ true, false }) {
-                    sp.test(version, serverKeepalive, async);
+        Throwable failed = null;
+        try {
+            for (Version version : Version.values()) {
+                for (boolean serverKeepalive : new boolean[]{true, false}) {
+                    // Note: the mock server doesn't support Keep-Alive, but
+                    // pretending that it might exercises code paths in and out of
+                    // the connection pool, and retry logic
+                    for (boolean async : new boolean[]{true, false}) {
+                        sp.test(version, serverKeepalive, async);
+                    }
                 }
             }
+        } catch (Exception | Error t) {
+            failed = t;
+            throw t;
+        } finally {
+            Thread.sleep(100);
+            AssertionError trackFailed = TRACKER.check(500);
+            if (trackFailed != null) {
+                if (failed != null) {
+                    failed.addSuppressed(trackFailed);
+                    if (failed instanceof Error) throw (Error) failed;
+                    if (failed instanceof Exception) throw (Exception) failed;
+                }
+                throw trackFailed;
+            }
         }
     }
 
@@ -158,11 +179,11 @@
                 try {
                     if (async) {
                         out.println("send async: " + request);
-                        cf1 = client.sendAsync(request, asString(body, cancelled));
+                        cf1 = client.sendAsync(request, ofString(body, cancelled));
                         r = cf1.get();
                     } else { // sync
                         out.println("send sync: " + request);
-                        r = client.send(request, asString(body, cancelled));
+                        r = client.send(request, ofString(body, cancelled));
                     }
                 } catch (CancelException c1) {
                     System.out.println("Got expected exception: " + c1);
@@ -286,13 +307,13 @@
             this.cancelled = cancelled;
         }
         @Override
-        public BodySubscriber<String> apply(int statusCode, HttpHeaders responseHeaders) {
+        public BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
             assert !cancelled.get();
             return new CancellingSubscriber(expected, cancelled);
         }
     }
 
-    BodyHandler<String> asString(String expected, AtomicBoolean cancelled) {
+    BodyHandler<String> ofString(String expected, AtomicBoolean cancelled) {
         return new CancellingHandler(expected, cancelled);
     }
 
--- a/test/jdk/java/net/httpclient/ConcurrentResponses.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ConcurrentResponses.java	Tue Apr 17 08:54:17 2018 -0700
@@ -27,9 +27,9 @@
  * @summary Buffers given to response body subscribers should not contain
  *          unprocessed HTTP data
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
@@ -41,6 +41,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
@@ -57,19 +58,20 @@
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsServer;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+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.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
+import static java.net.http.HttpResponse.BodyHandlers.discarding;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.fail;
@@ -144,7 +146,7 @@
     }
 
 
-    // The asString implementation accumulates data, below a certain threshold
+    // The ofString implementation accumulates data, below a certain threshold
     // into the byte buffers it is given.
     @Test(dataProvider = "uris")
     void testAsString(String uri) throws Exception {
@@ -158,11 +160,11 @@
         }
 
         // initial connection to seed the cache so next parallel connections reuse it
-        client.sendAsync(HttpRequest.newBuilder(URI.create(uri)).build(), discard(null)).join();
+        client.sendAsync(HttpRequest.newBuilder(URI.create(uri)).build(), discarding()).join();
 
         // will reuse connection cached from the previous request ( when HTTP/2 )
         CompletableFuture.allOf(requests.keySet().parallelStream()
-                .map(request -> client.sendAsync(request, asString()))
+                .map(request -> client.sendAsync(request, BodyHandlers.ofString()))
                 .map(cf -> cf.thenCompose(ConcurrentResponses::assert200ResponseCode))
                 .map(cf -> cf.thenCompose(response -> assertbody(response, requests.get(response.request()))))
                 .toArray(CompletableFuture<?>[]::new))
@@ -183,7 +185,7 @@
         }
 
         // initial connection to seed the cache so next parallel connections reuse it
-        client.sendAsync(HttpRequest.newBuilder(URI.create(uri)).build(), discard(null)).join();
+        client.sendAsync(HttpRequest.newBuilder(URI.create(uri)).build(), discarding()).join();
 
         // will reuse connection cached from the previous request ( when HTTP/2 )
         CompletableFuture.allOf(requests.keySet().parallelStream()
@@ -195,21 +197,21 @@
     }
 
     /**
-     * A subscriber that wraps asString, but mucks with any data between limit
+     * A subscriber that wraps ofString, but mucks with any data between limit
      * and capacity, if the client mistakenly passes it any that is should not.
      */
     static class CustomSubscriber implements BodySubscriber<String> {
-        static final BodyHandler<String> handler = (r,h) -> new CustomSubscriber();
-        private final BodySubscriber<String> asString = BodySubscriber.asString(UTF_8);
+        static final BodyHandler<String> handler = (r) -> new CustomSubscriber();
+        private final BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
 
         @Override
         public CompletionStage<String> getBody() {
-            return asString.getBody();
+            return ofString.getBody();
         }
 
         @Override
         public void onSubscribe(Flow.Subscription subscription) {
-            asString.onSubscribe(subscription);
+            ofString.onSubscribe(subscription);
         }
 
         @Override
@@ -217,6 +219,9 @@
             // Muck any data beyond the give limit, since there shouldn't
             // be any of interest to the HTTP Client.
             for (ByteBuffer buffer : buffers) {
+                if (buffer.isReadOnly())
+                    continue;
+
                 if (buffer.limit() != buffer.capacity()) {
                     final int limit = buffer.limit();
                     final int position = buffer.position();
@@ -228,22 +233,26 @@
                     buffer.limit(limit);       // restore original limit
                 }
             }
-            asString.onNext(buffers);
+            ofString.onNext(buffers);
         }
 
         @Override
         public void onError(Throwable throwable) {
-            asString.onError(throwable);
+            ofString.onError(throwable);
             throwable.printStackTrace();
             fail("UNEXPECTED:" + throwable);
         }
 
         @Override
         public void onComplete() {
-            asString.onComplete();
+            ofString.onComplete();
         }
     }
 
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
 
     @BeforeTest
     public void setup() throws Exception {
@@ -251,31 +260,31 @@
         if (sslContext == null)
             throw new AssertionError("Unexpected null sslContext");
 
-        InetSocketAddress sa = new InetSocketAddress("localhost", 0);
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         httpTestServer = HttpServer.create(sa, 0);
         httpTestServer.createContext("/http1/fixed", new Http1FixedHandler());
-        httpFixedURI = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/fixed";
+        httpFixedURI = "http://" + serverAuthority(httpTestServer) + "/http1/fixed";
         httpTestServer.createContext("/http1/chunked", new Http1ChunkedHandler());
-        httpChunkedURI = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/chunked";
+        httpChunkedURI = "http://" + serverAuthority(httpTestServer) + "/http1/chunked";
 
         httpsTestServer = HttpsServer.create(sa, 0);
         httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
         httpsTestServer.createContext("/https1/fixed", new Http1FixedHandler());
-        httpsFixedURI = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/fixed";
+        httpsFixedURI = "https://" + serverAuthority(httpsTestServer) + "/https1/fixed";
         httpsTestServer.createContext("/https1/chunked", new Http1ChunkedHandler());
-        httpsChunkedURI = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/chunked";
+        httpsChunkedURI = "https://" + serverAuthority(httpsTestServer) + "/https1/chunked";
 
-        http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+        http2TestServer = new Http2TestServer("localhost", false, 0);
         http2TestServer.addHandler(new Http2FixedHandler(), "/http2/fixed");
-        http2FixedURI = "http://127.0.0.1:" + http2TestServer.getAddress().getPort() + "/http2/fixed";
+        http2FixedURI = "http://" + http2TestServer.serverAuthority()+ "/http2/fixed";
         http2TestServer.addHandler(new Http2VariableHandler(), "/http2/variable");
-        http2VariableURI = "http://127.0.0.1:" + http2TestServer.getAddress().getPort() + "/http2/variable";
+        http2VariableURI = "http://" + http2TestServer.serverAuthority() + "/http2/variable";
 
-        https2TestServer = new Http2TestServer("127.0.0.1", true, 0);
+        https2TestServer = new Http2TestServer("localhost", true, 0);
         https2TestServer.addHandler(new Http2FixedHandler(), "/https2/fixed");
-        https2FixedURI = "https://127.0.0.1:" + https2TestServer.getAddress().getPort() + "/https2/fixed";
+        https2FixedURI = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
         https2TestServer.addHandler(new Http2VariableHandler(), "/https2/variable");
-        https2VariableURI = "https://127.0.0.1:" + https2TestServer.getAddress().getPort() + "/https2/variable";
+        https2VariableURI = "https://" + https2TestServer.serverAuthority() + "/https2/variable";
 
         httpTestServer.start();
         httpsTestServer.start();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/CookieHeaderTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,559 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8199851
+ * @summary Test for multiple vs single cookie header for HTTP/2 vs HTTP/1.1
+ * @modules java.base/sun.net.www.http
+ *          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 /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm
+ *       -Djdk.httpclient.HttpClient.log=trace,headers,requests
+ *       CookieHeaderTest
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Redirect;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class CookieHeaderTest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;        // HTTP/1.1    [ 6 servers ]
+    HttpTestServer httpsTestServer;       // HTTPS/1.1
+    HttpTestServer http2TestServer;       // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;      // HTTP/2 ( h2  )
+    DummyServer httpDummyServer;
+    DummyServer httpsDummyServer;
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+    String httpDummy;
+    String httpsDummy;
+
+    static final String MESSAGE = "Basic CookieHeaderTest message body";
+    static final int ITERATIONS = 3;
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    @DataProvider(name = "positive")
+    public Object[][] positive() {
+        return new Object[][] {
+                { httpURI, HttpClient.Version.HTTP_1_1  },
+                { httpsURI, HttpClient.Version.HTTP_1_1  },
+                { httpDummy, HttpClient.Version.HTTP_1_1 },
+                { httpsDummy, HttpClient.Version.HTTP_1_1 },
+                { httpURI, HttpClient.Version.HTTP_2  },
+                { httpsURI, HttpClient.Version.HTTP_2  },
+                { httpDummy, HttpClient.Version.HTTP_2 },
+                { httpsDummy, HttpClient.Version.HTTP_2 },
+                { http2URI, null  },
+                { https2URI, null },
+        };
+    }
+
+    static final AtomicLong requestCounter = new AtomicLong();
+
+    @Test(dataProvider = "positive")
+    void test(String uriString, HttpClient.Version version) throws Exception {
+        out.printf("%n---- starting (%s) ----%n", uriString);
+        ConcurrentHashMap<String, List<String>> cookieHeaders
+                = new ConcurrentHashMap<>();
+        CookieHandler cookieManager = new TestCookieHandler(cookieHeaders);
+        HttpClient client = HttpClient.newBuilder()
+                .followRedirects(Redirect.ALWAYS)
+                .cookieHandler(cookieManager)
+                .sslContext(sslContext)
+                .build();
+        assert client.cookieHandler().isPresent();
+
+        URI uri = URI.create(uriString);
+        List<String> cookies = new ArrayList<>();
+        cookies.add("CUSTOMER=ARTHUR_DENT");
+        cookies.add("LOCATION=TR\u0100IN_STATION");
+        cookies.add("LOC\u0100TION=TRAIN_STATION");
+        cookies.add("ORDER=BISCUITS");
+        cookieHeaders.put("Cookie", cookies);
+
+        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(uri)
+                .header("X-uuid", "uuid-" + requestCounter.incrementAndGet());
+        if (version != null) {
+            requestBuilder.version(version);
+        }
+        HttpRequest request = requestBuilder.build();
+        out.println("Initial request: " + request.uri());
+
+        for (int i=0; i< ITERATIONS; i++) {
+            out.println("iteration: " + i);
+            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+
+            out.println("  Got response: " + response);
+            out.println("  Got body Path: " + response.body());
+
+            assertEquals(response.statusCode(), 200);
+            assertEquals(response.body(), MESSAGE);
+            assertEquals(response.headers().allValues("X-Request-Cookie"),
+                    cookies.stream()
+                            .filter(s -> !s.startsWith("LOC"))
+                            .collect(Collectors.toList()));
+            requestBuilder = HttpRequest.newBuilder(uri)
+                    .header("X-uuid", "uuid-" + requestCounter.incrementAndGet());
+            if (version != null) {
+                requestBuilder.version(version);
+            }
+            request = requestBuilder.build();
+        }
+    }
+
+    // -- Infrastructure
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(new CookieValidationHandler(), "/http1/cookie/");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/cookie/retry";
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(new CookieValidationHandler(),"/https1/cookie/");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/cookie/retry";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(new CookieValidationHandler(), "/http2/cookie/");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/cookie/retry";
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(new CookieValidationHandler(), "/https2/cookie/");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/cookie/retry";
+
+
+        // DummyServer
+        httpDummyServer = DummyServer.create(sa);
+        httpsDummyServer = DummyServer.create(sa, sslContext);
+        httpDummy = "http://" + httpDummyServer.serverAuthority() + "/http1/dummy/x";
+        httpsDummy = "https://" + httpsDummyServer.serverAuthority() + "/https1/dummy/x";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+        httpDummyServer.start();
+        httpsDummyServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+        httpsDummyServer.stopServer();
+        httpsDummyServer.stopServer();
+    }
+
+    static class TestCookieHandler extends CookieHandler {
+
+        final ConcurrentHashMap<String, List<String>> cookies;
+        TestCookieHandler(ConcurrentHashMap<String, List<String>> map) {
+            this.cookies = map;
+        }
+
+        @Override
+        public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders)
+                throws IOException
+        {
+            return cookies;
+        }
+
+        @Override
+        public void put(URI uri, Map<String, List<String>> responseHeaders)
+                throws IOException
+        {
+            // do nothing
+        }
+    }
+
+    static class CookieValidationHandler implements HttpTestHandler {
+        ConcurrentHashMap<String,String> closedRequests = new ConcurrentHashMap<>();
+
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            System.out.println("CookieValidationHandler for: " + t.getRequestURI());
+
+            List<String> uuids = t.getRequestHeaders().get("X-uuid");
+            if (uuids == null || uuids.size() != 1) {
+                readAllRequestData(t);
+                try (OutputStream os = t.getResponseBody()) {
+                    String msg = "Incorrect uuid header values:[" + uuids + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                }
+                return;
+            }
+
+            String uuid = uuids.get(0);
+            // retrying
+            if (closedRequests.putIfAbsent(uuid, t.getRequestURI().toString()) == null) {
+                if (t.getExchangeVersion() == HttpClient.Version.HTTP_1_1) {
+                    // Throwing an exception here only causes a retry
+                    // with HTTP_1_1 - where it forces the server to close
+                    // the connection.
+                    // For HTTP/2 then throwing an IOE would cause the server
+                    // to close the stream, and throwing anything else would
+                    // cause it to close the connection, but neither would
+                    // cause the client to retry.
+                    // So we simply do not try to retry with HTTP/2 and just verify
+                    // we have received the expected cookie
+                    throw new IOException("Closing on first request");
+                }
+            }
+
+            // Check whether this request was upgraded.
+            // An upgraded request will have a version of HTTP_2 and
+            // an Upgrade: h2c header
+            HttpClient.Version version = t.getExchangeVersion();
+            List<String> upgrade = t.getRequestHeaders().get("Upgrade");
+            if (upgrade == null) upgrade = List.of();
+            boolean upgraded = version == HttpClient.Version.HTTP_2
+                    && upgrade.stream().anyMatch("h2c"::equalsIgnoreCase);
+
+            // not retrying
+            readAllRequestData(t);
+            try (OutputStream os = t.getResponseBody()) {
+                List<String> cookie = t.getRequestHeaders().get("Cookie");
+                if (cookie != null) {
+                    if (version == HttpClient.Version.HTTP_1_1 || upgraded) {
+                        if (cookie.size() == 1) {
+                            cookie = List.of(cookie.get(0).split("; "));
+                        } else if (cookie.size() > 1) {
+                            String msg = "Found multiple 'Cookie:' lines for version=%s (upgraded=%s): %s";
+                            msg = String.format(msg, version, upgraded, cookie);
+                            (new RuntimeException(msg)).printStackTrace();
+                            t.sendResponseHeaders(500, -1);
+                            os.write(msg.getBytes(UTF_8));
+                            return;
+                        }
+                    }
+                    Collections.sort(cookie = new ArrayList<String>(cookie));
+                }
+                if (cookie == null || cookie.size() == 0) {
+                    String msg = "No cookie header present";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else if (!cookie.get(0).equals("CUSTOMER=ARTHUR_DENT")) {
+                    String msg = "Incorrect cookie header value:[" + cookie.get(0) + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else if (cookie.size() == 2 && !cookie.get(1).equals("ORDER=BISCUITS")) {
+                    String msg = "Incorrect cookie header value:[" + cookie.get(0) + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else if (cookie.size() != 2) {
+                    String msg = "Incorrect cookie header values:[" + cookie + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else {
+                    assert cookie.get(0).equals("CUSTOMER=ARTHUR_DENT");
+                    byte[] bytes = MESSAGE.getBytes(UTF_8);
+                    for (String value : cookie) {
+                        t.getResponseHeaders().addHeader("X-Request-Cookie", value);
+                    }
+                    t.sendResponseHeaders(200, bytes.length);
+                    os.write(bytes);
+                }
+            } finally {
+                closedRequests.remove(uuid);
+            }
+
+        }
+    }
+
+    static void readAllRequestData(HttpTestExchange t) throws IOException {
+        try (InputStream is = t.getRequestBody()) {
+            is.readAllBytes();
+        }
+    }
+
+    static class DummyServer extends Thread {
+        final ServerSocket ss;
+        final boolean secure;
+        ConcurrentLinkedQueue<Socket> connections = new ConcurrentLinkedQueue<>();
+        volatile boolean stopped;
+        DummyServer(ServerSocket ss, boolean secure) {
+            super("DummyServer[" + ss.getLocalPort()+"]");
+            this.secure = secure;
+            this.ss = ss;
+        }
+
+        // This is a bit shaky. It doesn't handle continuation
+        // lines, but our client shouldn't send any.
+        // Read a line from the input stream, swallowing the final
+        // \r\n sequence. Stops at the first \n, doesn't complain
+        // if it wasn't preceded by '\r'.
+        //
+        String readLine(InputStream r) throws IOException {
+            StringBuilder b = new StringBuilder();
+            int c;
+            while ((c = r.read()) != -1) {
+                if (c == '\n') break;
+                b.appendCodePoint(c);
+            }
+            if (b.codePointAt(b.length() -1) == '\r') {
+                b.delete(b.length() -1, b.length());
+            }
+            return b.toString();
+        }
+
+        @Override
+        public void run() {
+            try {
+                while(!stopped) {
+                    Socket clientConnection = ss.accept();
+                    connections.add(clientConnection);
+                    System.out.println(now() + getName() + ": Client accepted");
+                    StringBuilder headers = new StringBuilder();
+                    Socket targetConnection = null;
+                    InputStream  ccis = clientConnection.getInputStream();
+                    OutputStream ccos = clientConnection.getOutputStream();
+                    Writer w = new OutputStreamWriter(
+                            clientConnection.getOutputStream(), "UTF-8");
+                    PrintWriter pw = new PrintWriter(w);
+                    System.out.println(now() + getName() + ": Reading request line");
+                    String requestLine = readLine(ccis);
+                    System.out.println(now() + getName() + ": Request line: " + requestLine);
+
+                    StringTokenizer tokenizer = new StringTokenizer(requestLine);
+                    String method = tokenizer.nextToken();
+                    assert method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("GET");
+                    String path = tokenizer.nextToken();
+                    URI uri;
+                    try {
+                        String hostport = serverAuthority();
+                        uri = new URI((secure ? "https" : "http") +"://" + hostport + path);
+                    } catch (Throwable x) {
+                        System.err.printf("Bad target address: \"%s\" in \"%s\"%n",
+                                path, requestLine);
+                        clientConnection.close();
+                        continue;
+                    }
+
+                    // Read all headers until we find the empty line that
+                    // signals the end of all headers.
+                    String line = requestLine;
+                    String cookies = null;
+                    while (!line.equals("")) {
+                        System.out.println(now() + getName() + ": Reading header: "
+                                + (line = readLine(ccis)));
+                        if (line.startsWith("Cookie:")) {
+                            if (cookies == null) cookies = line;
+                            else cookies = cookies + "\n" + line;
+                        }
+                        headers.append(line).append("\r\n");
+                    }
+
+                    StringBuilder response = new StringBuilder();
+                    StringBuilder xheaders = new StringBuilder();
+
+                    int index = headers.toString()
+                            .toLowerCase(Locale.US)
+                            .indexOf("content-length: ");
+                    if (index >= 0) {
+                        index = index + "content-length: ".length();
+                        String cl = headers.toString().substring(index);
+                        StringTokenizer tk = new StringTokenizer(cl);
+                        int len = Integer.parseInt(tk.nextToken());
+                        System.out.println(now() + getName()
+                                + ": received body: "
+                                + new String(ccis.readNBytes(len), UTF_8));
+                    }
+                    String resp = MESSAGE;
+                    String status = "200 OK";
+                    if (cookies == null) {
+                        resp = "No cookies found in headers";
+                        status = "500 Internal Server Error";
+                    } else if (cookies.contains("\n")) {
+                        resp = "More than one 'Cookie:' line found: "
+                                + Arrays.asList(cookies.split("\n"));
+                        status = "500 Internal Server Error";
+                    } else {
+                        List<String> values =
+                                Stream.of(cookies.substring("Cookie:".length()).trim().split("; "))
+                                        .map(String::trim)
+                                        .collect(Collectors.toList());
+                        Collections.sort(values);
+                        if (values.size() != 2) {
+                            resp = "Bad cookie list: " + values;
+                            status = "500 Internal Server Error";
+                        } else if (!values.get(0).equals("CUSTOMER=ARTHUR_DENT")) {
+                            resp = "Unexpected cookie: " + values.get(0) + " in " + values;
+                            status = "500 Internal Server Error";
+                        } else if (!values.get(1).equals("ORDER=BISCUITS")) {
+                            resp = "Unexpected cookie: " + values.get(1) + " in " + values;
+                            status = "500 Internal Server Error";
+                        } else {
+                            for (String cookie : values) {
+                                xheaders.append("X-Request-Cookie: ")
+                                        .append(cookie)
+                                        .append("\r\n");
+                            }
+                        }
+                    }
+                    byte[] b = resp.getBytes(UTF_8);
+                    System.out.println(now()
+                            + getName() + ": sending back " + uri);
+
+                    response.append("HTTP/1.1 ")
+                            .append(status)
+                            .append("\r\nContent-Length: ")
+                            .append(b.length)
+                            .append("\r\n")
+                            .append(xheaders)
+                            .append("\r\n");
+
+                    // Then send the 200 OK response to the client
+                    System.out.println(now() + getName() + ": Sending "
+                            + response);
+                    pw.print(response);
+                    pw.flush();
+                    ccos.write(b);
+                    ccos.flush();
+                    ccos.close();
+                    connections.remove(clientConnection);
+                    clientConnection.close();
+                }
+            } catch (Throwable t) {
+                if (!stopped) {
+                    System.out.println(now() + getName() + ": failed: " + t);
+                    t.printStackTrace();
+                    try {
+                        stopServer();
+                    } catch (Throwable e) {
+
+                    }
+                }
+            } finally {
+                System.out.println(now() + getName() + ": exiting");
+            }
+        }
+
+        void close(Socket s) {
+            try {
+                s.close();
+            } catch(Throwable t) {
+
+            }
+        }
+        public void stopServer() throws IOException {
+            stopped = true;
+            ss.close();
+            connections.forEach(this::close);
+        }
+
+        public String serverAuthority() {
+            return InetAddress.getLoopbackAddress().getHostName() + ":"
+                    + ss.getLocalPort();
+        }
+
+        static DummyServer create(InetSocketAddress sa) throws IOException {
+            ServerSocket ss = ServerSocketFactory.getDefault()
+                    .createServerSocket(sa.getPort(), -1, sa.getAddress());
+            return  new DummyServer(ss, false);
+        }
+
+        static DummyServer create(InetSocketAddress sa, SSLContext sslContext) throws IOException {
+            ServerSocket ss = sslContext.getServerSocketFactory()
+                    .createServerSocket(sa.getPort(), -1, sa.getAddress());
+            return new DummyServer(ss, true);
+        }
+
+
+    }
+}
--- a/test/jdk/java/net/httpclient/CustomRequestPublisher.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/CustomRequestPublisher.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -25,9 +25,9 @@
  * @test
  * @summary Checks correct handling of Publishers that call onComplete without demand
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
@@ -44,10 +44,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -56,19 +58,23 @@
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import javax.net.ssl.SSLContext;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import javax.net.ssl.SSLSession;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static java.lang.System.out;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+import static java.net.http.HttpClient.Version.HTTP_2;
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.net.http.HttpResponse.BodyHandlers.ofString;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 public class CustomRequestPublisher {
 
@@ -110,6 +116,28 @@
 
     static final int ITERATION_COUNT = 10;
 
+    /** Asserts HTTP Version, and SSLSession presence when applicable. */
+    static void assertVersionAndSession(HttpResponse response, String uri) {
+        if (uri.contains("http2") || uri.contains("https2"))
+            assertEquals(response.version(), HTTP_2);
+        else if (uri.contains("http1") || uri.contains("https1"))
+            assertEquals(response.version(), HTTP_1_1);
+        else
+            fail("Unknown HTTP version in test for: " + uri);
+
+        Optional<SSLSession> ssl = response.sslSession();
+        if (uri.contains("https")) {
+            assertTrue(ssl.isPresent(),
+                    "Expected optional containing SSLSession but got:" + ssl);
+            try {
+                ssl.get().invalidate();
+                fail("SSLSession is not immutable: " + ssl.get());
+            } catch (UnsupportedOperationException expected) { }
+        } else {
+            assertTrue(!ssl.isPresent(), "UNEXPECTED non-empty optional:" + ssl);
+        }
+    }
+
     @Test(dataProvider = "variants")
     void test(String uri, Supplier<BodyPublisher> bpSupplier, boolean sameClient)
             throws Exception
@@ -124,13 +152,15 @@
                     .POST(bodyPublisher)
                     .build();
 
-            HttpResponse<String> resp = client.send(request, asString());
+            HttpResponse<String> resp = client.send(request, ofString());
 
             out.println("Got response: " + resp);
             out.println("Got body: " + resp.body());
             assertTrue(resp.statusCode() == 200,
                     "Expected 200, got:" + resp.statusCode());
             assertEquals(resp.body(), bodyPublisher.bodyAsString());
+
+            assertVersionAndSession(resp, uri);
         }
     }
 
@@ -148,7 +178,7 @@
                     .POST(bodyPublisher)
                     .build();
 
-            CompletableFuture<HttpResponse<String>> cf = client.sendAsync(request, asString());
+            CompletableFuture<HttpResponse<String>> cf = client.sendAsync(request, ofString());
             HttpResponse<String> resp = cf.get();
 
             out.println("Got response: " + resp);
@@ -156,6 +186,8 @@
             assertTrue(resp.statusCode() == 200,
                     "Expected 200, got:" + resp.statusCode());
             assertEquals(resp.body(), bodyPublisher.bodyAsString());
+
+            assertVersionAndSession(resp, uri);
         }
     }
 
@@ -283,31 +315,34 @@
         }
     }
 
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
     @BeforeTest
     public void setup() throws Exception {
         sslContext = new SimpleSSLContext().get();
         if (sslContext == null)
             throw new AssertionError("Unexpected null sslContext");
 
-        InetSocketAddress sa = new InetSocketAddress("localhost", 0);
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         httpTestServer = HttpServer.create(sa, 0);
         httpTestServer.createContext("/http1/echo", new Http1EchoHandler());
-        httpURI = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/echo";
+        httpURI = "http://" + serverAuthority(httpTestServer) + "/http1/echo";
 
         httpsTestServer = HttpsServer.create(sa, 0);
         httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
         httpsTestServer.createContext("/https1/echo", new Http1EchoHandler());
-        httpsURI = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/echo";
+        httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1/echo";
 
-        http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+        http2TestServer = new Http2TestServer("localhost", false, 0);
         http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
-        int port = http2TestServer.getAddress().getPort();
-        http2URI = "http://127.0.0.1:" + port + "/http2/echo";
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/echo";
 
-        https2TestServer = new Http2TestServer("127.0.0.1", true, 0);
+        https2TestServer = new Http2TestServer("localhost", true, 0);
         https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
-        port = https2TestServer.getAddress().getPort();
-        https2URI = "https://127.0.0.1:" + port + "/https2/echo";
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/echo";
 
         httpTestServer.start();
         httpsTestServer.start();
--- a/test/jdk/java/net/httpclient/CustomResponseSubscriber.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/CustomResponseSubscriber.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,14 +27,15 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
  */
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
@@ -48,12 +49,13 @@
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsServer;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import  jdk.incubator.http.HttpResponse.BodySubscriber;
+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.BodyHandler;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
 import javax.net.ssl.SSLContext;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.AfterTest;
@@ -62,7 +64,6 @@
 import org.testng.annotations.Test;
 import static java.lang.System.out;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static jdk.incubator.http.HttpResponse.BodySubscriber.asString;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
@@ -134,50 +135,54 @@
 
     static class CRSBodyHandler implements BodyHandler<String> {
         @Override
-        public BodySubscriber<String> apply(int statusCode, HttpHeaders responseHeaders) {
-            assertEquals(statusCode, 200);
+        public BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
             return new CRSBodySubscriber();
         }
     }
 
     static class CRSBodySubscriber implements BodySubscriber<String> {
-        private final BodySubscriber<String> asString = asString(UTF_8);
+        private final BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
         volatile boolean onSubscribeCalled;
 
         @Override
         public void onSubscribe(Flow.Subscription subscription) {
             //out.println("onSubscribe ");
             onSubscribeCalled = true;
-            asString.onSubscribe(subscription);
+            ofString.onSubscribe(subscription);
         }
 
         @Override
         public void onNext(List<ByteBuffer> item) {
            // out.println("onNext " + item);
             assertTrue(onSubscribeCalled);
-            asString.onNext(item);
+            ofString.onNext(item);
         }
 
         @Override
         public void onError(Throwable throwable) {
             //out.println("onError");
             assertTrue(onSubscribeCalled);
-            asString.onError(throwable);
+            ofString.onError(throwable);
         }
 
         @Override
         public void onComplete() {
             //out.println("onComplete");
             assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
-            asString.onComplete();
+            ofString.onComplete();
         }
 
         @Override
         public CompletionStage<String> getBody() {
-            return asString.getBody();
+            return ofString.getBody();
         }
     }
 
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
 
     @BeforeTest
     public void setup() throws Exception {
@@ -188,37 +193,35 @@
         // HTTP/1.1
         HttpHandler h1_fixedLengthHandler = new HTTP1_FixedLengthHandler();
         HttpHandler h1_chunkHandler = new HTTP1_ChunkedHandler();
-        InetSocketAddress sa = new InetSocketAddress("localhost", 0);
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         httpTestServer = HttpServer.create(sa, 0);
         httpTestServer.createContext("/http1/fixed", h1_fixedLengthHandler);
         httpTestServer.createContext("/http1/chunk", h1_chunkHandler);
-        httpURI_fixed = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/fixed";
-        httpURI_chunk = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/chunk";
+        httpURI_fixed = "http://" + serverAuthority(httpTestServer) + "/http1/fixed";
+        httpURI_chunk = "http://" + serverAuthority(httpTestServer) + "/http1/chunk";
 
         httpsTestServer = HttpsServer.create(sa, 0);
         httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
         httpsTestServer.createContext("/https1/fixed", h1_fixedLengthHandler);
         httpsTestServer.createContext("/https1/chunk", h1_chunkHandler);
-        httpsURI_fixed = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/fixed";
-        httpsURI_chunk = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/chunk";
+        httpsURI_fixed = "https://" + serverAuthority(httpsTestServer) + "/https1/fixed";
+        httpsURI_chunk = "https://" + serverAuthority(httpsTestServer) + "/https1/chunk";
 
         // HTTP/2
         Http2Handler h2_fixedLengthHandler = new HTTP2_FixedLengthHandler();
         Http2Handler h2_chunkedHandler = new HTTP2_VariableHandler();
 
-        http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+        http2TestServer = new Http2TestServer("localhost", false, 0);
         http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
         http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
-        int port = http2TestServer.getAddress().getPort();
-        http2URI_fixed = "http://127.0.0.1:" + port + "/http2/fixed";
-        http2URI_chunk = "http://127.0.0.1:" + port + "/http2/chunk";
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
 
-        https2TestServer = new Http2TestServer("127.0.0.1", true, 0);
+        https2TestServer = new Http2TestServer("localhost", true, 0);
         https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
         https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
-        port = https2TestServer.getAddress().getPort();
-        https2URI_fixed = "https://127.0.0.1:" + port + "/https2/fixed";
-        https2URI_chunk = "https://127.0.0.1:" + port + "/https2/chunk";
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
 
         httpTestServer.start();
         httpsTestServer.start();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/DependentActionsTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,674 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Verify that dependent synchronous actions added before the CF
+ *          completes are executed either asynchronously in an executor when the
+ *          CF later completes, or in the user thread that joins.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters ThrowingPublishers
+ * @modules java.base/sun.net.www.http
+ *          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 DependentActionsTest
+ * @run testng/othervm/java.security.policy=dependent.policy
+  *        -Djdk.internal.httpclient.debug=true DependentActionsTest
+ */
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.StackWalker.StackFrame;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+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.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.System.out;
+import static java.lang.String.format;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class DependentActionsTest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final StackWalker WALKER =
+            StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+
+    static final int ITERATION_COUNT = 1;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+    static volatile boolean tasksFailed;
+    static final AtomicLong serverCount = new AtomicLong();
+    static final AtomicLong clientCount = new AtomicLong();
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    private volatile HttpClient sharedClient;
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    System.out.printf(now() + "Task %s failed: %s%n", id, t);
+                    System.err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.printf("%n%sCreated %d servers and %d clients%n",
+                    now(), serverCount.get(), clientCount.get());
+            if (FAILURES.isEmpty()) return;
+            out.println("Failed tests: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+                e.getValue().printStackTrace();
+            });
+            if (tasksFailed) {
+                System.out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    private String[] uris() {
+        return new String[] {
+                httpURI_fixed,
+                httpURI_chunk,
+                httpsURI_fixed,
+                httpsURI_chunk,
+                http2URI_fixed,
+                http2URI_chunk,
+                https2URI_fixed,
+                https2URI_chunk,
+        };
+    }
+
+    static final class SemaphoreStallerSupplier
+            implements Supplier<SemaphoreStaller> {
+        @Override
+        public SemaphoreStaller get() {
+            return new SemaphoreStaller();
+        }
+        @Override
+        public String toString() {
+            return "SemaphoreStaller";
+        }
+    }
+
+    @DataProvider(name = "noStalls")
+    public Object[][] noThrows() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        int i = 0;
+        for (boolean sameClient : List.of(false, true)) {
+            for (String uri: uris()) {
+                result[i++] = new Object[] {uri, sameClient};
+            }
+        }
+        assert i == uris.length * 2;
+        return result;
+    }
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        int i = 0;
+        Supplier<? extends Staller> s = new SemaphoreStallerSupplier();
+        for (Supplier<? extends Staller> staller : List.of(s)) {
+            for (boolean sameClient : List.of(false, true)) {
+                for (String uri : uris()) {
+                    result[i++] = new Object[]{uri, sameClient, staller};
+                }
+            }
+        }
+        assert i == uris.length * 2;
+        return result;
+    }
+
+    private HttpClient makeNewClient() {
+        clientCount.incrementAndGet();
+        return HttpClient.newBuilder()
+                .executor(executor)
+                .sslContext(sslContext)
+                .build();
+    }
+
+    HttpClient newHttpClient(boolean share) {
+        if (!share) return makeNewClient();
+        HttpClient shared = sharedClient;
+        if (shared != null) return shared;
+        synchronized (this) {
+            shared = sharedClient;
+            if (shared == null) {
+                shared = sharedClient = makeNewClient();
+            }
+            return shared;
+        }
+    }
+
+    @Test(dataProvider = "noStalls")
+    public void testNoStalls(String uri, boolean sameClient)
+            throws Exception {
+        HttpClient client = null;
+        out.printf("%ntestNoStalls(%s, %b)%n", uri, sameClient);
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<String> handler =
+                    new StallingBodyHandler((w) -> {},
+                            BodyHandlers.ofString());
+            HttpResponse<String> response = client.send(req, handler);
+            String body = response.body();
+            assertEquals(URI.create(body).getPath(), URI.create(uri).getPath());
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsStringAsync(String uri,
+                                  boolean sameClient,
+                                  Supplier<Staller> s)
+            throws Exception
+    {
+        Staller staller = s.get();
+        String test = format("testAsStringAsync(%s, %b, %s)",
+                uri, sameClient, staller);
+        testDependent(test, uri, sameClient, BodyHandlers::ofString,
+                this::finish, this::extractString, staller);
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsLinesAsync(String uri,
+                                 boolean sameClient,
+                                 Supplier<Staller> s)
+            throws Exception
+    {
+        Staller staller = s.get();
+        String test = format("testAsLinesAsync(%s, %b, %s)",
+                uri, sameClient, staller);
+        testDependent(test, uri, sameClient, BodyHandlers::ofLines,
+                this::finish, this::extractStream, staller);
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsInputStreamAsync(String uri,
+                                       boolean sameClient,
+                                       Supplier<Staller> s)
+            throws Exception
+    {
+        Staller staller = s.get();
+        String test = format("testAsInputStreamAsync(%s, %b, %s)",
+                uri, sameClient, staller);
+        testDependent(test, uri, sameClient, BodyHandlers::ofInputStream,
+                this::finish, this::extractInputStream, staller);
+    }
+
+    private <T,U> void testDependent(String name, String uri, boolean sameClient,
+                                     Supplier<BodyHandler<T>> handlers,
+                                     Finisher finisher,
+                                     Extractor extractor,
+                                     Staller staller)
+            throws Exception
+    {
+        out.printf("%n%s%s%n", now(), name);
+        try {
+            testDependent(uri, sameClient, handlers, finisher, extractor, staller);
+        } catch (Error | Exception x) {
+            FAILURES.putIfAbsent(name, x);
+            throw x;
+        }
+    }
+
+    private <T,U> void testDependent(String uri, boolean sameClient,
+                                     Supplier<BodyHandler<T>> handlers,
+                                     Finisher finisher,
+                                     Extractor extractor,
+                                     Staller staller)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (Where where : EnumSet.of(Where.BODY_HANDLER)) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.
+                    newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<T> handler =
+                    new StallingBodyHandler(where.select(staller), handlers.get());
+            System.out.println("try stalling in " + where);
+            staller.acquire();
+            assert staller.willStall();
+            CompletableFuture<HttpResponse<T>> responseCF = client.sendAsync(req, handler);
+            assert !responseCF.isDone();
+            finisher.finish(where, responseCF, staller, extractor);
+        }
+    }
+
+    enum Where {
+        BODY_HANDLER, ON_SUBSCRIBE, ON_NEXT, ON_COMPLETE, ON_ERROR, GET_BODY, BODY_CF;
+        public Consumer<Where> select(Consumer<Where> consumer) {
+            return new Consumer<Where>() {
+                @Override
+                public void accept(Where where) {
+                    if (Where.this == where) {
+                        consumer.accept(where);
+                    }
+                }
+            };
+        }
+    }
+
+    interface Extractor<T> {
+        public List<String> extract(HttpResponse<T> resp);
+    }
+
+    final List<String> extractString(HttpResponse<String> resp) {
+        return List.of(resp.body());
+    }
+
+    final List<String> extractStream(HttpResponse<Stream<String>> resp) {
+        return resp.body().collect(Collectors.toList());
+    }
+
+    final List<String> extractInputStream(HttpResponse<InputStream> resp) {
+        try (InputStream is = resp.body()) {
+            return new BufferedReader(new InputStreamReader(is))
+                    .lines().collect(Collectors.toList());
+        } catch (IOException x) {
+            throw new CompletionException(x);
+        }
+    }
+
+    interface Finisher<T> {
+        public void finish(Where w,
+                           CompletableFuture<HttpResponse<T>> cf,
+                           Staller staller,
+                           Extractor extractor);
+    }
+
+    Optional<StackFrame> findFrame(Stream<StackFrame> s, String name) {
+        return s.filter((f) -> f.getClassName().contains(name))
+                .filter((f) -> f.getDeclaringClass().getModule().equals(HttpClient.class.getModule()))
+                .findFirst();
+    }
+
+    <T> void checkThreadAndStack(Thread thread,
+                                 AtomicReference<RuntimeException> failed,
+                                 T result,
+                                 Throwable error) {
+        if (Thread.currentThread() == thread) {
+            //failed.set(new RuntimeException("Dependant action was executed in " + thread));
+            List<StackFrame> httpStack = WALKER.walk(s -> s.filter(f -> f.getDeclaringClass()
+                    .getModule().equals(HttpClient.class.getModule()))
+                    .collect(Collectors.toList()));
+            if (!httpStack.isEmpty()) {
+                System.out.println("Found unexpected trace: ");
+                httpStack.forEach(f -> System.out.printf("\t%s%n", f));
+                failed.set(new RuntimeException("Dependant action has unexpected frame in " +
+                        Thread.currentThread() + ": " + httpStack.get(0)));
+
+            }
+            return;
+        } else if (System.getSecurityManager() != null) {
+            Optional<StackFrame> sf = WALKER.walk(s -> findFrame(s, "PrivilegedRunnable"));
+            if (!sf.isPresent()) {
+                failed.set(new RuntimeException("Dependant action does not have expected frame in "
+                        + Thread.currentThread()));
+                return;
+            } else {
+                System.out.println("Found expected frame: " + sf.get());
+            }
+        } else {
+            List<StackFrame> httpStack = WALKER.walk(s -> s.filter(f -> f.getDeclaringClass()
+                    .getModule().equals(HttpClient.class.getModule()))
+                    .collect(Collectors.toList()));
+            if (!httpStack.isEmpty()) {
+                System.out.println("Found unexpected trace: ");
+                httpStack.forEach(f -> System.out.printf("\t%s%n", f));
+                failed.set(new RuntimeException("Dependant action has unexpected frame in " +
+                        Thread.currentThread() + ": " + httpStack.get(0)));
+
+            }
+        }
+    }
+
+    <T> void finish(Where w, CompletableFuture<HttpResponse<T>> cf,
+                    Staller staller,
+                    Extractor<T> extractor) {
+        Thread thread = Thread.currentThread();
+        AtomicReference<RuntimeException> failed = new AtomicReference<>();
+        CompletableFuture<HttpResponse<T>> done = cf.whenComplete(
+                (r,t) -> checkThreadAndStack(thread, failed, r, t));
+        assert !cf.isDone();
+        try {
+            Thread.sleep(100);
+        } catch (Throwable t) {/* don't care */}
+        assert !cf.isDone();
+        staller.release();
+        try {
+            HttpResponse<T> response = done.join();
+            List<String> result = extractor.extract(response);
+            RuntimeException error = failed.get();
+            if (error != null) {
+                throw new RuntimeException("Test failed in "
+                        + w + ": " + response, error);
+            }
+            assertEquals(result, List.of(response.request().uri().getPath()));
+        } finally {
+            staller.reset();
+        }
+    }
+
+    interface Staller extends Consumer<Where> {
+        void release();
+        void acquire();
+        void reset();
+        boolean willStall();
+    }
+
+    static final class SemaphoreStaller implements Staller {
+        final Semaphore sem = new Semaphore(1);
+        @Override
+        public void accept(Where where) {
+            System.out.println("Acquiring semaphore in "
+                    + where + " permits=" + sem.availablePermits());
+            sem.acquireUninterruptibly();
+            System.out.println("Semaphored acquired in " + where);
+        }
+
+        @Override
+        public void release() {
+            System.out.println("Releasing semaphore: permits="
+                    + sem.availablePermits());
+            sem.release();
+        }
+
+        @Override
+        public void acquire() {
+            sem.acquireUninterruptibly();
+            System.out.println("Semaphored acquired");
+        }
+
+        @Override
+        public void reset() {
+            System.out.println("Reseting semaphore: permits="
+                    + sem.availablePermits());
+            sem.drainPermits();
+            sem.release();
+            System.out.println("Semaphore reset: permits="
+                    + sem.availablePermits());
+        }
+
+        @Override
+        public boolean willStall() {
+            return sem.availablePermits() <= 0;
+        }
+
+        @Override
+        public String toString() {
+            return "SemaphoreStaller";
+        }
+    }
+
+    static final class StallingBodyHandler<T> implements BodyHandler<T> {
+        final Consumer<Where> stalling;
+        final BodyHandler<T> bodyHandler;
+        StallingBodyHandler(Consumer<Where> stalling, BodyHandler<T> bodyHandler) {
+            this.stalling = stalling;
+            this.bodyHandler = bodyHandler;
+        }
+        @Override
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
+            stalling.accept(Where.BODY_HANDLER);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
+            return new StallingBodySubscriber(stalling, subscriber);
+        }
+    }
+
+    static final class StallingBodySubscriber<T> implements BodySubscriber<T> {
+        private final BodySubscriber<T> subscriber;
+        volatile boolean onSubscribeCalled;
+        final Consumer<Where> stalling;
+        StallingBodySubscriber(Consumer<Where> stalling, BodySubscriber<T> subscriber) {
+            this.stalling = stalling;
+            this.subscriber = subscriber;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            //out.println("onSubscribe ");
+            onSubscribeCalled = true;
+            stalling.accept(Where.ON_SUBSCRIBE);
+            subscriber.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            // out.println("onNext " + item);
+            assertTrue(onSubscribeCalled);
+            stalling.accept(Where.ON_NEXT);
+            subscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            //out.println("onError");
+            assertTrue(onSubscribeCalled);
+            stalling.accept(Where.ON_ERROR);
+            subscriber.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            //out.println("onComplete");
+            assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
+            stalling.accept(Where.ON_COMPLETE);
+            subscriber.onComplete();
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            stalling.accept(Where.GET_BODY);
+            try {
+                stalling.accept(Where.BODY_CF);
+            } catch (Throwable t) {
+                return CompletableFuture.failedFuture(t);
+            }
+            return subscriber.getBody();
+        }
+    }
+
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h1_chunkHandler = new HTTP_ChunkedHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(h1_fixedLengthHandler, "/http1/fixed");
+        httpTestServer.addHandler(h1_chunkHandler, "/http1/chunk");
+        httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed/x";
+        httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk/x";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
+        httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
+        httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed/x";
+        httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk/x";
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";
+
+        serverCount.addAndGet(4);
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        sharedClient = null;
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            byte[] resp = t.getRequestURI().getPath().getBytes(StandardCharsets.UTF_8);
+            t.sendResponseHeaders(200, resp.length);  //fixed content length
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+
+    static class HTTP_ChunkedHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());
+            byte[] resp = t.getRequestURI().getPath().toString().getBytes(StandardCharsets.UTF_8);
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, -1); // chunked/variable
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/DependentPromiseActionsTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,761 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Verify that dependent synchronous actions added before the promise CF
+ *          completes are executed either asynchronously in an executor when the
+ *          CF later completes, or in the user thread that joins.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters ThrowingPublishers
+ * @modules java.base/sun.net.www.http
+ *          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 DependentPromiseActionsTest
+ * @run testng/othervm/java.security.policy=dependent.policy
+  *           -Djdk.internal.httpclient.debug=true DependentPromiseActionsTest
+ */
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.StackWalker.StackFrame;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+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.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.System.err;
+import static java.lang.System.out;
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class DependentPromiseActionsTest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final StackWalker WALKER =
+            StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+
+    static final int ITERATION_COUNT = 1;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+    static volatile boolean tasksFailed;
+    static final AtomicLong serverCount = new AtomicLong();
+    static final AtomicLong clientCount = new AtomicLong();
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    private volatile HttpClient sharedClient;
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    System.out.printf(now() + "Task %s failed: %s%n", id, t);
+                    System.err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.printf("%n%sCreated %d servers and %d clients%n",
+                    now(), serverCount.get(), clientCount.get());
+            if (FAILURES.isEmpty()) return;
+            out.println("Failed tests: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+                e.getValue().printStackTrace();
+            });
+            if (tasksFailed) {
+                System.out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    private String[] uris() {
+        return new String[] {
+                http2URI_fixed,
+                http2URI_chunk,
+                https2URI_fixed,
+                https2URI_chunk,
+        };
+    }
+
+    static final class SemaphoreStallerSupplier
+            implements Supplier<SemaphoreStaller> {
+        @Override
+        public SemaphoreStaller get() {
+            return new SemaphoreStaller();
+        }
+        @Override
+        public String toString() {
+            return "SemaphoreStaller";
+        }
+    }
+
+    @DataProvider(name = "noStalls")
+    public Object[][] noThrows() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        int i = 0;
+        for (boolean sameClient : List.of(false, true)) {
+            for (String uri: uris()) {
+                result[i++] = new Object[] {uri, sameClient};
+            }
+        }
+        assert i == uris.length * 2;
+        return result;
+    }
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        int i = 0;
+        Supplier<? extends Staller> s = new SemaphoreStallerSupplier();
+        for (Supplier<? extends Staller> staller : List.of(s)) {
+            for (boolean sameClient : List.of(false, true)) {
+                for (String uri : uris()) {
+                    result[i++] = new Object[]{uri, sameClient, staller};
+                }
+            }
+        }
+        assert i == uris.length * 2;
+        return result;
+    }
+
+    private HttpClient makeNewClient() {
+        clientCount.incrementAndGet();
+        return HttpClient.newBuilder()
+                .executor(executor)
+                .sslContext(sslContext)
+                .build();
+    }
+
+    HttpClient newHttpClient(boolean share) {
+        if (!share) return makeNewClient();
+        HttpClient shared = sharedClient;
+        if (shared != null) return shared;
+        synchronized (this) {
+            shared = sharedClient;
+            if (shared == null) {
+                shared = sharedClient = makeNewClient();
+            }
+            return shared;
+        }
+    }
+
+    @Test(dataProvider = "noStalls")
+    public void testNoStalls(String uri, boolean sameClient)
+            throws Exception {
+        HttpClient client = null;
+        out.printf("%ntestNoStalls(%s, %b)%n", uri, sameClient);
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<Stream<String>> handler =
+                    new StallingBodyHandler((w) -> {},
+                            BodyHandlers.ofLines());
+            Map<HttpRequest, CompletableFuture<HttpResponse<Stream<String>>>> pushPromises =
+                    new ConcurrentHashMap<>();
+            PushPromiseHandler<Stream<String>> pushHandler = new PushPromiseHandler<>() {
+                @Override
+                public void applyPushPromise(HttpRequest initiatingRequest,
+                                             HttpRequest pushPromiseRequest,
+                                             Function<BodyHandler<Stream<String>>,
+                                                     CompletableFuture<HttpResponse<Stream<String>>>>
+                                                     acceptor) {
+                    pushPromises.putIfAbsent(pushPromiseRequest, acceptor.apply(handler));
+                }
+            };
+            HttpResponse<Stream<String>> response =
+                    client.sendAsync(req, BodyHandlers.ofLines(), pushHandler).get();
+            String body = response.body().collect(Collectors.joining("|"));
+            assertEquals(URI.create(body).getPath(), URI.create(uri).getPath());
+            for (HttpRequest promised : pushPromises.keySet()) {
+                out.printf("%s Received promise: %s%n\tresponse: %s%n",
+                        now(), promised, pushPromises.get(promised).get());
+                String promisedBody = pushPromises.get(promised).get().body()
+                        .collect(Collectors.joining("|"));
+                assertEquals(promisedBody, promised.uri().toASCIIString());
+            }
+            assertEquals(3, pushPromises.size());
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsStringAsync(String uri,
+                                  boolean sameClient,
+                                  Supplier<Staller> stallers)
+            throws Exception
+    {
+        String test = format("testAsStringAsync(%s, %b, %s)",
+                uri, sameClient, stallers);
+        testDependent(test, uri, sameClient, BodyHandlers::ofString,
+                this::finish, this::extractString, stallers);
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsLinesAsync(String uri,
+                                 boolean sameClient,
+                                 Supplier<Staller> stallers)
+            throws Exception
+    {
+        String test = format("testAsLinesAsync(%s, %b, %s)",
+                uri, sameClient, stallers);
+        testDependent(test, uri, sameClient, BodyHandlers::ofLines,
+                this::finish, this::extractStream, stallers);
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsInputStreamAsync(String uri,
+                                       boolean sameClient,
+                                       Supplier<Staller> stallers)
+            throws Exception
+    {
+        String test = format("testAsInputStreamAsync(%s, %b, %s)",
+                uri, sameClient, stallers);
+        testDependent(test, uri, sameClient, BodyHandlers::ofInputStream,
+                this::finish, this::extractInputStream, stallers);
+    }
+
+    private <T,U> void testDependent(String name, String uri, boolean sameClient,
+                                     Supplier<BodyHandler<T>> handlers,
+                                     Finisher finisher,
+                                     Extractor<T> extractor,
+                                     Supplier<Staller> stallers)
+            throws Exception
+    {
+        out.printf("%n%s%s%n", now(), name);
+        try {
+            testDependent(uri, sameClient, handlers, finisher, extractor, stallers);
+        } catch (Error | Exception x) {
+            FAILURES.putIfAbsent(name, x);
+            throw x;
+        }
+    }
+
+    private <T,U> void testDependent(String uri, boolean sameClient,
+                                     Supplier<BodyHandler<T>> handlers,
+                                     Finisher finisher,
+                                     Extractor<T> extractor,
+                                     Supplier<Staller> stallers)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (Where where : EnumSet.of(Where.BODY_HANDLER)) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.
+                    newBuilder(URI.create(uri))
+                    .build();
+            StallingPushPromiseHandler<T> promiseHandler =
+                    new StallingPushPromiseHandler<>(where, handlers, stallers);
+            BodyHandler<T> handler = handlers.get();
+            System.out.println("try stalling in " + where);
+            CompletableFuture<HttpResponse<T>> responseCF =
+                    client.sendAsync(req, handler, promiseHandler);
+            assert !responseCF.isDone();
+            finisher.finish(where, responseCF, promiseHandler, extractor);
+        }
+    }
+
+    enum Where {
+        ON_PUSH_PROMISE, BODY_HANDLER, ON_SUBSCRIBE, ON_NEXT, ON_COMPLETE, ON_ERROR, GET_BODY, BODY_CF;
+        public Consumer<Where> select(Consumer<Where> consumer) {
+            return new Consumer<Where>() {
+                @Override
+                public void accept(Where where) {
+                    if (Where.this == where) {
+                        consumer.accept(where);
+                    }
+                }
+            };
+        }
+    }
+
+    static final class StallingPushPromiseHandler<T> implements PushPromiseHandler<T> {
+
+        static final class Tuple<U> {
+            public final CompletableFuture<HttpResponse<U>> response;
+            public final Staller staller;
+            public final AtomicReference<RuntimeException> failed;
+            Tuple(AtomicReference<RuntimeException> failed,
+                  CompletableFuture<HttpResponse<U>> response,
+                  Staller staller) {
+                this.response = response;
+                this.staller = staller;
+                this.failed = failed;
+            }
+        }
+
+        public final ConcurrentMap<HttpRequest, Tuple<T>> promiseMap =
+                new ConcurrentHashMap<>();
+        private final Supplier<Staller> stallers;
+        private final Supplier<BodyHandler<T>> handlers;
+        private final Where where;
+        private final Thread thread = Thread.currentThread(); // main thread
+
+        StallingPushPromiseHandler(Where where,
+                                   Supplier<BodyHandler<T>> handlers,
+                                   Supplier<Staller> stallers) {
+            this.where = where;
+            this.handlers = handlers;
+            this.stallers = stallers;
+        }
+
+        @Override
+        public void applyPushPromise(HttpRequest initiatingRequest,
+                                     HttpRequest pushPromiseRequest,
+                                     Function<BodyHandler<T>,
+                                             CompletableFuture<HttpResponse<T>>> acceptor) {
+            AtomicReference<RuntimeException> failed = new AtomicReference<>();
+            Staller staller = stallers.get();
+            staller.acquire();
+            assert staller.willStall();
+            try {
+                BodyHandler handler = new StallingBodyHandler<>(
+                        where.select(staller), handlers.get());
+                CompletableFuture<HttpResponse<T>> cf = acceptor.apply(handler);
+                Tuple<T> tuple = new Tuple(failed, cf, staller);
+                promiseMap.putIfAbsent(pushPromiseRequest, tuple);
+                CompletableFuture<?> done = cf.whenComplete(
+                        (r, t) -> checkThreadAndStack(thread, failed, r, t));
+                assert !cf.isDone();
+            } finally {
+                staller.release();
+            }
+        }
+    }
+
+    interface Extractor<T> {
+        public List<String> extract(HttpResponse<T> resp);
+    }
+
+    final List<String> extractString(HttpResponse<String> resp) {
+        return List.of(resp.body());
+    }
+
+    final List<String> extractStream(HttpResponse<Stream<String>> resp) {
+        return resp.body().collect(Collectors.toList());
+    }
+
+    final List<String> extractInputStream(HttpResponse<InputStream> resp) {
+        try (InputStream is = resp.body()) {
+            return new BufferedReader(new InputStreamReader(is))
+                    .lines().collect(Collectors.toList());
+        } catch (IOException x) {
+            throw new CompletionException(x);
+        }
+    }
+
+    interface Finisher<T> {
+        public void finish(Where w,
+                           CompletableFuture<HttpResponse<T>> cf,
+                           StallingPushPromiseHandler<T> ph,
+                           Extractor<T> extractor);
+    }
+
+    static Optional<StackFrame> findFrame(Stream<StackFrame> s, String name) {
+        return s.filter((f) -> f.getClassName().contains(name))
+                .filter((f) -> f.getDeclaringClass().getModule().equals(HttpClient.class.getModule()))
+                .findFirst();
+    }
+
+    static <T> void checkThreadAndStack(Thread thread,
+                                        AtomicReference<RuntimeException> failed,
+                                        T result,
+                                        Throwable error) {
+        if (Thread.currentThread() == thread) {
+            //failed.set(new RuntimeException("Dependant action was executed in " + thread));
+            List<StackFrame> httpStack = WALKER.walk(s -> s.filter(f -> f.getDeclaringClass()
+                    .getModule().equals(HttpClient.class.getModule()))
+                    .collect(Collectors.toList()));
+            if (!httpStack.isEmpty()) {
+                System.out.println("Found unexpected trace: ");
+                httpStack.forEach(f -> System.out.printf("\t%s%n", f));
+                failed.set(new RuntimeException("Dependant action has unexpected frame in " +
+                        Thread.currentThread() + ": " + httpStack.get(0)));
+
+            }            return;
+        } else if (System.getSecurityManager() != null) {
+            Optional<StackFrame> sf = WALKER.walk(s -> findFrame(s, "PrivilegedRunnable"));
+            if (!sf.isPresent()) {
+                failed.set(new RuntimeException("Dependant action does not have expected frame in "
+                        + Thread.currentThread()));
+                return;
+            } else {
+                System.out.println("Found expected frame: " + sf.get());
+            }
+        } else {
+            List<StackFrame> httpStack = WALKER.walk(s -> s.filter(f -> f.getDeclaringClass()
+                    .getModule().equals(HttpClient.class.getModule()))
+                    .collect(Collectors.toList()));
+            if (!httpStack.isEmpty()) {
+                System.out.println("Found unexpected trace: ");
+                httpStack.forEach(f -> System.out.printf("\t%s%n", f));
+                failed.set(new RuntimeException("Dependant action has unexpected frame in " +
+                        Thread.currentThread() + ": " + httpStack.get(0)));
+
+            }
+        }
+    }
+
+    <T> void finish(Where w,
+                    StallingPushPromiseHandler.Tuple<T> tuple,
+                    Extractor<T> extractor) {
+        AtomicReference<RuntimeException> failed = tuple.failed;
+        CompletableFuture<HttpResponse<T>> done = tuple.response;
+        Staller staller = tuple.staller;
+        try {
+            HttpResponse<T> response = done.join();
+            List<String> result = extractor.extract(response);
+            URI uri = response.uri();
+            RuntimeException error = failed.get();
+            if (error != null) {
+                throw new RuntimeException("Test failed in "
+                        + w + ": " + uri, error);
+            }
+            assertEquals(result, List.of(response.request().uri().toASCIIString()));
+        } finally {
+            staller.reset();
+        }
+    }
+
+    <T> void finish(Where w,
+                    CompletableFuture<HttpResponse<T>> cf,
+                    StallingPushPromiseHandler<T> ph,
+                    Extractor<T> extractor) {
+        HttpResponse<T> response = cf.join();
+        List<String> result = extractor.extract(response);
+        for (HttpRequest req : ph.promiseMap.keySet()) {
+            finish(w, ph.promiseMap.get(req), extractor);
+        }
+        assertEquals(ph.promiseMap.size(), 3,
+                "Expected 3 push promises for " + w + " in "
+                        + response.request().uri());
+        assertEquals(result, List.of(response.request().uri().toASCIIString()));
+
+    }
+
+    interface Staller extends Consumer<Where> {
+        void release();
+        void acquire();
+        void reset();
+        boolean willStall();
+    }
+
+    static final class SemaphoreStaller implements Staller {
+        final Semaphore sem = new Semaphore(1);
+        @Override
+        public void accept(Where where) {
+            sem.acquireUninterruptibly();
+        }
+
+        @Override
+        public void release() {
+            sem.release();
+        }
+
+        @Override
+        public void acquire() {
+            sem.acquireUninterruptibly();
+        }
+
+        @Override
+        public void reset() {
+            sem.drainPermits();
+            sem.release();
+        }
+
+        @Override
+        public boolean willStall() {
+            return sem.availablePermits() <= 0;
+        }
+
+        @Override
+        public String toString() {
+            return "SemaphoreStaller";
+        }
+    }
+
+    static final class StallingBodyHandler<T> implements BodyHandler<T> {
+        final Consumer<Where> stalling;
+        final BodyHandler<T> bodyHandler;
+        StallingBodyHandler(Consumer<Where> stalling, BodyHandler<T> bodyHandler) {
+            this.stalling = stalling;
+            this.bodyHandler = bodyHandler;
+        }
+        @Override
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
+            stalling.accept(Where.BODY_HANDLER);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
+            return new StallingBodySubscriber(stalling, subscriber);
+        }
+    }
+
+    static final class StallingBodySubscriber<T> implements BodySubscriber<T> {
+        private final BodySubscriber<T> subscriber;
+        volatile boolean onSubscribeCalled;
+        final Consumer<Where> stalling;
+        StallingBodySubscriber(Consumer<Where> stalling, BodySubscriber<T> subscriber) {
+            this.stalling = stalling;
+            this.subscriber = subscriber;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            //out.println("onSubscribe ");
+            onSubscribeCalled = true;
+            stalling.accept(Where.ON_SUBSCRIBE);
+            subscriber.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            // out.println("onNext " + item);
+            assertTrue(onSubscribeCalled);
+            stalling.accept(Where.ON_NEXT);
+            subscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            //out.println("onError");
+            assertTrue(onSubscribeCalled);
+            stalling.accept(Where.ON_ERROR);
+            subscriber.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            //out.println("onComplete");
+            assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
+            stalling.accept(Where.ON_COMPLETE);
+            subscriber.onComplete();
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            stalling.accept(Where.GET_BODY);
+            try {
+                stalling.accept(Where.BODY_CF);
+            } catch (Throwable t) {
+                return CompletableFuture.failedFuture(t);
+            }
+            return subscriber.getBody();
+        }
+    }
+
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/y";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/y";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/y";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/y";
+
+        serverCount.addAndGet(4);
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        sharedClient = null;
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    private static void pushPromiseFor(HttpTestExchange t, URI requestURI, String pushPath, boolean fixed)
+            throws IOException
+    {
+        try {
+            URI promise = new URI(requestURI.getScheme(),
+                    requestURI.getAuthority(),
+                    pushPath, null, null);
+            byte[] promiseBytes = promise.toASCIIString().getBytes(UTF_8);
+            out.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
+            err.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
+            HttpTestHeaders headers =  HttpTestHeaders.of(new HttpHeadersImpl());
+            if (fixed) {
+                headers.addHeader("Content-length", String.valueOf(promiseBytes.length));
+            }
+            t.serverPush(promise, headers, promiseBytes);
+        } catch (URISyntaxException x) {
+            throw new IOException(x.getMessage(), x);
+        }
+    }
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            URI requestURI = t.getRequestURI();
+            for (int i = 1; i<2; i++) {
+                String path = requestURI.getPath() + "/before/promise-" + i;
+                pushPromiseFor(t, requestURI, path, true);
+            }
+            byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);
+            t.sendResponseHeaders(200, resp.length);  //fixed content length
+            try (OutputStream os = t.getResponseBody()) {
+                int bytes = resp.length/3;
+                for (int i = 0; i<2; i++) {
+                    String path = requestURI.getPath() + "/after/promise-" + (i + 2);
+                    os.write(resp, i * bytes, bytes);
+                    os.flush();
+                    pushPromiseFor(t, requestURI, path, true);
+                }
+                os.write(resp, 2*bytes, resp.length - 2*bytes);
+            }
+        }
+
+    }
+
+    static class HTTP_ChunkedHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());
+            byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            URI requestURI = t.getRequestURI();
+            for (int i = 1; i<2; i++) {
+                String path = requestURI.getPath() + "/before/promise-" + i;
+                pushPromiseFor(t, requestURI, path, false);
+            }
+            t.sendResponseHeaders(200, -1); // chunked/variable
+            try (OutputStream os = t.getResponseBody()) {
+                int bytes = resp.length/3;
+                for (int i = 0; i<2; i++) {
+                    String path = requestURI.getPath() + "/after/promise-" + (i + 2);
+                    os.write(resp, i * bytes, bytes);
+                    os.flush();
+                    pushPromiseFor(t, requestURI, path, false);
+                }
+                os.write(resp, 2*bytes, resp.length - 2*bytes);
+            }
+        }
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/DigestEchoClient.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,700 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.math.BigInteger;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublisher;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import sun.net.NetProperties;
+import sun.net.www.HeaderParser;
+import static java.lang.System.out;
+import static java.lang.String.format;
+
+/**
+ * @test
+ * @summary this test verifies that a client may provides authorization
+ *          headers directly when connecting with a server.
+ * @bug 8087112
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters DigestEchoServer
+ *        ReferenceTracker DigestEchoClient
+ * @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
+ *          java.base/sun.net
+ * @run main/othervm DigestEchoClient
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=
+ *                   DigestEchoClient
+ */
+
+public class DigestEchoClient {
+
+    static final String data[] = {
+        "Lorem ipsum",
+        "dolor sit amet",
+        "consectetur adipiscing elit, sed do eiusmod tempor",
+        "quis nostrud exercitation ullamco",
+        "laboris nisi",
+        "ut",
+        "aliquip ex ea commodo consequat." +
+        "Duis aute irure dolor in reprehenderit in voluptate velit esse" +
+        "cillum dolore eu fugiat nulla pariatur.",
+        "Excepteur sint occaecat cupidatat non proident."
+    };
+
+    static final AtomicLong serverCount = new AtomicLong();
+    static final class EchoServers {
+        final DigestEchoServer.HttpAuthType authType;
+        final DigestEchoServer.HttpAuthSchemeType authScheme;
+        final String protocolScheme;
+        final String key;
+        final DigestEchoServer server;
+        final Version serverVersion;
+
+        private EchoServers(DigestEchoServer server,
+                    Version version,
+                    String protocolScheme,
+                    DigestEchoServer.HttpAuthType authType,
+                    DigestEchoServer.HttpAuthSchemeType authScheme) {
+            this.authType = authType;
+            this.authScheme = authScheme;
+            this.protocolScheme = protocolScheme;
+            this.key = key(version, protocolScheme, authType, authScheme);
+            this.server = server;
+            this.serverVersion = version;
+        }
+
+        static String key(Version version,
+                          String protocolScheme,
+                          DigestEchoServer.HttpAuthType authType,
+                          DigestEchoServer.HttpAuthSchemeType authScheme) {
+            return String.format("%s:%s:%s:%s", version, protocolScheme, authType, authScheme);
+        }
+
+        private static EchoServers create(Version version,
+                                   String protocolScheme,
+                                   DigestEchoServer.HttpAuthType authType,
+                                   DigestEchoServer.HttpAuthSchemeType authScheme) {
+            try {
+                serverCount.incrementAndGet();
+                DigestEchoServer server =
+                    DigestEchoServer.create(version, protocolScheme, authType, authScheme);
+                return new EchoServers(server, version, protocolScheme, authType, authScheme);
+            } catch (IOException x) {
+                throw new UncheckedIOException(x);
+            }
+        }
+
+        public static DigestEchoServer of(Version version,
+                                    String protocolScheme,
+                                    DigestEchoServer.HttpAuthType authType,
+                                    DigestEchoServer.HttpAuthSchemeType authScheme) {
+            String key = key(version, protocolScheme, authType, authScheme);
+            return servers.computeIfAbsent(key, (k) ->
+                    create(version, protocolScheme, authType, authScheme)).server;
+        }
+
+        public static void stop() {
+            for (EchoServers s : servers.values()) {
+                s.server.stop();
+            }
+        }
+
+        private static final ConcurrentMap<String, EchoServers> servers = new ConcurrentHashMap<>();
+    }
+
+    final static String PROXY_DISABLED = NetProperties.get("jdk.http.auth.proxying.disabledSchemes");
+    final static String TUNNEL_DISABLED = NetProperties.get("jdk.http.auth.tunneling.disabledSchemes");
+    static {
+        System.out.println("jdk.http.auth.proxying.disabledSchemes=" + PROXY_DISABLED);
+        System.out.println("jdk.http.auth.tunneling.disabledSchemes=" + TUNNEL_DISABLED);
+    }
+
+
+
+    static final AtomicInteger NC = new AtomicInteger();
+    static final Random random = new Random();
+    static final SSLContext context;
+    static {
+        try {
+            context = new SimpleSSLContext().get();
+            SSLContext.setDefault(context);
+        } catch (Exception x) {
+            throw new ExceptionInInitializerError(x);
+        }
+    }
+    static final List<Boolean> BOOLEANS = List.of(true, false);
+
+    final boolean useSSL;
+    final DigestEchoServer.HttpAuthSchemeType authScheme;
+    final DigestEchoServer.HttpAuthType authType;
+    DigestEchoClient(boolean useSSL,
+                     DigestEchoServer.HttpAuthSchemeType authScheme,
+                     DigestEchoServer.HttpAuthType authType)
+            throws IOException {
+        this.useSSL = useSSL;
+        this.authScheme = authScheme;
+        this.authType = authType;
+    }
+
+    static final AtomicLong clientCount = new AtomicLong();
+    static final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+    public HttpClient newHttpClient(DigestEchoServer server) {
+        clientCount.incrementAndGet();
+        HttpClient.Builder builder = HttpClient.newBuilder();
+        builder = builder.proxy(ProxySelector.of(null));
+        if (useSSL) {
+            builder.sslContext(context);
+        }
+        switch (authScheme) {
+            case BASIC:
+                builder = builder.authenticator(DigestEchoServer.AUTHENTICATOR);
+                break;
+            case BASICSERVER:
+                // don't set the authenticator: we will handle the header ourselves.
+                // builder = builder.authenticator(DigestEchoServer.AUTHENTICATOR);
+                break;
+            default:
+                break;
+        }
+        switch (authType) {
+            case PROXY:
+                builder = builder.proxy(ProxySelector.of(server.getProxyAddress()));
+                break;
+            case PROXY305:
+                builder = builder.proxy(ProxySelector.of(server.getProxyAddress()));
+                builder = builder.followRedirects(HttpClient.Redirect.NORMAL);
+                break;
+            case SERVER307:
+                builder = builder.followRedirects(HttpClient.Redirect.NORMAL);
+                break;
+            default:
+                break;
+        }
+        return TRACKER.track(builder.build());
+    }
+
+    public static List<Version> serverVersions(Version clientVersion) {
+        if (clientVersion == Version.HTTP_1_1) {
+            return List.of(clientVersion);
+        } else {
+            return List.of(Version.values());
+        }
+    }
+
+    public static List<Version> clientVersions() {
+        return List.of(Version.values());
+    }
+
+    public static List<Boolean> expectContinue(Version serverVersion) {
+        if (serverVersion == Version.HTTP_1_1) {
+            return BOOLEANS;
+        } else {
+            // our test HTTP/2 server does not support Expect: 100-Continue
+            return List.of(Boolean.FALSE);
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        HttpServerAdapters.enableServerLogging();
+        boolean useSSL = false;
+        EnumSet<DigestEchoServer.HttpAuthType> types =
+                EnumSet.complementOf(EnumSet.of(DigestEchoServer.HttpAuthType.PROXY305));
+        Throwable failed = null;
+        if (args != null && args.length >= 1) {
+            useSSL = "SSL".equals(args[0]);
+            if (args.length > 1) {
+                List<DigestEchoServer.HttpAuthType> httpAuthTypes =
+                        Stream.of(Arrays.copyOfRange(args, 1, args.length))
+                                .map(DigestEchoServer.HttpAuthType::valueOf)
+                                .collect(Collectors.toList());
+                types = EnumSet.copyOf(httpAuthTypes);
+            }
+        }
+        try {
+            for (DigestEchoServer.HttpAuthType authType : types) {
+                // The test server does not support PROXY305 properly
+                if (authType == DigestEchoServer.HttpAuthType.PROXY305) continue;
+                EnumSet<DigestEchoServer.HttpAuthSchemeType> basics =
+                        EnumSet.of(DigestEchoServer.HttpAuthSchemeType.BASICSERVER,
+                                DigestEchoServer.HttpAuthSchemeType.BASIC);
+                for (DigestEchoServer.HttpAuthSchemeType authScheme : basics) {
+                    DigestEchoClient dec = new DigestEchoClient(useSSL,
+                            authScheme,
+                            authType);
+                    for (Version clientVersion : clientVersions()) {
+                        for (Version serverVersion : serverVersions(clientVersion)) {
+                            for (boolean expectContinue : expectContinue(serverVersion)) {
+                                for (boolean async : BOOLEANS) {
+                                    for (boolean preemptive : BOOLEANS) {
+                                        dec.testBasic(clientVersion,
+                                                serverVersion, async,
+                                                expectContinue, preemptive);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                EnumSet<DigestEchoServer.HttpAuthSchemeType> digests =
+                        EnumSet.of(DigestEchoServer.HttpAuthSchemeType.DIGEST);
+                for (DigestEchoServer.HttpAuthSchemeType authScheme : digests) {
+                    DigestEchoClient dec = new DigestEchoClient(useSSL,
+                            authScheme,
+                            authType);
+                    for (Version clientVersion : clientVersions()) {
+                        for (Version serverVersion : serverVersions(clientVersion)) {
+                            for (boolean expectContinue : expectContinue(serverVersion)) {
+                                for (boolean async : BOOLEANS) {
+                                    dec.testDigest(clientVersion, serverVersion,
+                                            async, expectContinue);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch(Throwable t) {
+            out.println(DigestEchoServer.now()
+                    + ": Unexpected exception: " + t);
+            t.printStackTrace();
+            failed = t;
+            throw t;
+        } finally {
+            Thread.sleep(100);
+            AssertionError trackFailed = TRACKER.check(500);
+            EchoServers.stop();
+            System.out.println(" ---------------------------------------------------------- ");
+            System.out.println(String.format("DigestEchoClient %s %s", useSSL ? "SSL" : "CLEAR", types));
+            System.out.println(String.format("Created %d clients and %d servers",
+                    clientCount.get(), serverCount.get()));
+            System.out.println(String.format("basics:  %d requests sent, %d ns / req",
+                    basicCount.get(), basics.get()));
+            System.out.println(String.format("digests: %d requests sent, %d ns / req",
+                    digestCount.get(), digests.get()));
+            System.out.println(" ---------------------------------------------------------- ");
+            if (trackFailed != null) {
+                if (failed != null) {
+                    failed.addSuppressed(trackFailed);
+                    if (failed instanceof Error) throw (Error) failed;
+                    if (failed instanceof Exception) throw (Exception) failed;
+                }
+                throw trackFailed;
+            }
+        }
+    }
+
+    boolean isSchemeDisabled() {
+        String disabledSchemes;
+        if (isProxy(authType)) {
+            disabledSchemes = useSSL
+                    ? TUNNEL_DISABLED
+                    : PROXY_DISABLED;
+        } else return false;
+        if (disabledSchemes == null
+                || disabledSchemes.isEmpty()) {
+            return false;
+        }
+        String scheme;
+        switch (authScheme) {
+            case DIGEST:
+                scheme = "Digest";
+                break;
+            case BASIC:
+                scheme = "Basic";
+                break;
+            case BASICSERVER:
+                scheme = "Basic";
+                break;
+            case NONE:
+                return false;
+            default:
+                throw new InternalError("Unknown auth scheme: " + authScheme);
+        }
+        return Stream.of(disabledSchemes.split(","))
+                .map(String::trim)
+                .filter(scheme::equalsIgnoreCase)
+                .findAny()
+                .isPresent();
+    }
+
+    final static AtomicLong basics = new AtomicLong();
+    final static AtomicLong basicCount = new AtomicLong();
+    // @Test
+    void testBasic(Version clientVersion, Version serverVersion, boolean async,
+                   boolean expectContinue, boolean preemptive)
+        throws Exception
+    {
+        final boolean addHeaders = authScheme == DigestEchoServer.HttpAuthSchemeType.BASICSERVER;
+        // !preemptive has no meaning if we don't handle the authorization
+        // headers ourselves
+        if (!preemptive && !addHeaders) return;
+
+        out.println(format("*** testBasic: client: %s, server: %s, async: %s, useSSL: %s, " +
+                        "authScheme: %s, authType: %s, expectContinue: %s preemptive: %s***",
+                clientVersion, serverVersion, async, useSSL, authScheme, authType,
+                expectContinue, preemptive));
+
+        DigestEchoServer server = EchoServers.of(serverVersion,
+                useSSL ? "https" : "http", authType, authScheme);
+        URI uri = DigestEchoServer.uri(useSSL ? "https" : "http",
+                server.getServerAddress(), "/foo/");
+
+        HttpClient client = newHttpClient(server);
+        HttpResponse<String> r;
+        CompletableFuture<HttpResponse<String>> cf1;
+        String auth = null;
+
+        try {
+            for (int i=0; i<data.length; i++) {
+                out.println(DigestEchoServer.now() + " ----- iteration " + i + " -----");
+                List<String> lines = List.of(Arrays.copyOfRange(data, 0, i+1));
+                assert lines.size() == i + 1;
+                String body = lines.stream().collect(Collectors.joining("\r\n"));
+                BodyPublisher reqBody = BodyPublishers.ofString(body);
+                HttpRequest.Builder builder = HttpRequest.newBuilder(uri).version(clientVersion)
+                        .POST(reqBody).expectContinue(expectContinue);
+                boolean isTunnel = isProxy(authType) && useSSL;
+                if (addHeaders) {
+                    // handle authentication ourselves
+                    assert !client.authenticator().isPresent();
+                    if (auth == null) auth = "Basic " + getBasicAuth("arthur");
+                    try {
+                        if ((i > 0 || preemptive)
+                                && (!isTunnel || i == 0 || isSchemeDisabled())) {
+                            // In case of a SSL tunnel through proxy then only the
+                            // first request should require proxy authorization
+                            // Though this might be invalidated if the server decides
+                            // to close the connection...
+                            out.println(String.format("%s adding %s: %s",
+                                    DigestEchoServer.now(),
+                                    authorizationKey(authType),
+                                    auth));
+                            builder = builder.header(authorizationKey(authType), auth);
+                        }
+                    } catch (IllegalArgumentException x) {
+                        throw x;
+                    }
+                } else {
+                    // let the stack do the authentication
+                    assert client.authenticator().isPresent();
+                }
+                long start = System.nanoTime();
+                HttpRequest request = builder.build();
+                HttpResponse<Stream<String>> resp;
+                try {
+                    if (async) {
+                        resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+                    } else {
+                        resp = client.send(request, BodyHandlers.ofLines());
+                    }
+                } catch (Throwable t) {
+                    long stop = System.nanoTime();
+                    synchronized (basicCount) {
+                        long n = basicCount.getAndIncrement();
+                        basics.set((basics.get() * n + (stop - start)) / (n + 1));
+                    }
+                    // unwrap CompletionException
+                    if (t instanceof CompletionException) {
+                        assert t.getCause() != null;
+                        t = t.getCause();
+                    }
+                    out.println(DigestEchoServer.now()
+                            + ": Unexpected exception: " + t);
+                    throw new RuntimeException("Unexpected exception: " + t, t);
+                }
+
+                if (addHeaders && !preemptive && (i==0 || isSchemeDisabled())) {
+                    assert resp.statusCode() == 401 || resp.statusCode() == 407;
+                    Stream<String> respBody = resp.body();
+                    if (respBody != null) {
+                        System.out.printf("Response body (%s):\n", resp.statusCode());
+                        respBody.forEach(System.out::println);
+                    }
+                    System.out.println(String.format("%s received: adding header %s: %s",
+                            resp.statusCode(), authorizationKey(authType), auth));
+                    request = HttpRequest.newBuilder(uri).version(clientVersion)
+                            .POST(reqBody).header(authorizationKey(authType), auth).build();
+                    if (async) {
+                        resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+                    } else {
+                        resp = client.send(request, BodyHandlers.ofLines());
+                    }
+                }
+                final List<String> respLines;
+                try {
+                    if (isSchemeDisabled()) {
+                        if (resp.statusCode() != 407) {
+                            throw new RuntimeException("expected 407 not received");
+                        }
+                        System.out.println("Scheme disabled for [" + authType
+                                + ", " + authScheme
+                                + ", " + (useSSL ? "HTTP" : "HTTPS")
+                                + "]: Received expected " + resp.statusCode());
+                        continue;
+                    } else {
+                        System.out.println("Scheme enabled for [" + authType
+                                + ", " + authScheme
+                                + ", " + (useSSL ? "HTTPS" : "HTTP")
+                                + "]: Expecting 200, response is: " + resp);
+                        assert resp.statusCode() == 200 : "200 expected, received " + resp;
+                        respLines = resp.body().collect(Collectors.toList());
+                    }
+                } finally {
+                    long stop = System.nanoTime();
+                    synchronized (basicCount) {
+                        long n = basicCount.getAndIncrement();
+                        basics.set((basics.get() * n + (stop - start)) / (n + 1));
+                    }
+                }
+                if (!lines.equals(respLines)) {
+                    throw new RuntimeException("Unexpected response: " + respLines);
+                }
+            }
+        } finally {
+        }
+        System.out.println("OK");
+    }
+
+    String getBasicAuth(String username) {
+        StringBuilder builder = new StringBuilder(username);
+        builder.append(':');
+        for (char c : DigestEchoServer.AUTHENTICATOR.getPassword(username)) {
+            builder.append(c);
+        }
+        return Base64.getEncoder().encodeToString(builder.toString().getBytes(StandardCharsets.UTF_8));
+    }
+
+    final static AtomicLong digests = new AtomicLong();
+    final static AtomicLong digestCount = new AtomicLong();
+    // @Test
+    void testDigest(Version clientVersion, Version serverVersion,
+                    boolean async, boolean expectContinue)
+            throws Exception
+    {
+        String test = format("testDigest: client: %s, server: %s, async: %s, useSSL: %s, " +
+                             "authScheme: %s, authType: %s, expectContinue: %s",
+                              clientVersion, serverVersion, async, useSSL,
+                              authScheme, authType, expectContinue);
+        out.println("*** " + test + " ***");
+        DigestEchoServer server = EchoServers.of(serverVersion,
+                useSSL ? "https" : "http", authType, authScheme);
+
+        URI uri = DigestEchoServer.uri(useSSL ? "https" : "http",
+                server.getServerAddress(), "/foo/");
+
+        HttpClient client = newHttpClient(server);
+        HttpResponse<String> r;
+        CompletableFuture<HttpResponse<String>> cf1;
+        byte[] cnonce = new byte[16];
+        String cnonceStr = null;
+        DigestEchoServer.DigestResponse challenge = null;
+
+        try {
+            for (int i=0; i<data.length; i++) {
+                out.println(DigestEchoServer.now() + "----- iteration " + i + " -----");
+                List<String> lines = List.of(Arrays.copyOfRange(data, 0, i+1));
+                assert lines.size() == i + 1;
+                String body = lines.stream().collect(Collectors.joining("\r\n"));
+                HttpRequest.BodyPublisher reqBody = HttpRequest.BodyPublishers.ofString(body);
+                HttpRequest.Builder reqBuilder = HttpRequest
+                        .newBuilder(uri).version(clientVersion).POST(reqBody)
+                        .expectContinue(expectContinue);
+
+                boolean isTunnel = isProxy(authType) && useSSL;
+                String digestMethod = isTunnel ? "CONNECT" : "POST";
+
+                // In case of a tunnel connection only the first request
+                // which establishes the tunnel needs to authenticate with
+                // the proxy.
+                if (challenge != null && (!isTunnel || isSchemeDisabled())) {
+                    assert cnonceStr != null;
+                    String auth = digestResponse(uri, digestMethod, challenge, cnonceStr);
+                    try {
+                        reqBuilder = reqBuilder.header(authorizationKey(authType), auth);
+                    } catch (IllegalArgumentException x) {
+                        throw x;
+                    }
+                }
+
+                long start = System.nanoTime();
+                HttpRequest request = reqBuilder.build();
+                HttpResponse<Stream<String>> resp;
+                if (async) {
+                    resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+                } else {
+                    resp = client.send(request, BodyHandlers.ofLines());
+                }
+                System.out.println(resp);
+                assert challenge != null || resp.statusCode() == 401 || resp.statusCode() == 407
+                        : "challenge=" + challenge + ", resp=" + resp + ", test=[" + test + "]";
+                if (resp.statusCode() == 401 || resp.statusCode() == 407) {
+                    // This assert may need to be relaxed if our server happened to
+                    // decide to close the tunnel connection, in which case we would
+                    // receive 407 again...
+                    assert challenge == null || !isTunnel || isSchemeDisabled()
+                            : "No proxy auth should be required after establishing an SSL tunnel";
+
+                    System.out.println("Received " + resp.statusCode() + " answering challenge...");
+                    random.nextBytes(cnonce);
+                    cnonceStr = new BigInteger(1, cnonce).toString(16);
+                    System.out.println("Response headers: " + resp.headers());
+                    Optional<String> authenticateOpt = resp.headers().firstValue(authenticateKey(authType));
+                    String authenticate = authenticateOpt.orElseThrow(
+                            () -> new RuntimeException(authenticateKey(authType) + ": not found"));
+                    assert authenticate.startsWith("Digest ");
+                    HeaderParser hp = new HeaderParser(authenticate.substring("Digest ".length()));
+                    String qop = hp.findValue("qop");
+                    String nonce = hp.findValue("nonce");
+                    if (qop == null && nonce == null) {
+                        throw new RuntimeException("QOP and NONCE not found");
+                    }
+                    challenge = DigestEchoServer.DigestResponse
+                            .create(authenticate.substring("Digest ".length()));
+                    String auth = digestResponse(uri, digestMethod, challenge, cnonceStr);
+                    try {
+                        request = HttpRequest.newBuilder(uri).version(clientVersion)
+                            .POST(reqBody).header(authorizationKey(authType), auth).build();
+                    } catch (IllegalArgumentException x) {
+                        throw x;
+                    }
+
+                    if (async) {
+                        resp = client.sendAsync(request, BodyHandlers.ofLines()).join();
+                    } else {
+                        resp = client.send(request, BodyHandlers.ofLines());
+                    }
+                    System.out.println(resp);
+                }
+                final List<String> respLines;
+                try {
+                    if (isSchemeDisabled()) {
+                        if (resp.statusCode() != 407) {
+                            throw new RuntimeException("expected 407 not received");
+                        }
+                        System.out.println("Scheme disabled for [" + authType
+                                + ", " + authScheme +
+                                ", " + (useSSL ? "HTTP" : "HTTPS")
+                                + "]: Received expected " + resp.statusCode());
+                        continue;
+                    } else {
+                        assert resp.statusCode() == 200;
+                        respLines = resp.body().collect(Collectors.toList());
+                    }
+                } finally {
+                    long stop = System.nanoTime();
+                    synchronized (digestCount) {
+                        long n = digestCount.getAndIncrement();
+                        digests.set((digests.get() * n + (stop - start)) / (n + 1));
+                    }
+                }
+                if (!lines.equals(respLines)) {
+                    throw new RuntimeException("Unexpected response: " + respLines);
+                }
+            }
+        } finally {
+        }
+        System.out.println("OK");
+    }
+
+    // WARNING: This is not a full fledged implementation of DIGEST.
+    // It does contain bugs and inaccuracy.
+    static String digestResponse(URI uri, String method, DigestEchoServer.DigestResponse challenge, String cnonce)
+            throws NoSuchAlgorithmException {
+        int nc = NC.incrementAndGet();
+        DigestEchoServer.DigestResponse response1 = new DigestEchoServer.DigestResponse("earth",
+                "arthur", challenge.nonce, cnonce, String.valueOf(nc), uri.toASCIIString(),
+                challenge.algorithm, challenge.qop, challenge.opaque, null);
+        String response = DigestEchoServer.DigestResponse.computeDigest(true, method,
+                DigestEchoServer.AUTHENTICATOR.getPassword("arthur"), response1);
+        String auth = "Digest username=\"arthur\", realm=\"earth\""
+                + ", response=\"" + response + "\", uri=\""+uri.toASCIIString()+"\""
+                + ", qop=\"" + response1.qop + "\", cnonce=\"" + response1.cnonce
+                + "\", nc=\"" + nc + "\", nonce=\"" + response1.nonce + "\"";
+        if (response1.opaque != null) {
+            auth = auth + ", opaque=\"" + response1.opaque + "\"";
+        }
+        return auth;
+    }
+
+    static String authenticateKey(DigestEchoServer.HttpAuthType authType) {
+        switch (authType) {
+            case SERVER: return "www-authenticate";
+            case SERVER307: return "www-authenticate";
+            case PROXY: return "proxy-authenticate";
+            case PROXY305: return "proxy-authenticate";
+            default: throw new InternalError("authType: " + authType);
+        }
+    }
+
+    static String authorizationKey(DigestEchoServer.HttpAuthType authType) {
+        switch (authType) {
+            case SERVER: return "authorization";
+            case SERVER307: return "Authorization";
+            case PROXY: return "Proxy-Authorization";
+            case PROXY305: return "proxy-Authorization";
+            default: throw new InternalError("authType: " + authType);
+        }
+    }
+
+    static boolean isProxy(DigestEchoServer.HttpAuthType authType) {
+        switch (authType) {
+            case SERVER: return false;
+            case SERVER307: return false;
+            case PROXY: return true;
+            case PROXY305: return true;
+            default: throw new InternalError("authType: " + authType);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/DigestEchoClientSSL.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @summary this test verifies that a client may provides authorization
+ *          headers directly when connecting with a server over SSL.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer
+ *        DigestEchoClient ReferenceTracker DigestEchoClientSSL
+ * @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
+ *          java.base/sun.net
+ * @run main/othervm DigestEchoClientSSL SSL
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=
+ *                   DigestEchoClientSSL SSL PROXY
+ */
+
+public class DigestEchoClientSSL {
+    public static void main(String[] args) throws Exception {
+        assert "SSL".equals(args[0]);
+        DigestEchoClient.main(args);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/DigestEchoServer.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,1774 @@
+/*
+ * 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.
+ */
+
+import com.sun.net.httpserver.BasicAuthenticator;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.math.BigInteger;
+import java.net.Authenticator;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.PasswordAuthentication;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLContext;
+import sun.net.www.HeaderParser;
+import java.net.http.HttpClient.Version;
+
+/**
+ * A simple HTTP server that supports Basic or Digest authentication.
+ * By default this server will echo back whatever is present
+ * in the request body. Note that the Digest authentication is
+ * a test implementation implemented only for tests purposes.
+ * @author danielfuchs
+ */
+public abstract class DigestEchoServer implements HttpServerAdapters {
+
+    public static final boolean DEBUG =
+            Boolean.parseBoolean(System.getProperty("test.debug", "false"));
+    public enum HttpAuthType {
+        SERVER, PROXY, SERVER307, PROXY305
+        /* add PROXY_AND_SERVER and SERVER_PROXY_NONE */
+    };
+    public enum HttpAuthSchemeType { NONE, BASICSERVER, BASIC, DIGEST };
+    public static final HttpAuthType DEFAULT_HTTP_AUTH_TYPE = HttpAuthType.SERVER;
+    public static final String DEFAULT_PROTOCOL_TYPE = "https";
+    public static final HttpAuthSchemeType DEFAULT_SCHEME_TYPE = HttpAuthSchemeType.DIGEST;
+
+    public static class HttpTestAuthenticator extends Authenticator {
+        private final String realm;
+        private final String username;
+        // Used to prevent incrementation of 'count' when calling the
+        // authenticator from the server side.
+        private final ThreadLocal<Boolean> skipCount = new ThreadLocal<>();
+        // count will be incremented every time getPasswordAuthentication()
+        // is called from the client side.
+        final AtomicInteger count = new AtomicInteger();
+
+        public HttpTestAuthenticator(String realm, String username) {
+            this.realm = realm;
+            this.username = username;
+        }
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            if (skipCount.get() == null || skipCount.get().booleanValue() == false) {
+                System.out.println("Authenticator called: " + count.incrementAndGet());
+            }
+            return new PasswordAuthentication(getUserName(),
+                    new char[] {'d','e','n', 't'});
+        }
+        // Called by the server side to get the password of the user
+        // being authentified.
+        public final char[] getPassword(String user) {
+            if (user.equals(username)) {
+                skipCount.set(Boolean.TRUE);
+                try {
+                    return getPasswordAuthentication().getPassword();
+                } finally {
+                    skipCount.set(Boolean.FALSE);
+                }
+            }
+            throw new SecurityException("User unknown: " + user);
+        }
+        public final String getUserName() {
+            return username;
+        }
+        public final String getRealm() {
+            return realm;
+        }
+    }
+
+    public static final HttpTestAuthenticator AUTHENTICATOR;
+    static {
+        AUTHENTICATOR = new HttpTestAuthenticator("earth", "arthur");
+    }
+
+
+    final HttpTestServer       serverImpl; // this server endpoint
+    final DigestEchoServer     redirect;   // the target server where to redirect 3xx
+    final HttpTestHandler      delegate;   // unused
+    final String               key;
+
+    DigestEchoServer(String key,
+                             HttpTestServer server,
+                             DigestEchoServer target,
+                             HttpTestHandler delegate) {
+        this.key = key;
+        this.serverImpl = server;
+        this.redirect = target;
+        this.delegate = delegate;
+    }
+
+    public static void main(String[] args)
+            throws IOException {
+
+        DigestEchoServer server = create(Version.HTTP_1_1,
+                DEFAULT_PROTOCOL_TYPE,
+                DEFAULT_HTTP_AUTH_TYPE,
+                AUTHENTICATOR,
+                DEFAULT_SCHEME_TYPE);
+        try {
+            System.out.println("Server created at " + server.getAddress());
+            System.out.println("Strike <Return> to exit");
+            System.in.read();
+        } finally {
+            System.out.println("stopping server");
+            server.stop();
+        }
+    }
+
+    private static String toString(HttpTestHeaders headers) {
+        return headers.entrySet().stream()
+                .map((e) -> e.getKey() + ": " + e.getValue())
+                .collect(Collectors.joining("\n"));
+    }
+
+    public static DigestEchoServer create(Version version,
+                                          String protocol,
+                                          HttpAuthType authType,
+                                          HttpAuthSchemeType schemeType)
+            throws IOException {
+        return create(version, protocol, authType, AUTHENTICATOR, schemeType);
+    }
+
+    public static DigestEchoServer create(Version version,
+                                          String protocol,
+                                          HttpAuthType authType,
+                                          HttpTestAuthenticator auth,
+                                          HttpAuthSchemeType schemeType)
+            throws IOException {
+        return create(version, protocol, authType, auth, schemeType, null);
+    }
+
+    public static DigestEchoServer create(Version version,
+                                        String protocol,
+                                        HttpAuthType authType,
+                                        HttpTestAuthenticator auth,
+                                        HttpAuthSchemeType schemeType,
+                                        HttpTestHandler delegate)
+            throws IOException {
+        Objects.requireNonNull(authType);
+        Objects.requireNonNull(auth);
+        switch(authType) {
+            // A server that performs Server Digest authentication.
+            case SERVER: return createServer(version, protocol, authType, auth,
+                                             schemeType, delegate, "/");
+            // A server that pretends to be a Proxy and performs
+            // Proxy Digest authentication. If protocol is HTTPS,
+            // then this will create a HttpsProxyTunnel that will
+            // handle the CONNECT request for tunneling.
+            case PROXY: return createProxy(version, protocol, authType, auth,
+                                           schemeType, delegate, "/");
+            // A server that sends 307 redirect to a server that performs
+            // Digest authentication.
+            // Note: 301 doesn't work here because it transforms POST into GET.
+            case SERVER307: return createServerAndRedirect(version,
+                                                        protocol,
+                                                        HttpAuthType.SERVER,
+                                                        auth, schemeType,
+                                                        delegate, 307);
+            // A server that sends 305 redirect to a proxy that performs
+            // Digest authentication.
+            // Note: this is not correctly stubbed/implemented in this test.
+            case PROXY305:  return createServerAndRedirect(version,
+                                                        protocol,
+                                                        HttpAuthType.PROXY,
+                                                        auth, schemeType,
+                                                        delegate, 305);
+            default:
+                throw new InternalError("Unknown server type: " + authType);
+        }
+    }
+
+
+    /**
+     * The SocketBindableFactory ensures that the local port used by an HttpServer
+     * or a proxy ServerSocket previously created by the current test/VM will not
+     * get reused by a subsequent test in the same VM.
+     * This is to avoid having the test client trying to reuse cached connections.
+     */
+    private static abstract class SocketBindableFactory<B> {
+        private static final int MAX = 10;
+        private static final CopyOnWriteArrayList<String> addresses =
+                new CopyOnWriteArrayList<>();
+        protected B createInternal() throws IOException {
+            final int max = addresses.size() + MAX;
+            final List<B> toClose = new ArrayList<>();
+            try {
+                for (int i = 1; i <= max; i++) {
+                    B bindable = createBindable();
+                    InetSocketAddress address = getAddress(bindable);
+                    String key = "localhost:" + address.getPort();
+                    if (addresses.addIfAbsent(key)) {
+                        System.out.println("Socket bound to: " + key
+                                + " after " + i + " attempt(s)");
+                        return bindable;
+                    }
+                    System.out.println("warning: address " + key
+                            + " already used. Retrying bind.");
+                    // keep the port bound until we get a port that we haven't
+                    // used already
+                    toClose.add(bindable);
+                }
+            } finally {
+                // if we had to retry, then close the socket we're not
+                // going to use.
+                for (B b : toClose) {
+                    try { close(b); } catch (Exception x) { /* ignore */ }
+                }
+            }
+            throw new IOException("Couldn't bind socket after " + max + " attempts: "
+                    + "addresses used before: " + addresses);
+        }
+
+        protected abstract B createBindable() throws IOException;
+
+        protected abstract InetSocketAddress getAddress(B bindable);
+
+        protected abstract void close(B bindable) throws IOException;
+    }
+
+    /*
+     * Used to create ServerSocket for a proxy.
+     */
+    private static final class ServerSocketFactory
+    extends SocketBindableFactory<ServerSocket> {
+        private static final ServerSocketFactory instance = new ServerSocketFactory();
+
+        static ServerSocket create() throws IOException {
+            return instance.createInternal();
+        }
+
+        @Override
+        protected ServerSocket createBindable() throws IOException {
+            ServerSocket ss = new ServerSocket();
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+            return ss;
+        }
+
+        @Override
+        protected InetSocketAddress getAddress(ServerSocket socket) {
+            return new InetSocketAddress(socket.getInetAddress(), socket.getLocalPort());
+        }
+
+        @Override
+        protected void close(ServerSocket socket) throws IOException {
+            socket.close();
+        }
+    }
+
+    /*
+     * Used to create HttpServer
+     */
+    private static abstract class H1ServerFactory<S extends HttpServer>
+            extends SocketBindableFactory<S> {
+        @Override
+        protected S createBindable() throws IOException {
+            S server = newHttpServer();
+            server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+            return server;
+        }
+
+        @Override
+        protected InetSocketAddress getAddress(S server) {
+            return server.getAddress();
+        }
+
+        @Override
+        protected void close(S server) throws IOException {
+            server.stop(1);
+        }
+
+        /*
+         * Returns a HttpServer or a HttpsServer in different subclasses.
+         */
+        protected abstract S newHttpServer() throws IOException;
+    }
+
+    /*
+     * Used to create Http2TestServer
+     */
+    private static abstract class H2ServerFactory<S extends Http2TestServer>
+            extends SocketBindableFactory<S> {
+        @Override
+        protected S createBindable() throws IOException {
+            final S server;
+            try {
+                server = newHttpServer();
+            } catch (IOException io) {
+                throw io;
+            } catch (Exception x) {
+                throw new IOException(x);
+            }
+            return server;
+        }
+
+        @Override
+        protected InetSocketAddress getAddress(S server) {
+            return server.getAddress();
+        }
+
+        @Override
+        protected void close(S server) throws IOException {
+            server.stop();
+        }
+
+        /*
+         * Returns a HttpServer or a HttpsServer in different subclasses.
+         */
+        protected abstract S newHttpServer() throws Exception;
+    }
+
+    private static final class Http2ServerFactory extends H2ServerFactory<Http2TestServer> {
+        private static final Http2ServerFactory instance = new Http2ServerFactory();
+
+        static Http2TestServer create() throws IOException {
+            return instance.createInternal();
+        }
+
+        @Override
+        protected Http2TestServer newHttpServer() throws Exception {
+            return new Http2TestServer("localhost", false, 0);
+        }
+    }
+
+    private static final class Https2ServerFactory extends H2ServerFactory<Http2TestServer> {
+        private static final Https2ServerFactory instance = new Https2ServerFactory();
+
+        static Http2TestServer create() throws IOException {
+            return instance.createInternal();
+        }
+
+        @Override
+        protected Http2TestServer newHttpServer() throws Exception {
+            return new Http2TestServer("localhost", true, 0);
+        }
+    }
+
+    private static final class Http1ServerFactory extends H1ServerFactory<HttpServer> {
+        private static final Http1ServerFactory instance = new Http1ServerFactory();
+
+        static HttpServer create() throws IOException {
+            return instance.createInternal();
+        }
+
+        @Override
+        protected HttpServer newHttpServer() throws IOException {
+            return HttpServer.create();
+        }
+    }
+
+    private static final class Https1ServerFactory extends H1ServerFactory<HttpsServer> {
+        private static final Https1ServerFactory instance = new Https1ServerFactory();
+
+        static HttpsServer create() throws IOException {
+            return instance.createInternal();
+        }
+
+        @Override
+        protected HttpsServer newHttpServer() throws IOException {
+            return HttpsServer.create();
+        }
+    }
+
+    static Http2TestServer createHttp2Server(String protocol) throws IOException {
+        final Http2TestServer server;
+        if ("http".equalsIgnoreCase(protocol)) {
+            server = Http2ServerFactory.create();
+        } else if ("https".equalsIgnoreCase(protocol)) {
+            server = Https2ServerFactory.create();
+        } else {
+            throw new InternalError("unsupported protocol: " + protocol);
+        }
+        return server;
+    }
+
+    static HttpTestServer createHttpServer(Version version, String protocol)
+            throws IOException
+    {
+        switch(version) {
+            case HTTP_1_1:
+                return HttpTestServer.of(createHttp1Server(protocol));
+            case HTTP_2:
+                return HttpTestServer.of(createHttp2Server(protocol));
+            default:
+                throw new InternalError("Unexpected version: " + version);
+        }
+    }
+
+    static HttpServer createHttp1Server(String protocol) throws IOException {
+        final HttpServer server;
+        if ("http".equalsIgnoreCase(protocol)) {
+            server = Http1ServerFactory.create();
+        } else if ("https".equalsIgnoreCase(protocol)) {
+            server = configure(Https1ServerFactory.create());
+        } else {
+            throw new InternalError("unsupported protocol: " + protocol);
+        }
+        return server;
+    }
+
+    static HttpsServer configure(HttpsServer server) throws IOException {
+        try {
+            SSLContext ctx = SSLContext.getDefault();
+            server.setHttpsConfigurator(new Configurator(ctx));
+        } catch (NoSuchAlgorithmException ex) {
+            throw new IOException(ex);
+        }
+        return server;
+    }
+
+
+    static void setContextAuthenticator(HttpTestContext ctxt,
+                                        HttpTestAuthenticator auth) {
+        final String realm = auth.getRealm();
+        com.sun.net.httpserver.Authenticator authenticator =
+            new BasicAuthenticator(realm) {
+                @Override
+                public boolean checkCredentials(String username, String pwd) {
+                    return auth.getUserName().equals(username)
+                           && new String(auth.getPassword(username)).equals(pwd);
+                }
+        };
+        ctxt.setAuthenticator(authenticator);
+    }
+
+    public static DigestEchoServer createServer(Version version,
+                                        String protocol,
+                                        HttpAuthType authType,
+                                        HttpTestAuthenticator auth,
+                                        HttpAuthSchemeType schemeType,
+                                        HttpTestHandler delegate,
+                                        String path)
+            throws IOException {
+        Objects.requireNonNull(authType);
+        Objects.requireNonNull(auth);
+
+        HttpTestServer impl = createHttpServer(version, protocol);
+        String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s",
+                ProcessHandle.current().pid(),
+                impl.getAddress().getPort(),
+                version, protocol, authType, schemeType);
+        final DigestEchoServer server = new DigestEchoServerImpl(key, impl, null, delegate);
+        final HttpTestHandler handler =
+                server.createHandler(schemeType, auth, authType, false);
+        HttpTestContext context = impl.addHandler(handler, path);
+        server.configureAuthentication(context, schemeType, auth, authType);
+        impl.start();
+        return server;
+    }
+
+    public static DigestEchoServer createProxy(Version version,
+                                        String protocol,
+                                        HttpAuthType authType,
+                                        HttpTestAuthenticator auth,
+                                        HttpAuthSchemeType schemeType,
+                                        HttpTestHandler delegate,
+                                        String path)
+            throws IOException {
+        Objects.requireNonNull(authType);
+        Objects.requireNonNull(auth);
+
+        if (version == Version.HTTP_2 && protocol.equalsIgnoreCase("http")) {
+            System.out.println("WARNING: can't use HTTP/1.1 proxy with unsecure HTTP/2 server");
+            version = Version.HTTP_1_1;
+        }
+        HttpTestServer impl = createHttpServer(version, protocol);
+        String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s",
+                ProcessHandle.current().pid(),
+                impl.getAddress().getPort(),
+                version, protocol, authType, schemeType);
+        final DigestEchoServer server = "https".equalsIgnoreCase(protocol)
+                ? new HttpsProxyTunnel(key, impl, null, delegate)
+                : new DigestEchoServerImpl(key, impl, null, delegate);
+
+        final HttpTestHandler hh = server.createHandler(HttpAuthSchemeType.NONE,
+                                         null, HttpAuthType.SERVER,
+                                         server instanceof HttpsProxyTunnel);
+        HttpTestContext ctxt = impl.addHandler(hh, path);
+        server.configureAuthentication(ctxt, schemeType, auth, authType);
+        impl.start();
+
+        return server;
+    }
+
+    public static DigestEchoServer createServerAndRedirect(
+                                        Version version,
+                                        String protocol,
+                                        HttpAuthType targetAuthType,
+                                        HttpTestAuthenticator auth,
+                                        HttpAuthSchemeType schemeType,
+                                        HttpTestHandler targetDelegate,
+                                        int code300)
+            throws IOException {
+        Objects.requireNonNull(targetAuthType);
+        Objects.requireNonNull(auth);
+
+        // The connection between client and proxy can only
+        // be a plain connection: SSL connection to proxy
+        // is not supported by our client connection.
+        String targetProtocol = targetAuthType == HttpAuthType.PROXY
+                                          ? "http"
+                                          : protocol;
+        DigestEchoServer redirectTarget =
+                (targetAuthType == HttpAuthType.PROXY)
+                ? createProxy(version, protocol, targetAuthType,
+                              auth, schemeType, targetDelegate, "/")
+                : createServer(version, targetProtocol, targetAuthType,
+                               auth, schemeType, targetDelegate, "/");
+        HttpTestServer impl = createHttpServer(version, protocol);
+        String key = String.format("RedirectingServer[PID=%s,PORT=%s]:%s:%s:%s:%s",
+                ProcessHandle.current().pid(),
+                impl.getAddress().getPort(),
+                version, protocol,
+                HttpAuthType.SERVER, code300)
+                + "->" + redirectTarget.key;
+        final DigestEchoServer redirectingServer =
+                 new DigestEchoServerImpl(key, impl, redirectTarget, null);
+        InetSocketAddress redirectAddr = redirectTarget.getAddress();
+        URL locationURL = url(targetProtocol, redirectAddr, "/");
+        final HttpTestHandler hh = redirectingServer.create300Handler(key, locationURL,
+                                             HttpAuthType.SERVER, code300);
+        impl.addHandler(hh,"/");
+        impl.start();
+        return redirectingServer;
+    }
+
+    public abstract InetSocketAddress getServerAddress();
+    public abstract InetSocketAddress getProxyAddress();
+    public abstract InetSocketAddress getAddress();
+    public abstract void stop();
+    public abstract Version getServerVersion();
+
+    private static class DigestEchoServerImpl extends DigestEchoServer {
+        DigestEchoServerImpl(String key,
+                             HttpTestServer server,
+                             DigestEchoServer target,
+                             HttpTestHandler delegate) {
+            super(key, Objects.requireNonNull(server), target, delegate);
+        }
+
+        public InetSocketAddress getAddress() {
+            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                    serverImpl.getAddress().getPort());
+        }
+
+        public InetSocketAddress getServerAddress() {
+            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                    serverImpl.getAddress().getPort());
+        }
+
+        public InetSocketAddress getProxyAddress() {
+            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                    serverImpl.getAddress().getPort());
+        }
+
+        public Version getServerVersion() {
+            return serverImpl.getVersion();
+        }
+
+        public void stop() {
+            serverImpl.stop();
+            if (redirect != null) {
+                redirect.stop();
+            }
+        }
+    }
+
+    protected void writeResponse(HttpTestExchange he) throws IOException {
+        if (delegate == null) {
+            he.sendResponseHeaders(HttpURLConnection.HTTP_OK, -1);
+            he.getResponseBody().write(he.getRequestBody().readAllBytes());
+        } else {
+            delegate.handle(he);
+        }
+    }
+
+    private HttpTestHandler createHandler(HttpAuthSchemeType schemeType,
+                                      HttpTestAuthenticator auth,
+                                      HttpAuthType authType,
+                                      boolean tunelled) {
+        return new HttpNoAuthHandler(key, authType, tunelled);
+    }
+
+    void configureAuthentication(HttpTestContext ctxt,
+                                 HttpAuthSchemeType schemeType,
+                                 HttpTestAuthenticator auth,
+                                 HttpAuthType authType) {
+        switch(schemeType) {
+            case DIGEST:
+                // DIGEST authentication is handled by the handler.
+                ctxt.addFilter(new HttpDigestFilter(key, auth, authType));
+                break;
+            case BASIC:
+                // BASIC authentication is handled by the filter.
+                ctxt.addFilter(new HttpBasicFilter(key, auth, authType));
+                break;
+            case BASICSERVER:
+                switch(authType) {
+                    case PROXY: case PROXY305:
+                        // HttpServer can't support Proxy-type authentication
+                        // => we do as if BASIC had been specified, and we will
+                        //    handle authentication in the handler.
+                        ctxt.addFilter(new HttpBasicFilter(key, auth, authType));
+                        break;
+                    case SERVER: case SERVER307:
+                        if (ctxt.getVersion() == Version.HTTP_1_1) {
+                            // Basic authentication is handled by HttpServer
+                            // directly => the filter should not perform
+                            // authentication again.
+                            setContextAuthenticator(ctxt, auth);
+                            ctxt.addFilter(new HttpNoAuthFilter(key, authType));
+                        } else {
+                            ctxt.addFilter(new HttpBasicFilter(key, auth, authType));
+                        }
+                        break;
+                    default:
+                        throw new InternalError(key + ": Invalid combination scheme="
+                             + schemeType + " authType=" + authType);
+                }
+            case NONE:
+                // No authentication at all.
+                ctxt.addFilter(new HttpNoAuthFilter(key, authType));
+                break;
+            default:
+                throw new InternalError(key + ": No such scheme: " + schemeType);
+        }
+    }
+
+    private HttpTestHandler create300Handler(String key, URL proxyURL,
+                                             HttpAuthType type, int code300)
+            throws MalformedURLException
+    {
+        return new Http3xxHandler(key, proxyURL, type, code300);
+    }
+
+    // Abstract HTTP filter class.
+    private abstract static class AbstractHttpFilter extends HttpTestFilter {
+
+        final HttpAuthType authType;
+        final String type;
+        public AbstractHttpFilter(HttpAuthType authType, String type) {
+            this.authType = authType;
+            this.type = type;
+        }
+
+        String getLocation() {
+            return "Location";
+        }
+        String getAuthenticate() {
+            return authType == HttpAuthType.PROXY
+                    ? "Proxy-Authenticate" : "WWW-Authenticate";
+        }
+        String getAuthorization() {
+            return authType == HttpAuthType.PROXY
+                    ? "Proxy-Authorization" : "Authorization";
+        }
+        int getUnauthorizedCode() {
+            return authType == HttpAuthType.PROXY
+                    ? HttpURLConnection.HTTP_PROXY_AUTH
+                    : HttpURLConnection.HTTP_UNAUTHORIZED;
+        }
+        String getKeepAlive() {
+            return "keep-alive";
+        }
+        String getConnection() {
+            return authType == HttpAuthType.PROXY
+                    ? "Proxy-Connection" : "Connection";
+        }
+        protected abstract boolean isAuthentified(HttpTestExchange he) throws IOException;
+        protected abstract void requestAuthentication(HttpTestExchange he) throws IOException;
+        protected void accept(HttpTestExchange he, HttpChain chain) throws IOException {
+            chain.doFilter(he);
+        }
+
+        @Override
+        public String description() {
+            return "Filter for " + type;
+        }
+        @Override
+        public void doFilter(HttpTestExchange he, HttpChain chain) throws IOException {
+            try {
+                System.out.println(type + ": Got " + he.getRequestMethod()
+                    + ": " + he.getRequestURI()
+                    + "\n" + DigestEchoServer.toString(he.getRequestHeaders()));
+
+                // Assert only a single value for Expect. Not directly related
+                // to digest authentication, but verifies good client behaviour.
+                List<String> expectValues = he.getRequestHeaders().get("Expect");
+                if (expectValues != null && expectValues.size() > 1) {
+                    throw new IOException("Expect:  " + expectValues);
+                }
+
+                if (!isAuthentified(he)) {
+                    try {
+                        requestAuthentication(he);
+                        he.sendResponseHeaders(getUnauthorizedCode(), -1);
+                        System.out.println(type
+                            + ": Sent back " + getUnauthorizedCode());
+                    } finally {
+                        he.close();
+                    }
+                } else {
+                    accept(he, chain);
+                }
+            } catch (RuntimeException | Error | IOException t) {
+               System.err.println(type
+                    + ": Unexpected exception while handling request: " + t);
+               t.printStackTrace(System.err);
+               he.close();
+               throw t;
+            }
+        }
+
+    }
+
+    // WARNING: This is not a full fledged implementation of DIGEST.
+    // It does contain bugs and inaccuracy.
+    final static class DigestResponse {
+        final String realm;
+        final String username;
+        final String nonce;
+        final String cnonce;
+        final String nc;
+        final String uri;
+        final String algorithm;
+        final String response;
+        final String qop;
+        final String opaque;
+
+        public DigestResponse(String realm, String username, String nonce,
+                              String cnonce, String nc, String uri,
+                              String algorithm, String qop, String opaque,
+                              String response) {
+            this.realm = realm;
+            this.username = username;
+            this.nonce = nonce;
+            this.cnonce = cnonce;
+            this.nc = nc;
+            this.uri = uri;
+            this.algorithm = algorithm;
+            this.qop = qop;
+            this.opaque = opaque;
+            this.response = response;
+        }
+
+        String getAlgorithm(String defval) {
+            return algorithm == null ? defval : algorithm;
+        }
+        String getQoP(String defval) {
+            return qop == null ? defval : qop;
+        }
+
+        // Code stolen from DigestAuthentication:
+
+        private static final char charArray[] = {
+            '0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+        };
+
+        private static String encode(String src, char[] passwd, MessageDigest md) {
+            try {
+                md.update(src.getBytes("ISO-8859-1"));
+            } catch (java.io.UnsupportedEncodingException uee) {
+                assert false;
+            }
+            if (passwd != null) {
+                byte[] passwdBytes = new byte[passwd.length];
+                for (int i=0; i<passwd.length; i++)
+                    passwdBytes[i] = (byte)passwd[i];
+                md.update(passwdBytes);
+                Arrays.fill(passwdBytes, (byte)0x00);
+            }
+            byte[] digest = md.digest();
+
+            StringBuilder res = new StringBuilder(digest.length * 2);
+            for (int i = 0; i < digest.length; i++) {
+                int hashchar = ((digest[i] >>> 4) & 0xf);
+                res.append(charArray[hashchar]);
+                hashchar = (digest[i] & 0xf);
+                res.append(charArray[hashchar]);
+            }
+            return res.toString();
+        }
+
+        public static String computeDigest(boolean isRequest,
+                                            String reqMethod,
+                                            char[] password,
+                                            DigestResponse params)
+            throws NoSuchAlgorithmException
+        {
+
+            String A1, HashA1;
+            String algorithm = params.getAlgorithm("MD5");
+            boolean md5sess = algorithm.equalsIgnoreCase ("MD5-sess");
+
+            MessageDigest md = MessageDigest.getInstance(md5sess?"MD5":algorithm);
+
+            if (params.username == null) {
+                throw new IllegalArgumentException("missing username");
+            }
+            if (params.realm == null) {
+                throw new IllegalArgumentException("missing realm");
+            }
+            if (params.uri == null) {
+                throw new IllegalArgumentException("missing uri");
+            }
+            if (params.nonce == null) {
+                throw new IllegalArgumentException("missing nonce");
+            }
+
+            A1 = params.username + ":" + params.realm + ":";
+            HashA1 = encode(A1, password, md);
+
+            String A2;
+            if (isRequest) {
+                A2 = reqMethod + ":" + params.uri;
+            } else {
+                A2 = ":" + params.uri;
+            }
+            String HashA2 = encode(A2, null, md);
+            String combo, finalHash;
+
+            if ("auth".equals(params.qop)) { /* RRC2617 when qop=auth */
+                if (params.cnonce == null) {
+                    throw new IllegalArgumentException("missing nonce");
+                }
+                if (params.nc == null) {
+                    throw new IllegalArgumentException("missing nonce");
+                }
+                combo = HashA1+ ":" + params.nonce + ":" + params.nc + ":" +
+                            params.cnonce + ":auth:" +HashA2;
+
+            } else { /* for compatibility with RFC2069 */
+                combo = HashA1 + ":" +
+                           params.nonce + ":" +
+                           HashA2;
+            }
+            finalHash = encode(combo, null, md);
+            return finalHash;
+        }
+
+        public static DigestResponse create(String raw) {
+            String username, realm, nonce, nc, uri, response, cnonce,
+                   algorithm, qop, opaque;
+            HeaderParser parser = new HeaderParser(raw);
+            username = parser.findValue("username");
+            realm = parser.findValue("realm");
+            nonce = parser.findValue("nonce");
+            nc = parser.findValue("nc");
+            uri = parser.findValue("uri");
+            cnonce = parser.findValue("cnonce");
+            response = parser.findValue("response");
+            algorithm = parser.findValue("algorithm");
+            qop = parser.findValue("qop");
+            opaque = parser.findValue("opaque");
+            return new DigestResponse(realm, username, nonce, cnonce, nc, uri,
+                                      algorithm, qop, opaque, response);
+        }
+
+    }
+
+    private static class HttpNoAuthFilter extends AbstractHttpFilter {
+
+        static String type(String key, HttpAuthType authType) {
+            String type = authType == HttpAuthType.SERVER
+                    ? "NoAuth Server Filter" : "NoAuth Proxy Filter";
+            return "["+type+"]:"+key;
+        }
+
+        public HttpNoAuthFilter(String key, HttpAuthType authType) {
+            super(authType, type(key, authType));
+        }
+
+        @Override
+        protected boolean isAuthentified(HttpTestExchange he) throws IOException {
+            return true;
+        }
+
+        @Override
+        protected void requestAuthentication(HttpTestExchange he) throws IOException {
+            throw new InternalError("Should not com here");
+        }
+
+        @Override
+        public String description() {
+            return "Passthrough Filter";
+        }
+
+    }
+
+    // An HTTP Filter that performs Basic authentication
+    private static class HttpBasicFilter extends AbstractHttpFilter {
+
+        static String type(String key, HttpAuthType authType) {
+            String type = authType == HttpAuthType.SERVER
+                    ? "Basic Server Filter" : "Basic Proxy Filter";
+            return "["+type+"]:"+key;
+        }
+
+        private final HttpTestAuthenticator auth;
+        public HttpBasicFilter(String key, HttpTestAuthenticator auth,
+                               HttpAuthType authType) {
+            super(authType, type(key, authType));
+            this.auth = auth;
+        }
+
+        @Override
+        protected void requestAuthentication(HttpTestExchange he)
+            throws IOException {
+            he.getResponseHeaders().addHeader(getAuthenticate(),
+                 "Basic realm=\"" + auth.getRealm() + "\"");
+            System.out.println(type + ": Requesting Basic Authentication "
+                 + he.getResponseHeaders().firstValue(getAuthenticate()));
+        }
+
+        @Override
+        protected boolean isAuthentified(HttpTestExchange he) {
+            if (he.getRequestHeaders().containsKey(getAuthorization())) {
+                List<String> authorization =
+                    he.getRequestHeaders().get(getAuthorization());
+                for (String a : authorization) {
+                    System.out.println(type + ": processing " + a);
+                    int sp = a.indexOf(' ');
+                    if (sp < 0) return false;
+                    String scheme = a.substring(0, sp);
+                    if (!"Basic".equalsIgnoreCase(scheme)) {
+                        System.out.println(type + ": Unsupported scheme '"
+                                           + scheme +"'");
+                        return false;
+                    }
+                    if (a.length() <= sp+1) {
+                        System.out.println(type + ": value too short for '"
+                                            + scheme +"'");
+                        return false;
+                    }
+                    a = a.substring(sp+1);
+                    return validate(a);
+                }
+                return false;
+            }
+            return false;
+        }
+
+        boolean validate(String a) {
+            byte[] b = Base64.getDecoder().decode(a);
+            String userpass = new String (b);
+            int colon = userpass.indexOf (':');
+            String uname = userpass.substring (0, colon);
+            String pass = userpass.substring (colon+1);
+            return auth.getUserName().equals(uname) &&
+                   new String(auth.getPassword(uname)).equals(pass);
+        }
+
+        @Override
+        public String description() {
+            return "Filter for BASIC authentication: " + type;
+        }
+
+    }
+
+
+    // An HTTP Filter that performs Digest authentication
+    // WARNING: This is not a full fledged implementation of DIGEST.
+    // It does contain bugs and inaccuracy.
+    private static class HttpDigestFilter extends AbstractHttpFilter {
+
+        static String type(String key, HttpAuthType authType) {
+            String type = authType == HttpAuthType.SERVER
+                    ? "Digest Server Filter" : "Digest Proxy Filter";
+            return "["+type+"]:"+key;
+        }
+
+        // This is a very basic DIGEST - used only for the purpose of testing
+        // the client implementation. Therefore we can get away with never
+        // updating the server nonce as it makes the implementation of the
+        // server side digest simpler.
+        private final HttpTestAuthenticator auth;
+        private final byte[] nonce;
+        private final String ns;
+        public HttpDigestFilter(String key, HttpTestAuthenticator auth, HttpAuthType authType) {
+            super(authType, type(key, authType));
+            this.auth = auth;
+            nonce = new byte[16];
+            new Random(Instant.now().toEpochMilli()).nextBytes(nonce);
+            ns = new BigInteger(1, nonce).toString(16);
+        }
+
+        @Override
+        protected void requestAuthentication(HttpTestExchange he)
+                throws IOException {
+            String separator;
+            Version v = he.getExchangeVersion();
+            if (v == Version.HTTP_1_1) {
+                separator = "\r\n    ";
+            } else if (v == Version.HTTP_2) {
+                separator = " ";
+            } else {
+                throw new InternalError(String.valueOf(v));
+            }
+            he.getResponseHeaders().addHeader(getAuthenticate(),
+                 "Digest realm=\"" + auth.getRealm() + "\","
+                 + separator + "qop=\"auth\","
+                 + separator + "nonce=\"" + ns +"\"");
+            System.out.println(type + ": Requesting Digest Authentication "
+                 + he.getResponseHeaders()
+                    .firstValue(getAuthenticate())
+                    .orElse("null"));
+        }
+
+        @Override
+        protected boolean isAuthentified(HttpTestExchange he) {
+            if (he.getRequestHeaders().containsKey(getAuthorization())) {
+                List<String> authorization = he.getRequestHeaders().get(getAuthorization());
+                for (String a : authorization) {
+                    System.out.println(type + ": processing " + a);
+                    int sp = a.indexOf(' ');
+                    if (sp < 0) return false;
+                    String scheme = a.substring(0, sp);
+                    if (!"Digest".equalsIgnoreCase(scheme)) {
+                        System.out.println(type + ": Unsupported scheme '" + scheme +"'");
+                        return false;
+                    }
+                    if (a.length() <= sp+1) {
+                        System.out.println(type + ": value too short for '" + scheme +"'");
+                        return false;
+                    }
+                    a = a.substring(sp+1);
+                    DigestResponse dgr = DigestResponse.create(a);
+                    return validate(he.getRequestURI(), he.getRequestMethod(), dgr);
+                }
+                return false;
+            }
+            return false;
+        }
+
+        boolean validate(URI uri, String reqMethod, DigestResponse dg) {
+            if (!"MD5".equalsIgnoreCase(dg.getAlgorithm("MD5"))) {
+                System.out.println(type + ": Unsupported algorithm "
+                                   + dg.algorithm);
+                return false;
+            }
+            if (!"auth".equalsIgnoreCase(dg.getQoP("auth"))) {
+                System.out.println(type + ": Unsupported qop "
+                                   + dg.qop);
+                return false;
+            }
+            try {
+                if (!dg.nonce.equals(ns)) {
+                    System.out.println(type + ": bad nonce returned by client: "
+                                    + nonce + " expected " + ns);
+                    return false;
+                }
+                if (dg.response == null) {
+                    System.out.println(type + ": missing digest response.");
+                    return false;
+                }
+                char[] pa = auth.getPassword(dg.username);
+                return verify(uri, reqMethod, dg, pa);
+            } catch(IllegalArgumentException | SecurityException
+                    | NoSuchAlgorithmException e) {
+                System.out.println(type + ": " + e.getMessage());
+                return false;
+            }
+        }
+
+
+        boolean verify(URI uri, String reqMethod, DigestResponse dg, char[] pw)
+            throws NoSuchAlgorithmException {
+            String response = DigestResponse.computeDigest(true, reqMethod, pw, dg);
+            if (!dg.response.equals(response)) {
+                System.out.println(type + ": bad response returned by client: "
+                                    + dg.response + " expected " + response);
+                return false;
+            } else {
+                // A real server would also verify the uri=<request-uri>
+                // parameter - but this is just a test...
+                System.out.println(type + ": verified response " + response);
+            }
+            return true;
+        }
+
+
+        @Override
+        public String description() {
+            return "Filter for DIGEST authentication: " + type;
+        }
+    }
+
+    // Abstract HTTP handler class.
+    private abstract static class AbstractHttpHandler implements HttpTestHandler {
+
+        final HttpAuthType authType;
+        final String type;
+        public AbstractHttpHandler(HttpAuthType authType, String type) {
+            this.authType = authType;
+            this.type = type;
+        }
+
+        String getLocation() {
+            return "Location";
+        }
+
+        @Override
+        public void handle(HttpTestExchange he) throws IOException {
+            try {
+                sendResponse(he);
+            } catch (RuntimeException | Error | IOException t) {
+               System.err.println(type
+                    + ": Unexpected exception while handling request: " + t);
+               t.printStackTrace(System.err);
+               throw t;
+            } finally {
+                he.close();
+            }
+        }
+
+        protected abstract void sendResponse(HttpTestExchange he) throws IOException;
+
+    }
+
+    static String stype(String type, String key, HttpAuthType authType, boolean tunnelled) {
+        type = type + (authType == HttpAuthType.SERVER
+                       ? " Server" : " Proxy")
+                + (tunnelled ? " Tunnelled" : "");
+        return "["+type+"]:"+key;
+    }
+
+    private class HttpNoAuthHandler extends AbstractHttpHandler {
+
+        // true if this server is behind a proxy tunnel.
+        final boolean tunnelled;
+        public HttpNoAuthHandler(String key, HttpAuthType authType, boolean tunnelled) {
+            super(authType, stype("NoAuth", key, authType, tunnelled));
+            this.tunnelled = tunnelled;
+        }
+
+        @Override
+        protected void sendResponse(HttpTestExchange he) throws IOException {
+            if (DEBUG) {
+                System.out.println(type + ": headers are: "
+                        + DigestEchoServer.toString(he.getRequestHeaders()));
+            }
+            if (authType == HttpAuthType.SERVER && tunnelled) {
+                // Verify that the client doesn't send us proxy-* headers
+                // used to establish the proxy tunnel
+                Optional<String> proxyAuth = he.getRequestHeaders()
+                        .keySet().stream()
+                        .filter("proxy-authorization"::equalsIgnoreCase)
+                        .findAny();
+                if (proxyAuth.isPresent()) {
+                    System.out.println(type + " found "
+                            + proxyAuth.get() + ": failing!");
+                    throw new IOException(proxyAuth.get()
+                            + " found by " + type + " for "
+                            + he.getRequestURI());
+                }
+            }
+            DigestEchoServer.this.writeResponse(he);
+        }
+
+    }
+
+    // A dummy HTTP Handler that redirects all incoming requests
+    // by sending a back 3xx response code (301, 305, 307 etc..)
+    private class Http3xxHandler extends AbstractHttpHandler {
+
+        private final URL redirectTargetURL;
+        private final int code3XX;
+        public Http3xxHandler(String key, URL proxyURL, HttpAuthType authType, int code300) {
+            super(authType, stype("Server" + code300, key, authType, false));
+            this.redirectTargetURL = proxyURL;
+            this.code3XX = code300;
+        }
+
+        int get3XX() {
+            return code3XX;
+        }
+
+        @Override
+        public void sendResponse(HttpTestExchange he) throws IOException {
+            System.out.println(type + ": Got " + he.getRequestMethod()
+                    + ": " + he.getRequestURI()
+                    + "\n" + DigestEchoServer.toString(he.getRequestHeaders()));
+            System.out.println(type + ": Redirecting to "
+                               + (authType == HttpAuthType.PROXY305
+                                    ? "proxy" : "server"));
+            he.getResponseHeaders().addHeader(getLocation(),
+                redirectTargetURL.toExternalForm().toString());
+            he.sendResponseHeaders(get3XX(), -1);
+            System.out.println(type + ": Sent back " + get3XX() + " "
+                 + getLocation() + ": " + redirectTargetURL.toExternalForm().toString());
+        }
+    }
+
+    static class Configurator extends HttpsConfigurator {
+        public Configurator(SSLContext ctx) {
+            super(ctx);
+        }
+
+        @Override
+        public void configure (HttpsParameters params) {
+            params.setSSLParameters (getSSLContext().getSupportedSSLParameters());
+        }
+    }
+
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    static class  ProxyAuthorization {
+        final HttpAuthSchemeType schemeType;
+        final HttpTestAuthenticator authenticator;
+        private final byte[] nonce;
+        private final String ns;
+        private final String key;
+
+        ProxyAuthorization(String key, HttpAuthSchemeType schemeType, HttpTestAuthenticator auth) {
+            this.key = key;
+            this.schemeType = schemeType;
+            this.authenticator = auth;
+            nonce = new byte[16];
+            new Random(Instant.now().toEpochMilli()).nextBytes(nonce);
+            ns = new BigInteger(1, nonce).toString(16);
+        }
+
+        String doBasic(Optional<String> authorization) {
+            String offset = "proxy-authorization: basic ";
+            String authstring = authorization.orElse("");
+            if (!authstring.toLowerCase(Locale.US).startsWith(offset)) {
+                return "Proxy-Authenticate: BASIC " + "realm=\""
+                        + authenticator.getRealm() +"\"";
+            }
+            authstring = authstring
+                    .substring(offset.length())
+                    .trim();
+            byte[] base64 = Base64.getDecoder().decode(authstring);
+            String up = new String(base64, StandardCharsets.UTF_8);
+            int colon = up.indexOf(':');
+            if (colon < 1) {
+                return "Proxy-Authenticate: BASIC " + "realm=\""
+                        + authenticator.getRealm() +"\"";
+            }
+            String u = up.substring(0, colon);
+            String p = up.substring(colon+1);
+            char[] pw = authenticator.getPassword(u);
+            if (!p.equals(new String(pw))) {
+                return "Proxy-Authenticate: BASIC " + "realm=\""
+                        + authenticator.getRealm() +"\"";
+            }
+            System.out.println(now() + key + " Proxy basic authentication success");
+            return null;
+        }
+
+        String doDigest(Optional<String> authorization) {
+            String offset = "proxy-authorization: digest ";
+            String authstring = authorization.orElse("");
+            if (!authstring.toLowerCase(Locale.US).startsWith(offset)) {
+                return "Proxy-Authenticate: " +
+                        "Digest realm=\"" + authenticator.getRealm() + "\","
+                        + "\r\n    qop=\"auth\","
+                        + "\r\n    nonce=\"" + ns +"\"";
+            }
+            authstring = authstring
+                    .substring(offset.length())
+                    .trim();
+            boolean validated = false;
+            try {
+                DigestResponse dgr = DigestResponse.create(authstring);
+                validated = validate("CONNECT", dgr);
+            } catch (Throwable t) {
+                t.printStackTrace();
+            }
+            if (!validated) {
+                return "Proxy-Authenticate: " +
+                        "Digest realm=\"" + authenticator.getRealm() + "\","
+                        + "\r\n    qop=\"auth\","
+                        + "\r\n    nonce=\"" + ns +"\"";
+            }
+            return null;
+        }
+
+
+
+
+        boolean validate(String reqMethod, DigestResponse dg) {
+            String type = now() + this.getClass().getSimpleName() + ":" + key;
+            if (!"MD5".equalsIgnoreCase(dg.getAlgorithm("MD5"))) {
+                System.out.println(type + ": Unsupported algorithm "
+                        + dg.algorithm);
+                return false;
+            }
+            if (!"auth".equalsIgnoreCase(dg.getQoP("auth"))) {
+                System.out.println(type + ": Unsupported qop "
+                        + dg.qop);
+                return false;
+            }
+            try {
+                if (!dg.nonce.equals(ns)) {
+                    System.out.println(type + ": bad nonce returned by client: "
+                            + nonce + " expected " + ns);
+                    return false;
+                }
+                if (dg.response == null) {
+                    System.out.println(type + ": missing digest response.");
+                    return false;
+                }
+                char[] pa = authenticator.getPassword(dg.username);
+                return verify(type, reqMethod, dg, pa);
+            } catch(IllegalArgumentException | SecurityException
+                    | NoSuchAlgorithmException e) {
+                System.out.println(type + ": " + e.getMessage());
+                return false;
+            }
+        }
+
+
+        boolean verify(String type, String reqMethod, DigestResponse dg, char[] pw)
+                throws NoSuchAlgorithmException {
+            String response = DigestResponse.computeDigest(true, reqMethod, pw, dg);
+            if (!dg.response.equals(response)) {
+                System.out.println(type + ": bad response returned by client: "
+                        + dg.response + " expected " + response);
+                return false;
+            } else {
+                // A real server would also verify the uri=<request-uri>
+                // parameter - but this is just a test...
+                System.out.println(type + ": verified response " + response);
+            }
+            return true;
+        }
+
+        public boolean authorize(StringBuilder response, String requestLine, String headers) {
+            String message = "<html><body><p>Authorization Failed%s</p></body></html>\r\n";
+            if (authenticator == null && schemeType != HttpAuthSchemeType.NONE) {
+                message = String.format(message, " No Authenticator Set");
+                response.append("HTTP/1.1 407 Proxy Authentication Failed\r\n");
+                response.append("Content-Length: ")
+                        .append(message.getBytes(StandardCharsets.UTF_8).length)
+                        .append("\r\n\r\n");
+                response.append(message);
+                return false;
+            }
+            Optional<String> authorization = Stream.of(headers.split("\r\n"))
+                    .filter((k) -> k.toLowerCase(Locale.US).startsWith("proxy-authorization:"))
+                    .findFirst();
+            String authenticate = null;
+            switch(schemeType) {
+                case BASIC:
+                case BASICSERVER:
+                    authenticate = doBasic(authorization);
+                    break;
+                case DIGEST:
+                    authenticate = doDigest(authorization);
+                    break;
+                case NONE:
+                    response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+                    return true;
+                default:
+                    throw new InternalError("Unknown scheme type: " + schemeType);
+            }
+            if (authenticate != null) {
+                message = String.format(message, "");
+                response.append("HTTP/1.1 407 Proxy Authentication Required\r\n");
+                response.append("Content-Length: ")
+                        .append(message.getBytes(StandardCharsets.UTF_8).length)
+                        .append("\r\n")
+                        .append(authenticate)
+                        .append("\r\n\r\n");
+                response.append(message);
+                return false;
+            }
+            response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+            return true;
+        }
+    }
+
+    public interface TunnelingProxy {
+        InetSocketAddress getProxyAddress();
+        void stop();
+    }
+
+    // This is a bit hacky: HttpsProxyTunnel is an HTTPTestServer hidden
+    // behind a fake proxy that only understands CONNECT requests.
+    // The fake proxy is just a server socket that intercept the
+    // CONNECT and then redirect streams to the real server.
+    static class HttpsProxyTunnel extends DigestEchoServer
+            implements Runnable, TunnelingProxy {
+
+        final ServerSocket ss;
+        final CopyOnWriteArrayList<CompletableFuture<Void>> connectionCFs
+                = new CopyOnWriteArrayList<>();
+        volatile ProxyAuthorization authorization;
+        volatile boolean stopped;
+        public HttpsProxyTunnel(String key, HttpTestServer server, DigestEchoServer target,
+                                HttpTestHandler delegate)
+                throws IOException {
+            this(key, server, target, delegate, ServerSocketFactory.create());
+        }
+        private HttpsProxyTunnel(String key, HttpTestServer server, DigestEchoServer target,
+                                HttpTestHandler delegate, ServerSocket ss)
+                throws IOException {
+            super("HttpsProxyTunnel:" + ss.getLocalPort() + ":" + key,
+                    server, target, delegate);
+            System.out.flush();
+            System.err.println("WARNING: HttpsProxyTunnel is an experimental test class");
+            this.ss = ss;
+            start();
+        }
+
+        final void start() throws IOException {
+            Thread t = new Thread(this, "ProxyThread");
+            t.setDaemon(true);
+            t.start();
+        }
+
+        @Override
+        public Version getServerVersion() {
+            // serverImpl is not null when this proxy
+            // serves a single server. It will be null
+            // if this proxy can serve multiple servers.
+            if (serverImpl != null) return serverImpl.getVersion();
+            return null;
+        }
+
+        @Override
+        public void stop() {
+            stopped = true;
+            if (serverImpl != null) {
+                serverImpl.stop();
+            }
+            if (redirect != null) {
+                redirect.stop();
+            }
+            try {
+                ss.close();
+            } catch (IOException ex) {
+                if (DEBUG) ex.printStackTrace(System.out);
+            }
+        }
+
+
+        @Override
+        void configureAuthentication(HttpTestContext ctxt,
+                                     HttpAuthSchemeType schemeType,
+                                     HttpTestAuthenticator auth,
+                                     HttpAuthType authType) {
+            if (authType == HttpAuthType.PROXY || authType == HttpAuthType.PROXY305) {
+                authorization = new ProxyAuthorization(key, schemeType, auth);
+            } else {
+                super.configureAuthentication(ctxt, schemeType, auth, authType);
+            }
+        }
+
+        boolean authorize(StringBuilder response, String requestLine, String headers) {
+            if (authorization != null) {
+                return authorization.authorize(response, requestLine, headers);
+            }
+            response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+            return true;
+        }
+
+        // Pipe the input stream to the output stream.
+        private synchronized Thread pipe(InputStream is, OutputStream os, char tag, CompletableFuture<Void> end) {
+            return new Thread("TunnelPipe("+tag+")") {
+                @Override
+                public void run() {
+                    try {
+                        try {
+                            int c;
+                            while ((c = is.read()) != -1) {
+                                os.write(c);
+                                os.flush();
+                                // if DEBUG prints a + or a - for each transferred
+                                // character.
+                                if (DEBUG) System.out.print(tag);
+                            }
+                            is.close();
+                        } finally {
+                            os.close();
+                        }
+                    } catch (IOException ex) {
+                        if (DEBUG) ex.printStackTrace(System.out);
+                    } finally {
+                        end.complete(null);
+                    }
+                }
+            };
+        }
+
+        @Override
+        public InetSocketAddress getAddress() {
+            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                    ss.getLocalPort());
+        }
+        @Override
+        public InetSocketAddress getProxyAddress() {
+            return getAddress();
+        }
+        @Override
+        public InetSocketAddress getServerAddress() {
+            // serverImpl can be null if this proxy can serve
+            // multiple servers.
+            if (serverImpl != null) {
+                return serverImpl.getAddress();
+            }
+            return null;
+        }
+
+
+        // This is a bit shaky. It doesn't handle continuation
+        // lines, but our client shouldn't send any.
+        // Read a line from the input stream, swallowing the final
+        // \r\n sequence. Stops at the first \n, doesn't complain
+        // if it wasn't preceded by '\r'.
+        //
+        String readLine(InputStream r) throws IOException {
+            StringBuilder b = new StringBuilder();
+            int c;
+            while ((c = r.read()) != -1) {
+                if (c == '\n') break;
+                b.appendCodePoint(c);
+            }
+            if (b.codePointAt(b.length() -1) == '\r') {
+                b.delete(b.length() -1, b.length());
+            }
+            return b.toString();
+        }
+
+        @Override
+        public void run() {
+            Socket clientConnection = null;
+            try {
+                while (!stopped) {
+                    System.out.println(now() + "Tunnel: Waiting for client");
+                    Socket toClose;
+                    try {
+                        toClose = clientConnection = ss.accept();
+                    } catch (IOException io) {
+                        if (DEBUG || !stopped) io.printStackTrace(System.out);
+                        break;
+                    }
+                    System.out.println(now() + "Tunnel: Client accepted");
+                    StringBuilder headers = new StringBuilder();
+                    Socket targetConnection = null;
+                    InputStream  ccis = clientConnection.getInputStream();
+                    OutputStream ccos = clientConnection.getOutputStream();
+                    Writer w = new OutputStreamWriter(
+                                   clientConnection.getOutputStream(), "UTF-8");
+                    PrintWriter pw = new PrintWriter(w);
+                    System.out.println(now() + "Tunnel: Reading request line");
+                    String requestLine = readLine(ccis);
+                    System.out.println(now() + "Tunnel: Request line: " + requestLine);
+                    if (requestLine.startsWith("CONNECT ")) {
+                        // We should probably check that the next word following
+                        // CONNECT is the host:port of our HTTPS serverImpl.
+                        // Some improvement for a followup!
+                        StringTokenizer tokenizer = new StringTokenizer(requestLine);
+                        String connect = tokenizer.nextToken();
+                        assert connect.equalsIgnoreCase("connect");
+                        String hostport = tokenizer.nextToken();
+                        InetSocketAddress targetAddress;
+                        try {
+                            URI uri = new URI("https", hostport, "/", null, null);
+                            int port = uri.getPort();
+                            port = port == -1 ? 443 : port;
+                            targetAddress = new InetSocketAddress(uri.getHost(), port);
+                            if (serverImpl != null) {
+                                assert targetAddress.getHostString()
+                                        .equalsIgnoreCase(serverImpl.getAddress().getHostString());
+                                assert targetAddress.getPort() == serverImpl.getAddress().getPort();
+                            }
+                        } catch (Throwable x) {
+                            System.err.printf("Bad target address: \"%s\" in \"%s\"%n",
+                                    hostport, requestLine);
+                            toClose.close();
+                            continue;
+                        }
+
+                        // Read all headers until we find the empty line that
+                        // signals the end of all headers.
+                        String line = requestLine;
+                        while(!line.equals("")) {
+                            System.out.println(now() + "Tunnel: Reading header: "
+                                               + (line = readLine(ccis)));
+                            headers.append(line).append("\r\n");
+                        }
+
+                        StringBuilder response = new StringBuilder();
+                        final boolean authorize = authorize(response, requestLine, headers.toString());
+                        if (!authorize) {
+                            System.out.println(now() + "Tunnel: Sending "
+                                    + response);
+                            // send the 407 response
+                            pw.print(response.toString());
+                            pw.flush();
+                            toClose.close();
+                            continue;
+                        }
+                        System.out.println(now()
+                                + "Tunnel connecting to target server at "
+                                + targetAddress.getAddress() + ":" + targetAddress.getPort());
+                        targetConnection = new Socket(
+                                targetAddress.getAddress(),
+                                targetAddress.getPort());
+
+                        // Then send the 200 OK response to the client
+                        System.out.println(now() + "Tunnel: Sending "
+                                           + response);
+                        pw.print(response);
+                        pw.flush();
+                    } else {
+                        // This should not happen. If it does then just print an
+                        // error - both on out and err, and close the accepted
+                        // socket
+                        System.out.println("WARNING: Tunnel: Unexpected status line: "
+                                + requestLine + " received by "
+                                + ss.getLocalSocketAddress()
+                                + " from "
+                                + toClose.getRemoteSocketAddress()
+                                + " - closing accepted socket");
+                        // Print on err
+                        System.err.println("WARNING: Tunnel: Unexpected status line: "
+                                             + requestLine + " received by "
+                                           + ss.getLocalSocketAddress()
+                                           + " from "
+                                           + toClose.getRemoteSocketAddress());
+                        // close accepted socket.
+                        toClose.close();
+                        System.err.println("Tunnel: accepted socket closed.");
+                        continue;
+                    }
+
+                    // Pipe the input stream of the client connection to the
+                    // output stream of the target connection and conversely.
+                    // Now the client and target will just talk to each other.
+                    System.out.println(now() + "Tunnel: Starting tunnel pipes");
+                    CompletableFuture<Void> end, end1, end2;
+                    Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+',
+                            end1 = new CompletableFuture<>());
+                    Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-',
+                            end2 = new CompletableFuture<>());
+                    end = CompletableFuture.allOf(end1, end2);
+                    end.whenComplete(
+                            (r,t) -> {
+                                try { toClose.close(); } catch (IOException x) { }
+                                finally {connectionCFs.remove(end);}
+                            });
+                    connectionCFs.add(end);
+                    t1.start();
+                    t2.start();
+                }
+            } catch (Throwable ex) {
+                try {
+                    ss.close();
+                } catch (IOException ex1) {
+                    ex.addSuppressed(ex1);
+                }
+                ex.printStackTrace(System.err);
+            } finally {
+                System.out.println(now() + "Tunnel: exiting (stopped=" + stopped + ")");
+                connectionCFs.forEach(cf -> cf.complete(null));
+            }
+        }
+    }
+
+    /**
+     * Creates a TunnelingProxy that can serve multiple servers.
+     * The server address is extracted from the CONNECT request line.
+     * @param authScheme The authentication scheme supported by the proxy.
+     *                   Typically one of DIGEST, BASIC, NONE.
+     * @return A new TunnelingProxy able to serve multiple servers.
+     * @throws IOException If the proxy could not be created.
+     */
+    public static TunnelingProxy createHttpsProxyTunnel(HttpAuthSchemeType authScheme)
+            throws IOException {
+        HttpsProxyTunnel result = new HttpsProxyTunnel("", null, null, null);
+        if (authScheme != HttpAuthSchemeType.NONE) {
+            result.configureAuthentication(null,
+                                           authScheme,
+                                           AUTHENTICATOR,
+                                           HttpAuthType.PROXY);
+        }
+        return result;
+    }
+
+    private static String protocol(String protocol) {
+        if ("http".equalsIgnoreCase(protocol)) return "http";
+        else if ("https".equalsIgnoreCase(protocol)) return "https";
+        else throw new InternalError("Unsupported protocol: " + protocol);
+    }
+
+    public static URL url(String protocol, InetSocketAddress address,
+                          String path) throws MalformedURLException {
+        return new URL(protocol(protocol),
+                address.getHostString(),
+                address.getPort(), path);
+    }
+
+    public static URI uri(String protocol, InetSocketAddress address,
+                          String path) throws URISyntaxException {
+        return new URI(protocol(protocol) + "://" +
+                address.getHostString() + ":" +
+                address.getPort() + path);
+    }
+}
--- a/test/jdk/java/net/httpclient/EchoHandler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/EchoHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 import com.sun.net.httpserver.*;
 import java.net.*;
-import jdk.incubator.http.*;
+import java.net.http.*;
 import java.io.*;
 import java.util.concurrent.*;
 import javax.net.ssl.*;
@@ -33,8 +33,8 @@
 import java.util.List;
 import java.util.Random;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpRequest.*;
-import static jdk.incubator.http.HttpResponse.*;
+import static java.net.http.HttpRequest.*;
+import static java.net.http.HttpResponse.*;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Level;
 import java.util.logging.Logger;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/EncodedCharsInURI.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,518 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8199683
+ * @summary Tests that escaped characters in URI are correctly
+ *          handled (not re-escaped and not unescaped)
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters EncodedCharsInURI
+ * @modules java.base/sun.net.www.http
+ *          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=headers EncodedCharsInURI
+ */
+//*        -Djdk.internal.httpclient.debug=true
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ServerSocketFactory;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublisher;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static java.lang.String.format;
+import static java.lang.System.in;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class EncodedCharsInURI implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    DummyServer    httpDummyServer;    // HTTP/1.1    [ 2 servers ]
+    DummyServer    httpsDummyServer;   // HTTPS/1.1
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+    String httpDummy;
+    String httpsDummy;
+
+    static final int ITERATION_COUNT = 1;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+    static volatile boolean tasksFailed;
+    static final AtomicLong serverCount = new AtomicLong();
+    static final AtomicLong clientCount = new AtomicLong();
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    private volatile HttpClient sharedClient;
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    System.out.printf(now() + "Task %s failed: %s%n", id, t);
+                    System.err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.printf("%n%sCreated %d servers and %d clients%n",
+                    now(), serverCount.get(), clientCount.get());
+            if (FAILURES.isEmpty()) return;
+            out.println("Failed tests: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+            });
+            if (tasksFailed) {
+                System.out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    private String[] uris() {
+        return new String[] {
+                httpDummy,
+                httpsDummy,
+                httpURI_fixed,
+                httpURI_chunk,
+                httpsURI_fixed,
+                httpsURI_chunk,
+                http2URI_fixed,
+                http2URI_chunk,
+                https2URI_fixed,
+                https2URI_chunk,
+        };
+    }
+
+    @DataProvider(name = "noThrows")
+    public Object[][] noThrows() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        //Object[][] result = new Object[uris.length][];
+        int i = 0;
+        for (boolean sameClient : List.of(false, true)) {
+            //if (!sameClient) continue;
+            for (String uri: uris()) {
+                result[i++] = new Object[] {uri, sameClient};
+            }
+        }
+        assert i == uris.length * 2;
+        // assert i == uris.length ;
+        return result;
+    }
+
+    private HttpClient makeNewClient() {
+        clientCount.incrementAndGet();
+        return HttpClient.newBuilder()
+                .executor(executor)
+                .proxy(NO_PROXY)
+                .sslContext(sslContext)
+                .build();
+    }
+
+    HttpClient newHttpClient(boolean share) {
+        if (!share) return makeNewClient();
+        HttpClient shared = sharedClient;
+        if (shared != null) return shared;
+        synchronized (this) {
+            shared = sharedClient;
+            if (shared == null) {
+                shared = sharedClient = makeNewClient();
+            }
+            return shared;
+        }
+    }
+
+    final String ENCODED = "/01%252F03/";
+
+    @Test(dataProvider = "noThrows")
+    public void testEncodedChars(String uri, boolean sameClient)
+            throws Exception {
+        HttpClient client = null;
+        out.printf("%n%s testEncodedChars(%s, %b)%n", now(), uri, sameClient);
+        uri = uri + ENCODED;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            BodyPublisher bodyPublisher = BodyPublishers.ofString(uri);
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .POST(bodyPublisher)
+                    .build();
+            BodyHandler<String> handler = BodyHandlers.ofString();
+            CompletableFuture<HttpResponse<String>> responseCF = client.sendAsync(req, handler);
+            HttpResponse<String> response = responseCF.join();
+            String body = response.body();
+            if (!uri.contains(body)) {
+                System.err.println("Test failed: " + response);
+                throw new RuntimeException(uri + " doesn't contain '" + body + "'");
+            } else {
+                System.out.println("Found expected " + body + " in " + uri);
+            }
+        }
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h1_chunkHandler = new HTTP_ChunkedHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(h1_fixedLengthHandler, "/http1/fixed");
+        httpTestServer.addHandler(h1_chunkHandler, "/http1/chunk");
+        httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed/x";
+        httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk/x";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
+        httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
+        httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed/x";
+        httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk/x";
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";
+
+        // DummyServer
+        httpDummyServer = DummyServer.create(sa);
+        httpsDummyServer = DummyServer.create(sa, sslContext);
+        httpDummy = "http://" + httpDummyServer.serverAuthority() + "/http1/dummy/x";
+        httpsDummy = "https://" + httpsDummyServer.serverAuthority() + "/https1/dummy/x";
+
+
+        serverCount.addAndGet(6);
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+        httpDummyServer.start();
+        httpsDummyServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        sharedClient = null;
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+        httpDummyServer.stopServer();
+        httpsDummyServer.stopServer();
+    }
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            byte[] req;
+            try (InputStream is = t.getRequestBody()) {
+                req = is.readAllBytes();
+            }
+            String uri = new String(req, UTF_8);
+            byte[] resp = t.getRequestURI().toString().getBytes(UTF_8);
+            if (!uri.contains(t.getRequestURI().toString())) {
+                t.sendResponseHeaders(404, resp.length);
+            } else {
+                t.sendResponseHeaders(200, resp.length);  //fixed content length
+            }
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+
+    static class HTTP_ChunkedHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());
+            byte[] resp;
+            try (InputStream is = t.getRequestBody()) {
+                resp = is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, -1); // chunked/variable
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+
+    static class DummyServer extends Thread {
+        final ServerSocket ss;
+        final boolean secure;
+        ConcurrentLinkedQueue<Socket> connections = new ConcurrentLinkedQueue<>();
+        volatile boolean stopped;
+        DummyServer(ServerSocket ss, boolean secure) {
+            super("DummyServer[" + ss.getLocalPort()+"]");
+            this.secure = secure;
+            this.ss = ss;
+        }
+
+        // This is a bit shaky. It doesn't handle continuation
+        // lines, but our client shouldn't send any.
+        // Read a line from the input stream, swallowing the final
+        // \r\n sequence. Stops at the first \n, doesn't complain
+        // if it wasn't preceded by '\r'.
+        //
+        String readLine(InputStream r) throws IOException {
+            StringBuilder b = new StringBuilder();
+            int c;
+            while ((c = r.read()) != -1) {
+                if (c == '\n') break;
+                b.appendCodePoint(c);
+            }
+            if (b.codePointAt(b.length() -1) == '\r') {
+                b.delete(b.length() -1, b.length());
+            }
+            return b.toString();
+        }
+
+        @Override
+        public void run() {
+            try {
+                while(!stopped) {
+                    Socket clientConnection = ss.accept();
+                    connections.add(clientConnection);
+                    System.out.println(now() + getName() + ": Client accepted");
+                    StringBuilder headers = new StringBuilder();
+                    Socket targetConnection = null;
+                    InputStream  ccis = clientConnection.getInputStream();
+                    OutputStream ccos = clientConnection.getOutputStream();
+                    Writer w = new OutputStreamWriter(
+                            clientConnection.getOutputStream(), "UTF-8");
+                    PrintWriter pw = new PrintWriter(w);
+                    System.out.println(now() + getName() + ": Reading request line");
+                    String requestLine = readLine(ccis);
+                    System.out.println(now() + getName() + ": Request line: " + requestLine);
+
+                    StringTokenizer tokenizer = new StringTokenizer(requestLine);
+                    String method = tokenizer.nextToken();
+                    assert method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("GET");
+                    String path = tokenizer.nextToken();
+                    URI uri;
+                    try {
+                        String hostport = serverAuthority();
+                        uri = new URI((secure ? "https" : "http") +"://" + hostport + path);
+                    } catch (Throwable x) {
+                        System.err.printf("Bad target address: \"%s\" in \"%s\"%n",
+                                path, requestLine);
+                        clientConnection.close();
+                        continue;
+                    }
+
+                    // Read all headers until we find the empty line that
+                    // signals the end of all headers.
+                    String line = requestLine;
+                    while (!line.equals("")) {
+                        System.out.println(now() + getName() + ": Reading header: "
+                                + (line = readLine(ccis)));
+                        headers.append(line).append("\r\n");
+                    }
+
+                    StringBuilder response = new StringBuilder();
+
+                    int index = headers.toString()
+                            .toLowerCase(Locale.US)
+                            .indexOf("content-length: ");
+                    byte[] b = uri.toString().getBytes(UTF_8);
+                    if (index >= 0) {
+                        index = index + "content-length: ".length();
+                        String cl = headers.toString().substring(index);
+                        StringTokenizer tk = new StringTokenizer(cl);
+                        int len = Integer.parseInt(tk.nextToken());
+                        assert len < b.length * 2;
+                        System.out.println(now() + getName()
+                                + ": received body: "
+                                + new String(ccis.readNBytes(len), UTF_8));
+                    }
+                    System.out.println(now()
+                            + getName() + ": sending back " + uri);
+
+                    response.append("HTTP/1.1 200 OK\r\nContent-Length: ")
+                            .append(b.length)
+                            .append("\r\n\r\n");
+
+                    // Then send the 200 OK response to the client
+                    System.out.println(now() + getName() + ": Sending "
+                            + response);
+                    pw.print(response);
+                    pw.flush();
+                    ccos.write(b);
+                    ccos.flush();
+                    ccos.close();
+                    connections.remove(clientConnection);
+                    clientConnection.close();
+                }
+            } catch (Throwable t) {
+                if (!stopped) {
+                    System.out.println(now() + getName() + ": failed: " + t);
+                    t.printStackTrace();
+                    try {
+                        stopServer();
+                    } catch (Throwable e) {
+
+                    }
+                }
+            } finally {
+                System.out.println(now() + getName() + ": exiting");
+            }
+        }
+
+        void close(Socket s) {
+            try {
+                s.close();
+            } catch(Throwable t) {
+
+            }
+        }
+        public void stopServer() throws IOException {
+            stopped = true;
+            ss.close();
+            connections.forEach(this::close);
+        }
+
+        public String serverAuthority() {
+            return InetAddress.getLoopbackAddress().getHostName() + ":"
+                    + ss.getLocalPort();
+        }
+
+        static DummyServer create(InetSocketAddress sa) throws IOException {
+            ServerSocket ss = ServerSocketFactory.getDefault()
+                    .createServerSocket(sa.getPort(), -1, sa.getAddress());
+            return  new DummyServer(ss, false);
+        }
+
+        static DummyServer create(InetSocketAddress sa, SSLContext sslContext) throws IOException {
+            ServerSocket ss = sslContext.getServerSocketFactory()
+                    .createServerSocket(sa.getPort(), -1, sa.getAddress());
+            return new DummyServer(ss, true);
+        }
+
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/EscapedOctetsInURI.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,250 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Preserve URI component escaped octets when converting to HTTP headers
+ * @bug 8198716
+ * @modules java.base/sun.net.www.http
+ *          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
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm
+ *       -Djdk.httpclient.HttpClient.log=reqeusts,headers
+ *       EscapedOctetsInURI
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import javax.net.ssl.SSLContext;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static org.testng.Assert.assertEquals;
+
+public class EscapedOctetsInURI {
+
+    SSLContext sslContext;
+    HttpServer httpTestServer;         // HTTP/1.1    [ 4 servers ]
+    HttpsServer httpsTestServer;       // HTTPS/1.1
+    Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
+    Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    static final String[][] pathsAndQueryStrings = new String[][] {
+        // partial-path       URI query
+        {  "/001/noSpace", "?noQuotedOctets" },
+        {  "/002/noSpace", "?name=chegar,address=Dublin%20Ireland", },
+        {  "/003/noSpace", "?target=http%3A%2F%2Fwww.w3.org%2Fns%2Foa%23hasBody" },
+
+        {  "/010/with%20space", "?noQuotedOctets" },
+        {  "/011/with%20space", "?name=chegar,address=Dublin%20Ireland" },
+        {  "/012/with%20space", "?target=http%3A%2F%2Fwww.w3.org%2Fns%2Foa%23hasBody" },
+    };
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        List<Object[]> list = new ArrayList<>();
+
+        for (boolean sameClient : new boolean[] { false, true }) {
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {httpURI + e[0] + e[1], sameClient})
+                    .forEach(list::add);
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {httpsURI + e[0] + e[1], sameClient})
+                    .forEach(list::add);
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {http2URI + e[0] + e[1], sameClient})
+                    .forEach(list::add);
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {https2URI + e[0] + e[1], sameClient})
+                    .forEach(list::add);
+        }
+        return list.stream().toArray(Object[][]::new);
+    }
+
+    static final int ITERATION_COUNT = 3; // checks upgrade and re-use
+
+    @Test(dataProvider = "variants")
+    void test(String uriString, boolean sameClient) throws Exception {
+        // The single-argument factory requires any illegal characters in its
+        // argument to be quoted and preserves any escaped octets and other
+        // characters that are present.
+        URI uri = URI.create(uriString);
+
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = HttpClient.newBuilder()
+                        .proxy(NO_PROXY)
+                        .sslContext(sslContext)
+                        .build();
+
+            HttpRequest request = HttpRequest.newBuilder(uri).build();
+            HttpResponse<String> resp = client.send(request, BodyHandlers.ofString());
+
+            out.println("Got response: " + resp);
+            out.println("Got body: " + resp.body());
+            assertEquals(resp.statusCode(), 200,
+                    "Expected 200, got:" + resp.statusCode());
+
+            // the response body should contain the exact escaped request URI
+            URI retrievedURI = URI.create(resp.body());
+            assertEquals(retrievedURI.getRawPath(),  uri.getRawPath());
+            assertEquals(retrievedURI.getRawQuery(), uri.getRawQuery());
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    void testAsync(String uriString, boolean sameClient) {
+        URI uri = URI.create(uriString);
+
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = HttpClient.newBuilder()
+                        .proxy(NO_PROXY)
+                        .sslContext(sslContext)
+                        .build();
+
+            HttpRequest request = HttpRequest.newBuilder(uri).build();
+
+            client.sendAsync(request, BodyHandlers.ofString())
+                  .thenApply(response -> {
+                      out.println("Got response: " + response);
+                      out.println("Got body: " + response.body());
+                      assertEquals(response.statusCode(), 200);
+                      return response.body(); })
+                  .thenApply(body -> URI.create(body))
+                  .thenAccept(retrievedURI -> {
+                      // the body should contain the exact escaped request URI
+                      assertEquals(retrievedURI.getRawPath(), uri.getRawPath());
+                      assertEquals(retrievedURI.getRawQuery(), uri.getRawQuery()); })
+                  .join();
+        }
+    }
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpServer.create(sa, 0);
+        httpTestServer.createContext("/http1", new Http1ASCIIUriStringHandler());
+        httpURI = "http://" + serverAuthority(httpTestServer) + "/http1";
+
+        httpsTestServer = HttpsServer.create(sa, 0);
+        httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer.createContext("/https1", new Http1ASCIIUriStringHandler());
+        httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1";
+
+        http2TestServer = new Http2TestServer("localhost", false, 0);
+        http2TestServer.addHandler(new HttpASCIIUriStringHandler(), "/http2");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2";
+
+        https2TestServer = new Http2TestServer("localhost", true, 0);
+        https2TestServer.addHandler(new HttpASCIIUriStringHandler(), "/https2");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop(0);
+        httpsTestServer.stop(0);
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    /** A handler that returns as its body the exact escaped request URI. */
+    static class Http1ASCIIUriStringHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange t) throws IOException {
+            String asciiUriString = t.getRequestURI().toASCIIString();
+            out.println("Http1ASCIIUriString received, asciiUriString: " + asciiUriString);
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = asciiUriString.getBytes(US_ASCII);
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+
+    /** A handler that returns as its body the exact escaped request URI. */
+    static class HttpASCIIUriStringHandler implements Http2Handler {
+        @Override
+        public void handle(Http2TestExchange t) throws IOException {
+            String asciiUriString = t.getRequestURI().toASCIIString();
+            out.println("Http2ASCIIUriString received, asciiUriString: " + asciiUriString);
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = asciiUriString.getBytes(US_ASCII);
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ExpectContinue.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Basic test for Expect 100-Continue ( HTTP/1.1 only )
+ * @modules java.net.http
+ *          jdk.httpserver
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm ExpectContinue
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static org.testng.Assert.assertEquals;
+
+public class ExpectContinue {
+
+    SSLContext sslContext;
+    HttpServer httpTestServer;         // HTTP/1.1    [ 2 servers ]
+    HttpsServer httpsTestServer;       // HTTPS/1.1
+    String httpURI;
+    String httpsURI;
+
+    @DataProvider(name = "positive")
+    public Object[][] positive() {
+        return new Object[][] {
+                { httpURI,  false, "Billy" },
+                { httpURI,  false, "Bob"   },
+                { httpURI,  true,  "Jimmy" },
+                { httpsURI, true,  "Jack"  },
+        };
+    }
+
+    @Test(dataProvider = "positive")
+    void test(String uriString, boolean expectedContinue, String data)
+        throws Exception
+    {
+        out.printf("test(%s, %s, %s): starting%n", uriString, expectedContinue, data);
+        HttpClient client = HttpClient.newBuilder()
+                .sslContext(sslContext)
+                .build();
+
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri)
+                .expectContinue(expectedContinue)
+                .POST(BodyPublishers.ofString(data))
+                .build();
+
+        HttpResponse<String> response = client.send(request,
+                                                    BodyHandlers.ofString());
+        System.out.println("First response: " + response);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(response.body(), data);
+
+        // again with the same request, to ensure no Expect header duplication
+        response = client.send(request, BodyHandlers.ofString());
+        System.out.println("Second response: " + response);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(response.body(), data);
+    }
+
+    @Test(dataProvider = "positive")
+    void testAsync(String uriString, boolean expectedContinue, String data) {
+        out.printf("test(%s, %s, %s): starting%n", uriString, expectedContinue, data);
+        HttpClient client = HttpClient.newBuilder()
+                .sslContext(sslContext)
+                .build();
+
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri)
+                .expectContinue(expectedContinue)
+                .POST(BodyPublishers.ofString(data))
+                .build();
+
+        HttpResponse<String> response = client.sendAsync(request,
+                BodyHandlers.ofString()).join();
+        System.out.println("First response: " + response);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(response.body(), data);
+
+        // again with the same request, to ensure no Expect header duplication
+        response = client.sendAsync(request, BodyHandlers.ofString()).join();
+        System.out.println("Second response: " + response);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(response.body(), data);
+    }
+
+    // -- Infrastructure
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpServer.create(sa, 0);
+        httpTestServer.createContext("/http1/ec", new Http1ExpectContinueHandler());
+        httpURI = "http://" + serverAuthority(httpTestServer) + "/http1/ec";
+
+        httpsTestServer = HttpsServer.create(sa, 0);
+        httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer.createContext("/https1/ec", new Http1ExpectContinueHandler());
+        httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1/ec";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop(0);
+        httpsTestServer.stop(0);
+    }
+
+    static class Http1ExpectContinueHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange t) throws IOException {
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                byte[] bytes = is.readAllBytes();
+
+                List<String> expect = t.getRequestHeaders().get("Expect");
+                if (expect != null && expect.size() != 1) {
+                    System.out.println("Server: Expect: " + expect);
+                    Throwable ex = new AssertionError("Expect: " + expect);
+                    ex.printStackTrace();
+                    t.sendResponseHeaders(500, 0);
+                } else {
+                    t.sendResponseHeaders(200, bytes.length);
+                    os.write(bytes);
+                }
+            }
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/FlowAdapterPublisherTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/FlowAdapterPublisherTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
@@ -39,9 +40,9 @@
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsServer;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
@@ -50,8 +51,8 @@
 import javax.net.ssl.SSLContext;
 import static java.util.stream.Collectors.joining;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromPublisher;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.net.http.HttpRequest.BodyPublishers.fromPublisher;
+import static java.net.http.HttpResponse.BodyHandlers.ofString;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.assertTrue;
@@ -61,9 +62,9 @@
  * @test
  * @summary Basic tests for Flow adapter Publishers
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
@@ -119,7 +120,7 @@
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                 .POST(fromPublisher(new BBPublisher(body))).build();
 
-        HttpResponse<String> response = client.sendAsync(request, asString(UTF_8)).join();
+        HttpResponse<String> response = client.sendAsync(request, ofString(UTF_8)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -135,7 +136,7 @@
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                 .POST(fromPublisher(new BBPublisher(body), cl)).build();
 
-        HttpResponse<String> response = client.sendAsync(request, asString(UTF_8)).join();
+        HttpResponse<String> response = client.sendAsync(request, ofString(UTF_8)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -152,7 +153,7 @@
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                 .POST(fromPublisher(new MBBPublisher(body))).build();
 
-        HttpResponse<String> response = client.sendAsync(request, asString(UTF_8)).join();
+        HttpResponse<String> response = client.sendAsync(request, ofString(UTF_8)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -168,7 +169,7 @@
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                 .POST(fromPublisher(new MBBPublisher(body), cl)).build();
 
-        HttpResponse<String> response = client.sendAsync(request, asString(UTF_8)).join();
+        HttpResponse<String> response = client.sendAsync(request, ofString(UTF_8)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -189,11 +190,10 @@
                 .POST(fromPublisher(new BBPublisher(body), cl)).build();
 
         try {
-            HttpResponse<String> response = client.send(request, asString(UTF_8));
+            HttpResponse<String> response = client.send(request, ofString(UTF_8));
             fail("Unexpected response: " + response);
         } catch (IOException expected) {
-            assertTrue(expected.getMessage().contains("Too few bytes returned"),
-                       "Exception message:[" + expected.toString() + "]");
+            assertMessage(expected, "Too few bytes returned");
         }
     }
 
@@ -207,11 +207,17 @@
                 .POST(fromPublisher(new BBPublisher(body), cl)).build();
 
         try {
-            HttpResponse<String> response = client.send(request, asString(UTF_8));
+            HttpResponse<String> response = client.send(request, ofString(UTF_8));
             fail("Unexpected response: " + response);
         } catch (IOException expected) {
-            assertTrue(expected.getMessage().contains("Too many bytes in request body"),
-                    "Exception message:[" + expected.toString() + "]");
+            assertMessage(expected, "Too many bytes in request body");
+        }
+    }
+
+    private void assertMessage(Throwable t, String contains) {
+        if (!t.getMessage().contains(contains)) {
+            String error = "Exception message:[" + t.toString() + "] doesn't contain [" + contains + "]";
+            throw new AssertionError(error, t);
         }
     }
 
@@ -328,31 +334,34 @@
         }
     }
 
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
     @BeforeTest
     public void setup() throws Exception {
         sslContext = new SimpleSSLContext().get();
         if (sslContext == null)
             throw new AssertionError("Unexpected null sslContext");
 
-        InetSocketAddress sa = new InetSocketAddress("localhost", 0);
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(),0);
         httpTestServer = HttpServer.create(sa, 0);
         httpTestServer.createContext("/http1/echo", new Http1EchoHandler());
-        httpURI = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/echo";
+        httpURI = "http://" + serverAuthority(httpTestServer) + "/http1/echo";
 
         httpsTestServer = HttpsServer.create(sa, 0);
         httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
         httpsTestServer.createContext("/https1/echo", new Http1EchoHandler());
-        httpsURI = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/echo";
+        httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1/echo";
 
-        http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+        http2TestServer = new Http2TestServer("localhost", false, 0);
         http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
-        int port = http2TestServer.getAddress().getPort();
-        http2URI = "http://127.0.0.1:" + port + "/http2/echo";
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/echo";
 
-        https2TestServer = new Http2TestServer("127.0.0.1", true, 0);
+        https2TestServer = new Http2TestServer("localhost", true, 0);
         https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
-        port = https2TestServer.getAddress().getPort();
-        https2URI = "https://127.0.0.1:" + port + "/https2/echo";
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/echo";
 
         httpTestServer.start();
         httpsTestServer.start();
--- a/test/jdk/java/net/httpclient/FlowAdapterSubscriberTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/FlowAdapterSubscriberTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -26,11 +26,13 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UncheckedIOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow;
 import java.util.concurrent.Flow.Subscriber;
 import java.util.function.Function;
@@ -40,11 +42,12 @@
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsServer;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscribers;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
@@ -52,7 +55,6 @@
 import org.testng.annotations.Test;
 import javax.net.ssl.SSLContext;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.assertTrue;
@@ -61,15 +63,15 @@
  * @test
  * @summary Basic tests for Flow adapter Subscribers
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
  * @build Http2TestServer
  * @build jdk.testlibrary.SimpleSSLContext
- * @run testng/othervm FlowAdapterSubscriberTest
+ * @run testng/othervm -Djdk.internal.httpclient.debug=true FlowAdapterSubscriberTest
  */
 
 public class FlowAdapterSubscriberTest {
@@ -83,6 +85,14 @@
     String httpsURI;
     String http2URI;
     String https2URI;
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
 
     @DataProvider(name = "uris")
     public Object[][] variants() {
@@ -98,17 +108,18 @@
 
     @Test
     public void testNull() {
-        assertThrows(NPE, () -> BodyHandler.fromSubscriber(null));
-        assertThrows(NPE, () -> BodyHandler.fromSubscriber(null, Function.identity()));
-        assertThrows(NPE, () -> BodyHandler.fromSubscriber(new ListSubscriber(), null));
-        assertThrows(NPE, () -> BodyHandler.fromSubscriber(null, null));
+        System.out.printf(now() + "testNull() starting%n");
+        assertThrows(NPE, () -> BodyHandlers.fromSubscriber(null));
+        assertThrows(NPE, () -> BodyHandlers.fromSubscriber(null, Function.identity()));
+        assertThrows(NPE, () -> BodyHandlers.fromSubscriber(new ListSubscriber(), null));
+        assertThrows(NPE, () -> BodyHandlers.fromSubscriber(null, null));
 
-        assertThrows(NPE, () -> BodySubscriber.fromSubscriber(null));
-        assertThrows(NPE, () -> BodySubscriber.fromSubscriber(null, Function.identity()));
-        assertThrows(NPE, () -> BodySubscriber.fromSubscriber(new ListSubscriber(), null));
-        assertThrows(NPE, () -> BodySubscriber.fromSubscriber(null, null));
+        assertThrows(NPE, () -> BodySubscribers.fromSubscriber(null));
+        assertThrows(NPE, () -> BodySubscribers.fromSubscriber(null, Function.identity()));
+        assertThrows(NPE, () -> BodySubscribers.fromSubscriber(new ListSubscriber(), null));
+        assertThrows(NPE, () -> BodySubscribers.fromSubscriber(null, null));
 
-        Subscriber subscriber = BodySubscriber.fromSubscriber(new ListSubscriber());
+        Subscriber subscriber = BodySubscribers.fromSubscriber(new ListSubscriber());
         assertThrows(NPE, () -> subscriber.onSubscribe(null));
         assertThrows(NPE, () -> subscriber.onNext(null));
         assertThrows(NPE, () -> subscriber.onError(null));
@@ -118,13 +129,14 @@
 
     @Test(dataProvider = "uris")
     void testListWithFinisher(String url) {
+        System.out.printf(now() + "testListWithFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the luck of the Irish be with you!")).build();
+                .POST(BodyPublishers.ofString("May the luck of the Irish be with you!")).build();
 
         ListSubscriber subscriber = new ListSubscriber();
         HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber, Supplier::get)).join();
+                BodyHandlers.fromSubscriber(subscriber, Supplier::get)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -133,13 +145,14 @@
 
     @Test(dataProvider = "uris")
     void testListWithoutFinisher(String url) {
+        System.out.printf(now() + "testListWithoutFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the luck of the Irish be with you!")).build();
+                .POST(BodyPublishers.ofString("May the luck of the Irish be with you!")).build();
 
         ListSubscriber subscriber = new ListSubscriber();
         HttpResponse<Void> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber)).join();
+                BodyHandlers.fromSubscriber(subscriber)).join();
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -148,13 +161,14 @@
 
     @Test(dataProvider = "uris")
     void testListWithFinisherBlocking(String url) throws Exception {
+        System.out.printf(now() + "testListWithFinisherBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the luck of the Irish be with you!")).build();
+                .POST(BodyPublishers.ofString("May the luck of the Irish be with you!")).build();
 
         ListSubscriber subscriber = new ListSubscriber();
         HttpResponse<String> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber, Supplier::get));
+                BodyHandlers.fromSubscriber(subscriber, Supplier::get));
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -163,13 +177,14 @@
 
     @Test(dataProvider = "uris")
     void testListWithoutFinisherBlocking(String url) throws Exception {
+        System.out.printf(now() + "testListWithoutFinisherBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the luck of the Irish be with you!")).build();
+                .POST(BodyPublishers.ofString("May the luck of the Irish be with you!")).build();
 
         ListSubscriber subscriber = new ListSubscriber();
         HttpResponse<Void> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber));
+                BodyHandlers.fromSubscriber(subscriber));
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -180,13 +195,14 @@
 
     @Test(dataProvider = "uris")
     void testCollectionWithFinisher(String url) {
+        System.out.printf(now() + "testCollectionWithFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("What's the craic?")).build();
+                .POST(BodyPublishers.ofString("What's the craic?")).build();
 
         CollectionSubscriber subscriber = new CollectionSubscriber();
         HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber, CollectionSubscriber::get)).join();
+                BodyHandlers.fromSubscriber(subscriber, CollectionSubscriber::get)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -195,13 +211,14 @@
 
     @Test(dataProvider = "uris")
     void testCollectionWithoutFinisher(String url) {
+        System.out.printf(now() + "testCollectionWithoutFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("What's the craic?")).build();
+                .POST(BodyPublishers.ofString("What's the craic?")).build();
 
         CollectionSubscriber subscriber = new CollectionSubscriber();
         HttpResponse<Void> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber)).join();
+                BodyHandlers.fromSubscriber(subscriber)).join();
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -210,13 +227,14 @@
 
     @Test(dataProvider = "uris")
     void testCollectionWithFinisherBlocking(String url) throws Exception {
+        System.out.printf(now() + "testCollectionWithFinisherBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("What's the craic?")).build();
+                .POST(BodyPublishers.ofString("What's the craic?")).build();
 
         CollectionSubscriber subscriber = new CollectionSubscriber();
         HttpResponse<String> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber, CollectionSubscriber::get));
+                BodyHandlers.fromSubscriber(subscriber, CollectionSubscriber::get));
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -225,13 +243,14 @@
 
     @Test(dataProvider = "uris")
     void testCollectionWithoutFinisheBlocking(String url) throws Exception {
+        System.out.printf(now() + "testCollectionWithoutFinisheBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("What's the craic?")).build();
+                .POST(BodyPublishers.ofString("What's the craic?")).build();
 
         CollectionSubscriber subscriber = new CollectionSubscriber();
         HttpResponse<Void> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber));
+                BodyHandlers.fromSubscriber(subscriber));
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -242,13 +261,14 @@
 
     @Test(dataProvider = "uris")
     void testIterableWithFinisher(String url) {
+        System.out.printf(now() + "testIterableWithFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("We're sucking diesel now!")).build();
+                .POST(BodyPublishers.ofString("We're sucking diesel now!")).build();
 
         IterableSubscriber subscriber = new IterableSubscriber();
         HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber, Supplier::get)).join();
+                BodyHandlers.fromSubscriber(subscriber, Supplier::get)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -257,13 +277,14 @@
 
     @Test(dataProvider = "uris")
     void testIterableWithoutFinisher(String url) {
+        System.out.printf(now() + "testIterableWithoutFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("We're sucking diesel now!")).build();
+                .POST(BodyPublishers.ofString("We're sucking diesel now!")).build();
 
         IterableSubscriber subscriber = new IterableSubscriber();
         HttpResponse<Void> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber)).join();
+                BodyHandlers.fromSubscriber(subscriber)).join();
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -272,13 +293,14 @@
 
     @Test(dataProvider = "uris")
     void testIterableWithFinisherBlocking(String url) throws Exception {
+        System.out.printf(now() + "testIterableWithFinisherBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("We're sucking diesel now!")).build();
+                .POST(BodyPublishers.ofString("We're sucking diesel now!")).build();
 
         IterableSubscriber subscriber = new IterableSubscriber();
         HttpResponse<String> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber, Supplier::get));
+                BodyHandlers.fromSubscriber(subscriber, Supplier::get));
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -286,14 +308,15 @@
     }
 
     @Test(dataProvider = "uris")
-    void testIterableWithoutFinisherBlocking(String url) throws Exception{
+    void testIterableWithoutFinisherBlocking(String url) throws Exception {
+        System.out.printf(now() + "testIterableWithoutFinisherBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("We're sucking diesel now!")).build();
+                .POST(BodyPublishers.ofString("We're sucking diesel now!")).build();
 
         IterableSubscriber subscriber = new IterableSubscriber();
         HttpResponse<Void> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber));
+                BodyHandlers.fromSubscriber(subscriber));
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -304,13 +327,14 @@
 
     @Test(dataProvider = "uris")
     void testObjectWithFinisher(String url) {
+        System.out.printf(now() + "testObjectWithFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the wind always be at your back.")).build();
+                .POST(BodyPublishers.ofString("May the wind always be at your back.")).build();
 
         ObjectSubscriber subscriber = new ObjectSubscriber();
         HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber, ObjectSubscriber::get)).join();
+                BodyHandlers.fromSubscriber(subscriber, ObjectSubscriber::get)).join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -319,13 +343,14 @@
 
     @Test(dataProvider = "uris")
     void testObjectWithoutFinisher(String url) {
+        System.out.printf(now() + "testObjectWithoutFinisher(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the wind always be at your back.")).build();
+                .POST(BodyPublishers.ofString("May the wind always be at your back.")).build();
 
         ObjectSubscriber subscriber = new ObjectSubscriber();
         HttpResponse<Void> response = client.sendAsync(request,
-                BodyHandler.fromSubscriber(subscriber)).join();
+                BodyHandlers.fromSubscriber(subscriber)).join();
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -334,13 +359,14 @@
 
     @Test(dataProvider = "uris")
     void testObjectWithFinisherBlocking(String url) throws Exception {
+        System.out.printf(now() + "testObjectWithFinisherBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the wind always be at your back.")).build();
+                .POST(BodyPublishers.ofString("May the wind always be at your back.")).build();
 
         ObjectSubscriber subscriber = new ObjectSubscriber();
         HttpResponse<String> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber, ObjectSubscriber::get));
+                BodyHandlers.fromSubscriber(subscriber, ObjectSubscriber::get));
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -349,19 +375,55 @@
 
     @Test(dataProvider = "uris")
     void testObjectWithoutFinisherBlocking(String url) throws Exception {
+        System.out.printf(now() + "testObjectWithoutFinisherBlocking(%s) starting%n", url);
         HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
         HttpRequest request = HttpRequest.newBuilder(URI.create(url))
-                .POST(fromString("May the wind always be at your back.")).build();
+                .POST(BodyPublishers.ofString("May the wind always be at your back.")).build();
 
         ObjectSubscriber subscriber = new ObjectSubscriber();
         HttpResponse<Void> response = client.send(request,
-                BodyHandler.fromSubscriber(subscriber));
+                BodyHandlers.fromSubscriber(subscriber));
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
         assertTrue(text.length() != 0);  // what else can be asserted!
     }
 
+
+    // -- mapping using convenience handlers
+
+    @Test(dataProvider = "uris")
+    void mappingFromByteArray(String url) throws Exception {
+        System.out.printf(now() + "mappingFromByteArray(%s) starting%n", url);
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString("We're sucking diesel now!")).build();
+
+        client.sendAsync(request, BodyHandlers.fromSubscriber(BodySubscribers.ofByteArray(),
+                    bas -> new String(bas.getBody().toCompletableFuture().join(), UTF_8)))
+                .thenApply(FlowAdapterSubscriberTest::assert200ResponseCode)
+                .thenApply(HttpResponse::body)
+                .thenAccept(body -> assertEquals(body, "We're sucking diesel now!"))
+                .join();
+    }
+
+    @Test(dataProvider = "uris")
+    void mappingFromInputStream(String url) throws Exception {
+        System.out.printf(now() + "mappingFromInputStream(%s) starting%n", url);
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString("May the wind always be at your back.")).build();
+
+        client.sendAsync(request, BodyHandlers.fromSubscriber(BodySubscribers.ofInputStream(),
+                    ins -> {
+                        InputStream is = ins.getBody().toCompletableFuture().join();
+                        return new String(uncheckedReadAllBytes(is), UTF_8); } ))
+                .thenApply(FlowAdapterSubscriberTest::assert200ResponseCode)
+                .thenApply(HttpResponse::body)
+                .thenAccept(body -> assertEquals(body, "May the wind always be at your back."))
+                .join();
+    }
+
     /** An abstract Subscriber that converts all received data into a String. */
     static abstract class AbstractSubscriber implements Supplier<String> {
         protected volatile Flow.Subscription subscription;
@@ -434,31 +496,47 @@
         }
     }
 
+    static byte[] uncheckedReadAllBytes(InputStream is) {
+        try {
+            return is.readAllBytes();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    static final <T> HttpResponse<T> assert200ResponseCode(HttpResponse<T> response) {
+        assertEquals(response.statusCode(), 200);
+        return response;
+    }
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
     @BeforeTest
     public void setup() throws Exception {
         sslContext = new SimpleSSLContext().get();
         if (sslContext == null)
             throw new AssertionError("Unexpected null sslContext");
 
-        InetSocketAddress sa = new InetSocketAddress("localhost", 0);
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         httpTestServer = HttpServer.create(sa, 0);
         httpTestServer.createContext("/http1/echo", new Http1EchoHandler());
-        httpURI = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/echo";
+        httpURI = "http://" + serverAuthority(httpTestServer) + "/http1/echo";
 
         httpsTestServer = HttpsServer.create(sa, 0);
         httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
         httpsTestServer.createContext("/https1/echo", new Http1EchoHandler());
-        httpsURI = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/echo";
+        httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1/echo";
 
-        http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+        http2TestServer = new Http2TestServer("localhost", false, 0);
         http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
-        int port = http2TestServer.getAddress().getPort();
-        http2URI = "http://127.0.0.1:" + port + "/http2/echo";
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/echo";
 
-        https2TestServer = new Http2TestServer("127.0.0.1", true, 0);
+        https2TestServer = new Http2TestServer("localhost", true, 0);
         https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
-        port = https2TestServer.getAddress().getPort();
-        https2URI = "https://127.0.0.1:" + port + "/https2/echo";
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/echo";
 
         httpTestServer.start();
         httpsTestServer.start();
--- a/test/jdk/java/net/httpclient/FlowAdaptersCompileOnly.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/FlowAdaptersCompileOnly.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -21,15 +21,20 @@
  * questions.
  */
 
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import java.nio.ByteBuffer;
 import java.nio.MappedByteBuffer;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Flow;
 import java.util.function.Function;
-import jdk.incubator.http.HttpRequest.BodyPublisher;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
 
 /*
  * @test
@@ -40,31 +45,31 @@
 public class FlowAdaptersCompileOnly {
 
     static void makesSureDifferentGenericSignaturesCompile() {
-        BodyPublisher.fromPublisher(new BBPublisher());
-        BodyPublisher.fromPublisher(new MBBPublisher());
+        BodyPublishers.fromPublisher(new BBPublisher());
+        BodyPublishers.fromPublisher(new MBBPublisher());
 
-        BodyHandler.fromSubscriber(new ListSubscriber());
-        BodyHandler.fromSubscriber(new CollectionSubscriber());
-        BodyHandler.fromSubscriber(new IterableSubscriber());
-        BodyHandler.fromSubscriber(new ObjectSubscriber());
+        BodyHandlers.fromSubscriber(new ListSubscriber());
+        BodyHandlers.fromSubscriber(new CollectionSubscriber());
+        BodyHandlers.fromSubscriber(new IterableSubscriber());
+        BodyHandlers.fromSubscriber(new ObjectSubscriber());
 
-        BodySubscriber.fromSubscriber(new ListSubscriber());
-        BodySubscriber.fromSubscriber(new CollectionSubscriber());
-        BodySubscriber.fromSubscriber(new IterableSubscriber());
-        BodySubscriber.fromSubscriber(new ObjectSubscriber());
+        BodySubscribers.fromSubscriber(new ListSubscriber());
+        BodySubscribers.fromSubscriber(new CollectionSubscriber());
+        BodySubscribers.fromSubscriber(new IterableSubscriber());
+        BodySubscribers.fromSubscriber(new ObjectSubscriber());
 
-        BodyPublisher.fromPublisher(new BBPublisher(), 1);
-        BodyPublisher.fromPublisher(new MBBPublisher(), 1);
+        BodyPublishers.fromPublisher(new BBPublisher(), 1);
+        BodyPublishers.fromPublisher(new MBBPublisher(), 1);
 
-        BodyHandler.fromSubscriber(new ListSubscriber(), Function.identity());
-        BodyHandler.fromSubscriber(new CollectionSubscriber(), Function.identity());
-        BodyHandler.fromSubscriber(new IterableSubscriber(), Function.identity());
-        BodyHandler.fromSubscriber(new ObjectSubscriber(), Function.identity());
+        BodyHandlers.fromSubscriber(new ListSubscriber(), Function.identity());
+        BodyHandlers.fromSubscriber(new CollectionSubscriber(), Function.identity());
+        BodyHandlers.fromSubscriber(new IterableSubscriber(), Function.identity());
+        BodyHandlers.fromSubscriber(new ObjectSubscriber(), Function.identity());
 
-        BodySubscriber.fromSubscriber(new ListSubscriber(), Function.identity());
-        BodySubscriber.fromSubscriber(new CollectionSubscriber(), Function.identity());
-        BodySubscriber.fromSubscriber(new IterableSubscriber(), Function.identity());
-        BodySubscriber.fromSubscriber(new ObjectSubscriber(), Function.identity());
+        BodySubscribers.fromSubscriber(new ListSubscriber(), Function.identity());
+        BodySubscribers.fromSubscriber(new CollectionSubscriber(), Function.identity());
+        BodySubscribers.fromSubscriber(new IterableSubscriber(), Function.identity());
+        BodySubscribers.fromSubscriber(new ObjectSubscriber(), Function.identity());
     }
 
     static class BBPublisher implements Flow.Publisher<ByteBuffer> {
@@ -104,4 +109,97 @@
         @Override public void onError(Throwable throwable) { }
         @Override public void onComplete() { }
     }
+
+    // ---
+
+    static final Function<ListSubscriber,Integer> f1 = subscriber -> 1;
+    static final Function<ListSubscriber,Number> f2 = subscriber -> 2;
+    static final Function<ListSubscriberX,Integer> f3 = subscriber -> 3;
+    static final Function<ListSubscriberX,Number> f4 = subscriber -> 4;
+
+    static class ListSubscriberX extends ListSubscriber {
+        int getIntegerX() { return 5; }
+    }
+
+    static void makesSureDifferentGenericFunctionSignaturesCompile() {
+        BodyHandler<Integer> bh01 = BodyHandlers.fromSubscriber(new ListSubscriber(), s -> 6);
+        BodyHandler<Number>  bh02 = BodyHandlers.fromSubscriber(new ListSubscriber(), s -> 7);
+        BodyHandler<Integer> bh03 = BodyHandlers.fromSubscriber(new ListSubscriber(), f1);
+        BodyHandler<Number>  bh04 = BodyHandlers.fromSubscriber(new ListSubscriber(), f1);
+        BodyHandler<Number>  bh05 = BodyHandlers.fromSubscriber(new ListSubscriber(), f2);
+        BodyHandler<Integer> bh06 = BodyHandlers.fromSubscriber(new ListSubscriberX(), f1);
+        BodyHandler<Number>  bh07 = BodyHandlers.fromSubscriber(new ListSubscriberX(), f1);
+        BodyHandler<Number>  bh08 = BodyHandlers.fromSubscriber(new ListSubscriberX(), f2);
+        BodyHandler<Integer> bh09 = BodyHandlers.fromSubscriber(new ListSubscriberX(), ListSubscriberX::getIntegerX);
+        BodyHandler<Number>  bh10 = BodyHandlers.fromSubscriber(new ListSubscriberX(), ListSubscriberX::getIntegerX);
+        BodyHandler<Integer> bh11 = BodyHandlers.fromSubscriber(new ListSubscriberX(), f3);
+        BodyHandler<Number>  bh12 = BodyHandlers.fromSubscriber(new ListSubscriberX(), f3);
+        BodyHandler<Number>  bh13 = BodyHandlers.fromSubscriber(new ListSubscriberX(), f4);
+
+        BodySubscriber<Integer> bs01 = BodySubscribers.fromSubscriber(new ListSubscriber(), s -> 6);
+        BodySubscriber<Number>  bs02 = BodySubscribers.fromSubscriber(new ListSubscriber(), s -> 7);
+        BodySubscriber<Integer> bs03 = BodySubscribers.fromSubscriber(new ListSubscriber(), f1);
+        BodySubscriber<Number>  bs04 = BodySubscribers.fromSubscriber(new ListSubscriber(), f1);
+        BodySubscriber<Number>  bs05 = BodySubscribers.fromSubscriber(new ListSubscriber(), f2);
+        BodySubscriber<Integer> bs06 = BodySubscribers.fromSubscriber(new ListSubscriberX(), f1);
+        BodySubscriber<Number>  bs07 = BodySubscribers.fromSubscriber(new ListSubscriberX(), f1);
+        BodySubscriber<Number>  bs08 = BodySubscribers.fromSubscriber(new ListSubscriberX(), f2);
+        BodySubscriber<Integer> bs09 = BodySubscribers.fromSubscriber(new ListSubscriberX(), ListSubscriberX::getIntegerX);
+        BodySubscriber<Number>  bs10 = BodySubscribers.fromSubscriber(new ListSubscriberX(), ListSubscriberX::getIntegerX);
+        BodySubscriber<Integer> bs11 = BodySubscribers.fromSubscriber(new ListSubscriberX(), f3);
+        BodySubscriber<Number>  bs12 = BodySubscribers.fromSubscriber(new ListSubscriberX(), f3);
+        BodySubscriber<Number>  bs13 = BodySubscribers.fromSubscriber(new ListSubscriberX(), f4);
+    }
+
+    // ---
+
+    static class NumberSubscriber implements Flow.Subscriber<List<ByteBuffer>> {
+        @Override public void onSubscribe(Flow.Subscription subscription) { }
+        @Override public void onNext(List<ByteBuffer> item) { }
+        @Override public void onError(Throwable throwable) { }
+        @Override public void onComplete() { }
+        public Number getNumber() { return null; }
+    }
+
+    static class IntegerSubscriber extends NumberSubscriber {
+        @Override public void onSubscribe(Flow.Subscription subscription) { }
+        @Override public void onNext(List<ByteBuffer> item) { }
+        @Override public void onError(Throwable throwable) { }
+        @Override public void onComplete() { }
+        public Integer getInteger() { return null; }
+    }
+
+    static class LongSubscriber extends NumberSubscriber {
+        @Override public void onSubscribe(Flow.Subscription subscription) { }
+        @Override public void onNext(List<ByteBuffer> item) { }
+        @Override public void onError(Throwable throwable) { }
+        @Override public void onComplete() { }
+        public Long getLong() { return null; }
+    }
+
+    static final Function<NumberSubscriber,Number> numMapper = sub -> sub.getNumber();
+    static final Function<IntegerSubscriber,Integer> intMapper = sub -> sub.getInteger();
+    static final Function<LongSubscriber,Long> longMapper = sub -> sub.getLong();
+
+    public void makesSureDifferentGenericSubscriberSignaturesCompile()
+        throws Exception
+    {
+        HttpClient client = null;
+        HttpRequest request = null;
+        IntegerSubscriber sub1 = new IntegerSubscriber();
+
+        HttpResponse<Integer> r1 = client.send(request, BodyHandlers.fromSubscriber(sub1, IntegerSubscriber::getInteger));
+        HttpResponse<Number>  r2 = client.send(request, BodyHandlers.fromSubscriber(sub1, IntegerSubscriber::getInteger));
+        HttpResponse<Number>  r3 = client.send(request, BodyHandlers.fromSubscriber(sub1, NumberSubscriber::getNumber));
+        HttpResponse<Integer> r4 = client.send(request, BodyHandlers.fromSubscriber(sub1, intMapper));
+        HttpResponse<Number>  r5 = client.send(request, BodyHandlers.fromSubscriber(sub1, intMapper));
+        HttpResponse<Number>  r6 = client.send(request, BodyHandlers.fromSubscriber(sub1, numMapper));
+
+        // compiles but makes little sense. Just what you get with any usage of `? super`
+        final Function<Object,Number> objectMapper = sub -> 1;
+        client.sendAsync(request, BodyHandlers.fromSubscriber(sub1, objectMapper));
+
+        // does not compile, as expected ( uncomment to see )
+        //HttpResponse<Number> r7 = client.send(request, BodyHandler.fromSubscriber(sub1, longMapper));
+    }
 }
--- a/test/jdk/java/net/httpclient/HandshakeFailureTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HandshakeFailureTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -28,18 +28,20 @@
 import java.io.DataInputStream;
 import java.io.IOException;
 import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.URI;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpClient.Version;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpRequest;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpResponse;
+import java.net.http.HttpRequest;
 import static java.lang.System.out;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
+import static java.net.http.HttpResponse.BodyHandlers.discarding;
 
 /**
  * @test
@@ -63,7 +65,7 @@
         for (AbstractServer server : servers) {
             try (server) {
                 out.format("%n%n------ Testing with server:%s ------%n", server);
-                URI uri = new URI("https://127.0.0.1:" + server.getPort() + "/");
+                URI uri = new URI("https://localhost:" + server.getPort() + "/");
 
                 test.testSyncSameClient(uri, Version.HTTP_1_1);
                 test.testSyncSameClient(uri, Version.HTTP_2);
@@ -87,7 +89,7 @@
                                              .version(version)
                                              .build();
             try {
-                HttpResponse<Void> response = client.send(request, discard(null));
+                HttpResponse<Void> response = client.send(request, discarding());
                 String msg = String.format("UNEXPECTED response=%s%n", response);
                 throw new RuntimeException(msg);
             } catch (SSLHandshakeException expected) {
@@ -106,7 +108,7 @@
                                              .version(version)
                                              .build();
             try {
-                HttpResponse<Void> response = client.send(request, discard(null));
+                HttpResponse<Void> response = client.send(request, discarding());
                 String msg = String.format("UNEXPECTED response=%s%n", response);
                 throw new RuntimeException(msg);
             } catch (SSLHandshakeException expected) {
@@ -124,7 +126,7 @@
                                              .version(version)
                                              .build();
             CompletableFuture<HttpResponse<Void>> response =
-                        client.sendAsync(request, discard(null));
+                        client.sendAsync(request, discarding());
             try {
                 response.join();
                 String msg = String.format("UNEXPECTED response=%s%n", response);
@@ -150,7 +152,7 @@
                                              .version(version)
                                              .build();
             CompletableFuture<HttpResponse<Void>> response =
-                    client.sendAsync(request, discard(null));
+                    client.sendAsync(request, discarding());
             try {
                 response.join();
                 String msg = String.format("UNEXPECTED response=%s%n", response);
@@ -173,6 +175,8 @@
 
         AbstractServer(String name, ServerSocket ss) throws IOException {
             super(name);
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             this.ss = ss;
             this.start();
         }
@@ -198,7 +202,7 @@
         private volatile int count;
 
         PlainServer() throws IOException {
-            super("PlainServer", new ServerSocket(0));
+            super("PlainServer", new ServerSocket());
         }
 
         @Override
@@ -265,7 +269,7 @@
         }
 
         SSLServer() throws IOException {
-            super("SSLServer", factory.createServerSocket(0));
+            super("SSLServer", factory.createServerSocket());
         }
 
         @Override
--- a/test/jdk/java/net/httpclient/HeadersTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HeadersTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -21,24 +21,298 @@
  * questions.
  */
 
-import jdk.incubator.http.HttpRequest;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
 import java.net.URI;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
 
 /**
  * @test
  * @bug 8087112
- * @summary Basic test for headers
+ * @summary Basic test for headers, uri, and duration
  */
 public class HeadersTest {
 
     static final URI TEST_URI = URI.create("http://www.foo.com/");
+    static final HttpClient client = HttpClient.newBuilder().proxy(NO_PROXY).build();
 
-    static void bad(String name) {
+    static final class HttpHeadersStub extends HttpHeaders {
+        Map<String, List<String>> map;
+        HttpHeadersStub(Map<String, List<String>> map) {
+            this.map = map;
+        }
+        @Override
+        public Map<String, List<String>> map() {
+            return map;
+        }
+    }
+
+    static void bad(String name) throws Exception {
         HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
         try {
             builder.header(name, "foo");
             throw new RuntimeException("Expected IAE for header:" + name);
-        } catch (IllegalArgumentException expected) { }
+        } catch (IllegalArgumentException expected)  {
+            System.out.println("Got expected IAE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    Map<String, List<String>> map = Map.of(name, List.of("foo"));
+                    return new HttpHeadersStub(map);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected IAE for header:" + name);
+        } catch (IllegalArgumentException expected) {
+            System.out.println("Got expected IAE: " + expected);
+        }
+    }
+
+    static void badValue(String value) throws Exception {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.header("x-bad", value);
+            throw new RuntimeException("Expected IAE for header x-bad: "
+                    + value.replace("\r", "\\r")
+                    .replace("\n", "\\n"));
+        } catch (IllegalArgumentException expected)  {
+            System.out.println("Got expected IAE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    Map<String, List<String>> map = Map.of("x-bad", List.of(value));
+                    return new HttpHeadersStub(map);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected IAE for header x-bad:"
+                    + value.replace("\r", "\\r")
+                    .replace("\n", "\\n"));
+        } catch (IllegalArgumentException expected) {
+            System.out.println("Got expected IAE: " + expected);
+        }
+    }
+
+    static void nullName() throws Exception {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.header(null, "foo");
+            throw new RuntimeException("Expected NPE for null header name");
+        } catch (NullPointerException expected)  {
+            System.out.println("Got expected NPE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    Map<String, List<String>> map = new HashMap<>();
+                    map.put(null, List.of("foo"));
+                    return new HttpHeadersStub(map);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected NPE for null header name");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
+    }
+
+    static void nullValue() throws Exception {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.header("x-bar", null);
+            throw new RuntimeException("Expected NPE for null header value");
+        } catch (NullPointerException expected)  {
+            System.out.println("Got expected NPE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    Map<String, List<String>> map = new HashMap<>();
+                    map.put("x-bar", null);
+                    return new HttpHeadersStub(map);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected NPE for null header values");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    List<String> values = new ArrayList<>();
+                    values.add("foo");
+                    values.add(null);
+                    return new HttpHeadersStub(Map.of("x-bar", values));
+                }
+            };
+            client
+                    .send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected NPE for null header value");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
+    }
+
+    static void nullHeaders() throws Exception {
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    return new HttpHeadersStub(null);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected NPE for null header name");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    return null;
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected NPE for null header name");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
     }
 
     static void good(String name) {
@@ -50,7 +324,219 @@
         }
     }
 
-    public static void main(String[] args) {
+    static void goodValue(String value) {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.header("x-good", value);
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException("Unexpected IAE for x-good: " + value);
+        }
+    }
+
+    static void badURI() throws Exception {
+        HttpRequest.Builder builder = HttpRequest.newBuilder();
+        URI uri = URI.create(TEST_URI.toString().replace("http", "ftp"));
+        try {
+            builder.uri(uri);
+            throw new RuntimeException("Expected IAE for uri: " + uri);
+        } catch (IllegalArgumentException expected)  {
+            System.out.println("Got expected IAE: " + expected);
+        }
+        try {
+            HttpRequest.newBuilder(uri);
+            throw new RuntimeException("Expected IAE for uri: " + uri);
+        } catch (IllegalArgumentException expected)  {
+            System.out.println("Got expected IAE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return uri;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
+                    return new HttpHeadersStub(map);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected IAE for uri:" + uri);
+        } catch (IllegalArgumentException expected) {
+            System.out.println("Got expected IAE: " + expected);
+        }
+    }
+
+    static void nullURI() throws Exception {
+        HttpRequest.Builder builder = HttpRequest.newBuilder();
+        try {
+            builder.uri(null);
+            throw new RuntimeException("Expected NPE for null URI");
+        } catch (NullPointerException expected)  {
+            System.out.println("Got expected NPE: " + expected);
+        }
+        try {
+            HttpRequest.newBuilder(null);
+            throw new RuntimeException("Expected NPE for null uri");
+        } catch (NullPointerException expected)  {
+            System.out.println("Got expected NPE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return "GET";
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return null;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
+                    return new HttpHeadersStub(map);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected NPE for null uri");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
+    }
+
+    static void badTimeout() throws Exception {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        Duration zero = Duration.ofSeconds(0);
+        Duration negative = Duration.ofSeconds(-10);
+        for (Duration bad : List.of(zero, negative)) {
+            try {
+                builder.timeout(zero);
+                throw new RuntimeException("Expected IAE for timeout: " + bad);
+            } catch (IllegalArgumentException expected) {
+                System.out.println("Got expected IAE: " + expected);
+            }
+            try {
+                HttpRequest req = new HttpRequest() {
+                    @Override
+                    public Optional<BodyPublisher> bodyPublisher() {
+                        return Optional.of(BodyPublishers.noBody());
+                    }
+
+                    @Override
+                    public String method() {
+                        return "GET";
+                    }
+
+                    @Override
+                    public Optional<Duration> timeout() {
+                        return Optional.of(bad);
+                    }
+
+                    @Override
+                    public boolean expectContinue() {
+                        return false;
+                    }
+
+                    @Override
+                    public URI uri() {
+                        return TEST_URI;
+                    }
+
+                    @Override
+                    public Optional<HttpClient.Version> version() {
+                        return Optional.empty();
+                    }
+
+                    @Override
+                    public HttpHeaders headers() {
+                        Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
+                        return new HttpHeadersStub(map);
+                    }
+                };
+                client.send(req, HttpResponse.BodyHandlers.ofString());
+                throw new RuntimeException("Expected IAE for timeout:" + bad);
+            } catch (IllegalArgumentException expected) {
+                System.out.println("Got expected IAE: " + expected);
+            }
+        }
+    }
+
+    static void nullTimeout() throws Exception {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.timeout(null);
+            throw new RuntimeException("Expected NPE for null timeout");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override
+                public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+
+                @Override
+                public String method() {
+                    return "GET";
+                }
+
+                @Override
+                public Optional<Duration> timeout() {
+                    return null;
+                }
+
+                @Override
+                public boolean expectContinue() {
+                    return false;
+                }
+
+                @Override
+                public URI uri() {
+                    return TEST_URI;
+                }
+
+                @Override
+                public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+
+                @Override
+                public HttpHeaders headers() {
+                    Map<String, List<String>> map = Map.of("x-good", List.of("foo"));
+                    return new HttpHeadersStub(map);
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected NPE for null timeout");
+        } catch (NullPointerException expected) {
+            System.out.println("Got expected NPE: " + expected);
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
         bad("bad:header");
         bad("Foo\n");
         good("X-Foo!");
@@ -60,5 +546,16 @@
         bad("Bar\r\n");
         good("Hello#world");
         good("Qwer#ert");
+        badValue("blah\r\n blah");
+        goodValue("blah blah");
+        goodValue("blah  blah");
+        goodValue("\"blah\\\"  \\\"blah\"");
+        nullName();
+        nullValue();
+        nullHeaders();
+        badURI();
+        nullURI();
+        badTimeout();
+        nullTimeout();
     }
 }
--- a/test/jdk/java/net/httpclient/HeadersTest1.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HeadersTest1.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,19 +24,20 @@
 /*
  * @test
  * @bug 8153142 8195138
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          jdk.httpserver
  * @run testng/othervm HeadersTest1
  */
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -48,7 +49,7 @@
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
 import org.testng.annotations.Test;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.net.http.HttpResponse.BodyHandlers.ofString;
 import static java.nio.charset.StandardCharsets.US_ASCII;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -58,10 +59,12 @@
 public class HeadersTest1 {
 
     private static final String RESPONSE = "Hello world";
+    private static final String QUOTED = "a=\"quote\", b=\"\\\"quote\\\"  \\\"quote\\\"  codec\"";
 
     @Test
     public void test() throws Exception {
-        HttpServer server = HttpServer.create(new InetSocketAddress(0), 10);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        HttpServer server = HttpServer.create(addr, 10);
         Handler h = new Handler();
         server.createContext("/test", h);
         int port = server.getAddress().getPort();
@@ -75,14 +78,15 @@
                                       .build();
 
         try {
-            URI uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/test/foo");
+            URI uri = new URI("http://localhost:" + port + "/test/foo");
             HttpRequest req = HttpRequest.newBuilder(uri)
                                          .headers("X-Bar", "foo1")
                                          .headers("X-Bar", "foo2")
+                                         .headers("X-Quote",QUOTED)
                                          .GET()
                                          .build();
 
-            HttpResponse<?> resp = client.send(req, asString());
+            HttpResponse<?> resp = client.send(req, ofString());
             if (resp.statusCode() != 200) {
                 throw new RuntimeException("Expected 200, got: " + resp.statusCode());
             }
@@ -123,6 +127,10 @@
             assertTrue(multiline.get(0).contains(" foo=\"bar\""));
             assertTrue(multiline.get(0).contains(" bar=\"foo\""));
             assertTrue(multiline.get(0).contains(" foobar=\"barfoo\""));
+
+            // quote
+            List<String> quote = hd.allValues("X-Quote-Response");
+            assertEquals(quote, List.of(QUOTED));
         } finally {
             server.stop(0);
             e.shutdownNow();
@@ -143,12 +151,20 @@
                 he.close();
                 return;
             }
+            l = he.getRequestHeaders().get("X-Quote");
+            if (l.isEmpty() || l.size() != 1 || !QUOTED.equals(l.get(0))) {
+                System.out.println("Bad X-Quote: " + l);
+                he.sendResponseHeaders(500, -1);
+                he.close();
+                return;
+            }
             Headers h = he.getResponseHeaders();
             h.add("X-Foo-Response", "resp1");
             h.add("X-Foo-Response", "resp2");
             h.add("X-multi-line-response", "Custom foo=\"bar\","
                     + "\r\n    bar=\"foo\","
                     + "\r\n    foobar=\"barfoo\"");
+            h.add("X-Quote-Response", he.getRequestHeaders().getFirst("X-Quote"));
             he.sendResponseHeaders(200, RESPONSE.length());
             OutputStream os = he.getResponseBody();
             os.write(RESPONSE.getBytes(US_ASCII));
--- a/test/jdk/java/net/httpclient/HeadersTest2.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HeadersTest2.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,8 +27,8 @@
  * @summary Basic test for headers
  */
 
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
 import java.net.URI;
 import java.util.List;
 import java.util.Iterator;
--- a/test/jdk/java/net/httpclient/HttpClientBuilderTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HttpClientBuilderTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -21,19 +21,33 @@
  * questions.
  */
 
+import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.Authenticator;
 import java.net.CookieHandler;
 import java.net.CookieManager;
 import java.net.InetSocketAddress;
 import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.time.Duration;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpClient.Redirect;
-import jdk.incubator.http.HttpClient.Version;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Redirect;
+import java.net.http.HttpClient.Version;
 import jdk.testlibrary.SimpleSSLContext;
 import org.testng.annotations.Test;
 import static org.testng.Assert.*;
@@ -48,6 +62,9 @@
 
 public class HttpClientBuilderTest {
 
+    static final Class<NullPointerException> NPE = NullPointerException.class;
+    static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
+
     @Test
     public void testDefaults() throws Exception {
         List<HttpClient> clients = List.of(HttpClient.newHttpClient(),
@@ -69,14 +86,14 @@
     @Test
     public void testNull() throws Exception {
         HttpClient.Builder builder = HttpClient.newBuilder();
-        assertThrows(NullPointerException.class, () -> builder.authenticator(null));
-        assertThrows(NullPointerException.class, () -> builder.cookieHandler(null));
-        assertThrows(NullPointerException.class, () -> builder.executor(null));
-        assertThrows(NullPointerException.class, () -> builder.proxy(null));
-        assertThrows(NullPointerException.class, () -> builder.sslParameters(null));
-        assertThrows(NullPointerException.class, () -> builder.followRedirects(null));
-        assertThrows(NullPointerException.class, () -> builder.sslContext(null));
-        assertThrows(NullPointerException.class, () -> builder.version(null));
+        assertThrows(NPE, () -> builder.authenticator(null));
+        assertThrows(NPE, () -> builder.cookieHandler(null));
+        assertThrows(NPE, () -> builder.executor(null));
+        assertThrows(NPE, () -> builder.proxy(null));
+        assertThrows(NPE, () -> builder.sslParameters(null));
+        assertThrows(NPE, () -> builder.followRedirects(null));
+        assertThrows(NPE, () -> builder.sslContext(null));
+        assertThrows(NPE, () -> builder.version(null));
     }
 
     static class TestAuthenticator extends Authenticator { }
@@ -90,7 +107,7 @@
         Authenticator b = new TestAuthenticator();
         builder.authenticator(b);
         assertTrue(builder.build().authenticator().get() == b);
-        assertThrows(NullPointerException.class, () -> builder.authenticator(null));
+        assertThrows(NPE, () -> builder.authenticator(null));
         Authenticator c = new TestAuthenticator();
         builder.authenticator(c);
         assertTrue(builder.build().authenticator().get() == c);
@@ -105,7 +122,7 @@
         CookieHandler b = new CookieManager();
         builder.cookieHandler(b);
         assertTrue(builder.build().cookieHandler().get() == b);
-        assertThrows(NullPointerException.class, () -> builder.cookieHandler(null));
+        assertThrows(NPE, () -> builder.cookieHandler(null));
         CookieManager c = new CookieManager();
         builder.cookieHandler(c);
         assertTrue(builder.build().cookieHandler().get() == c);
@@ -124,7 +141,7 @@
         TestExecutor b = new TestExecutor();
         builder.executor(b);
         assertTrue(builder.build().executor().get() == b);
-        assertThrows(NullPointerException.class, () -> builder.executor(null));
+        assertThrows(NPE, () -> builder.executor(null));
         TestExecutor c = new TestExecutor();
         builder.executor(c);
         assertTrue(builder.build().executor().get() == c);
@@ -139,7 +156,7 @@
         ProxySelector b = ProxySelector.of(InetSocketAddress.createUnresolved("foo", 80));
         builder.proxy(b);
         assertTrue(builder.build().proxy().get() == b);
-        assertThrows(NullPointerException.class, () -> builder.proxy(null));
+        assertThrows(NPE, () -> builder.proxy(null));
         ProxySelector c = ProxySelector.of(InetSocketAddress.createUnresolved("bar", 80));
         builder.proxy(c);
         assertTrue(builder.build().proxy().get() == c);
@@ -159,7 +176,7 @@
         builder.sslParameters(b);
         assertTrue(builder.build().sslParameters() != b);
         assertTrue(builder.build().sslParameters().getEnableRetransmissions());
-        assertThrows(NullPointerException.class, () -> builder.sslParameters(null));
+        assertThrows(NPE, () -> builder.sslParameters(null));
         SSLParameters c = new SSLParameters();
         c.setProtocols(new String[] { "C" });
         builder.sslParameters(c);
@@ -176,7 +193,7 @@
         SSLContext b = (new SimpleSSLContext()).get();
         builder.sslContext(b);
         assertTrue(builder.build().sslContext() == b);
-        assertThrows(NullPointerException.class, () -> builder.sslContext(null));
+        assertThrows(NPE, () -> builder.sslContext(null));
         SSLContext c = (new SimpleSSLContext()).get();
         builder.sslContext(c);
         assertTrue(builder.build().sslContext() == c);
@@ -189,11 +206,9 @@
         assertTrue(builder.build().followRedirects() == Redirect.ALWAYS);
         builder.followRedirects(Redirect.NEVER);
         assertTrue(builder.build().followRedirects() == Redirect.NEVER);
-        assertThrows(NullPointerException.class, () -> builder.followRedirects(null));
-        builder.followRedirects(Redirect.SAME_PROTOCOL);
-        assertTrue(builder.build().followRedirects() == Redirect.SAME_PROTOCOL);
-        builder.followRedirects(Redirect.SECURE);
-        assertTrue(builder.build().followRedirects() == Redirect.SECURE);
+        assertThrows(NPE, () -> builder.followRedirects(null));
+        builder.followRedirects(Redirect.NORMAL);
+        assertTrue(builder.build().followRedirects() == Redirect.NORMAL);
     }
 
     @Test
@@ -203,7 +218,7 @@
         assertTrue(builder.build().version() == Version.HTTP_2);
         builder.version(Version.HTTP_1_1);
         assertTrue(builder.build().version() == Version.HTTP_1_1);
-        assertThrows(NullPointerException.class, () -> builder.version(null));
+        assertThrows(NPE, () -> builder.version(null));
         builder.version(Version.HTTP_2);
         assertTrue(builder.build().version() == Version.HTTP_2);
         builder.version(Version.HTTP_1_1);
@@ -213,10 +228,10 @@
     @Test
     static void testPriority() throws Exception {
         HttpClient.Builder builder = HttpClient.newBuilder();
-        assertThrows(IllegalArgumentException.class, () -> builder.priority(-1));
-        assertThrows(IllegalArgumentException.class, () -> builder.priority(0));
-        assertThrows(IllegalArgumentException.class, () -> builder.priority(257));
-        assertThrows(IllegalArgumentException.class, () -> builder.priority(500));
+        assertThrows(IAE, () -> builder.priority(-1));
+        assertThrows(IAE, () -> builder.priority(0));
+        assertThrows(IAE, () -> builder.priority(257));
+        assertThrows(IAE, () -> builder.priority(500));
 
         builder.priority(1);
         builder.build();
@@ -224,6 +239,90 @@
         builder.build();
     }
 
+    // ---
+
+    static final URI uri = URI.create("http://foo.com/");
+
+    @Test
+    static void testHttpClientSendArgs() throws Exception {
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder(uri).build();
+
+        assertThrows(NPE, () -> client.send(null, BodyHandlers.discarding()));
+        assertThrows(NPE, () -> client.send(request, null));
+        assertThrows(NPE, () -> client.send(null, null));
+
+        assertThrows(NPE, () -> client.sendAsync(null, BodyHandlers.discarding()));
+        assertThrows(NPE, () -> client.sendAsync(request, null));
+        assertThrows(NPE, () -> client.sendAsync(null, null));
+
+        assertThrows(NPE, () -> client.sendAsync(null, BodyHandlers.discarding(), null));
+        assertThrows(NPE, () -> client.sendAsync(request, null, null));
+        assertThrows(NPE, () -> client.sendAsync(null, null, null));
+
+        // CONNECT is disallowed in the implementation, since it is used for
+        // tunneling, and is handled separately for security checks.
+        HttpRequest connectRequest = new HttpConnectRequest();
+        assertThrows(IAE, () -> client.send(connectRequest, BodyHandlers.discarding()));
+        assertThrows(IAE, () -> client.sendAsync(connectRequest, BodyHandlers.discarding()));
+        assertThrows(IAE, () -> client.sendAsync(connectRequest, BodyHandlers.discarding(), null));
+    }
+
+    static class HttpConnectRequest extends HttpRequest {
+        @Override public Optional<BodyPublisher> bodyPublisher() { return Optional.empty(); }
+        @Override public String method() { return "CONNECT"; }
+        @Override public Optional<Duration> timeout() { return Optional.empty(); }
+        @Override public boolean expectContinue() { return false; }
+        @Override public URI uri() { return URI.create("http://foo.com/"); }
+        @Override public Optional<Version> version() { return Optional.empty(); }
+        private final FixedHttpHeaders headers = new FixedHttpHeaders();
+        @Override public HttpHeaders headers() { return headers; }
+        public class FixedHttpHeaders extends HttpHeaders {
+            private final Map<String, List<String>> map =
+                    new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+            @Override
+            public Map<String, List<String>> map() {
+                return map;
+            }
+        }
+    }
+
+    // ---
+
+    static final Class<UnsupportedOperationException> UOE =
+            UnsupportedOperationException.class;
+
+    @Test
+    static void testUnsupportedWebSocket() throws Exception {
+        //  @implSpec The default implementation of this method throws
+        // {@code UnsupportedOperationException}.
+        assertThrows(UOE, () -> (new MockHttpClient()).newWebSocketBuilder());
+    }
+
+    static class MockHttpClient extends HttpClient {
+        @Override public Optional<CookieHandler> cookieHandler() { return null; }
+        @Override public Redirect followRedirects() { return null; }
+        @Override public Optional<ProxySelector> proxy() { return null; }
+        @Override public SSLContext sslContext() { return null; }
+        @Override public SSLParameters sslParameters() { return null; }
+        @Override public Optional<Authenticator> authenticator() { return null; }
+        @Override public Version version() { return null; }
+        @Override public Optional<Executor> executor() { return null; }
+        @Override public <T> HttpResponse<T>
+        send(HttpRequest request, BodyHandler<T> responseBodyHandler)
+                throws IOException, InterruptedException {
+            return null;
+        }
+        @Override public <T> CompletableFuture<HttpResponse<T>>
+        sendAsync(HttpRequest request, BodyHandler<T> responseBodyHandler) {
+            return null;
+        }
+        @Override
+        public <T> CompletableFuture<HttpResponse<T>>
+        sendAsync(HttpRequest x, BodyHandler<T> y, PushPromiseHandler<T> z) {
+            return null;
+        }
+    }
 
     /* ---- standalone entry point ---- */
 
--- a/test/jdk/java/net/httpclient/HttpEchoHandler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HttpEchoHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 import com.sun.net.httpserver.*;
 import java.net.*;
-import jdk.incubator.http.*;
+import java.net.http.*;
 import java.io.*;
 import java.util.concurrent.*;
 import javax.net.ssl.*;
@@ -33,13 +33,15 @@
 import java.util.List;
 import java.util.Random;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpRequest.*;
-import static jdk.incubator.http.HttpResponse.*;
+import static java.net.http.HttpRequest.*;
+import static java.net.http.HttpResponse.*;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 public class HttpEchoHandler implements HttpHandler {
+    static final Path CWD = Paths.get(".");
+
     public HttpEchoHandler() {}
 
     @Override
@@ -53,7 +55,7 @@
             map1.add("X-Hello", "world");
             map1.add("X-Bye", "universe");
             String fixedrequest = map.getFirst("XFixed");
-            File outfile = File.createTempFile("foo", "bar");
+            File outfile = Files.createTempFile(CWD, "foo", "bar").toFile();
             FileOutputStream fos = new FileOutputStream(outfile);
             int count = (int) is.transferTo(fos);
             is.close();
--- a/test/jdk/java/net/httpclient/HttpInputStreamTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HttpInputStreamTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -26,10 +26,10 @@
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Iterator;
@@ -78,7 +78,7 @@
 
         @Override
         public HttpResponse.BodySubscriber<InputStream>
-                apply(int i, HttpHeaders hh) {
+                apply(HttpResponse.ResponseInfo rinfo) {
             return new HttpResponseInputStream(maxBuffers);
         }
 
@@ -293,11 +293,11 @@
         // This example shows how to return an InputStream that can be used to
         // start reading the response body before the response is fully received.
         // In comparison, the snipet below (which uses
-        // HttpResponse.BodyHandler.asString()) obviously will not return before the
+        // HttpResponse.BodyHandlers.ofString()) obviously will not return before the
         // response body is fully read:
         //
         // System.out.println(
-        //    client.sendAsync(request, HttpResponse.BodyHandler.asString()).get().body());
+        //    client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).get().body());
 
         CompletableFuture<HttpResponse<InputStream>> handle =
             client.sendAsync(request, new HttpInputStreamHandler(3));
--- a/test/jdk/java/net/httpclient/HttpRequestBuilderTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HttpRequestBuilderTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -22,7 +22,7 @@
  */
 
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
+import java.net.http.HttpClient;
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.function.BiFunction;
@@ -30,9 +30,9 @@
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import jdk.incubator.http.HttpRequest;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.noBody;
+import java.net.http.HttpRequest;
+import static java.net.http.HttpRequest.BodyPublishers.ofString;
+import static java.net.http.HttpRequest.BodyPublishers.noBody;
 
 /**
  * @test
@@ -155,8 +155,7 @@
                         (String[]) new String[] {"foo"},
                         IllegalArgumentException.class);
 
-        builder = test1("DELETE", builder, builder::DELETE,
-                        noBody(), null);
+        test0("DELETE", () -> HttpRequest.newBuilder(TEST_URI).DELETE().build(), null);
 
         builder = test1("POST", builder, builder::POST,
                         noBody(), null);
@@ -167,10 +166,6 @@
         builder = test2("method", builder, builder::method, "GET",
                         noBody(), null);
 
-        builder = test1("DELETE", builder, builder::DELETE,
-                        (HttpRequest.BodyPublisher)null,
-                        NullPointerException.class);
-
         builder = test1("POST", builder, builder::POST,
                         (HttpRequest.BodyPublisher)null,
                         NullPointerException.class);
@@ -204,12 +199,12 @@
                         NullPointerException.class);
 
         builder = test2("method", builder, builder::method, null,
-                        fromString("foo"),
+                        ofString("foo"),
                         NullPointerException.class);
 // see JDK-8170093
 //
 //        builder = test2("method", builder, builder::method, "foo",
-//                       HttpRequest.BodyProcessor.fromString("foo"),
+//                       HttpRequest.BodyProcessor.ofString("foo"),
 //                       IllegalArgumentException.class);
 //
 //        builder.build();
@@ -223,40 +218,40 @@
                () -> HttpRequest.newBuilder(TEST_URI).GET(),
                "GET");
 
-        method("newBuilder(TEST_URI).POST(fromString(\"\")).GET().build().method() == GET",
-               () -> HttpRequest.newBuilder(TEST_URI).POST(fromString("")).GET(),
+        method("newBuilder(TEST_URI).POST(ofString(\"\")).GET().build().method() == GET",
+               () -> HttpRequest.newBuilder(TEST_URI).POST(ofString("")).GET(),
                "GET");
 
-        method("newBuilder(TEST_URI).PUT(fromString(\"\")).GET().build().method() == GET",
-               () -> HttpRequest.newBuilder(TEST_URI).PUT(fromString("")).GET(),
+        method("newBuilder(TEST_URI).PUT(ofString(\"\")).GET().build().method() == GET",
+               () -> HttpRequest.newBuilder(TEST_URI).PUT(ofString("")).GET(),
                "GET");
 
-        method("newBuilder(TEST_URI).DELETE(fromString(\"\")).GET().build().method() == GET",
-               () -> HttpRequest.newBuilder(TEST_URI).DELETE(fromString("")).GET(),
+        method("newBuilder(TEST_URI).DELETE().GET().build().method() == GET",
+               () -> HttpRequest.newBuilder(TEST_URI).DELETE().GET(),
                "GET");
 
-        method("newBuilder(TEST_URI).POST(fromString(\"\")).build().method() == POST",
-               () -> HttpRequest.newBuilder(TEST_URI).POST(fromString("")),
+        method("newBuilder(TEST_URI).POST(ofString(\"\")).build().method() == POST",
+               () -> HttpRequest.newBuilder(TEST_URI).POST(ofString("")),
                "POST");
 
-        method("newBuilder(TEST_URI).PUT(fromString(\"\")).build().method() == PUT",
-               () -> HttpRequest.newBuilder(TEST_URI).PUT(fromString("")),
+        method("newBuilder(TEST_URI).PUT(ofString(\"\")).build().method() == PUT",
+               () -> HttpRequest.newBuilder(TEST_URI).PUT(ofString("")),
                "PUT");
 
-        method("newBuilder(TEST_URI).DELETE(fromString(\"\")).build().method() == DELETE",
-               () -> HttpRequest.newBuilder(TEST_URI).DELETE(fromString("")),
+        method("newBuilder(TEST_URI).DELETE().build().method() == DELETE",
+               () -> HttpRequest.newBuilder(TEST_URI).DELETE(),
                "DELETE");
 
-        method("newBuilder(TEST_URI).GET().POST(fromString(\"\")).build().method() == POST",
-               () -> HttpRequest.newBuilder(TEST_URI).GET().POST(fromString("")),
+        method("newBuilder(TEST_URI).GET().POST(ofString(\"\")).build().method() == POST",
+               () -> HttpRequest.newBuilder(TEST_URI).GET().POST(ofString("")),
                "POST");
 
-        method("newBuilder(TEST_URI).GET().PUT(fromString(\"\")).build().method() == PUT",
-               () -> HttpRequest.newBuilder(TEST_URI).GET().PUT(fromString("")),
+        method("newBuilder(TEST_URI).GET().PUT(ofString(\"\")).build().method() == PUT",
+               () -> HttpRequest.newBuilder(TEST_URI).GET().PUT(ofString("")),
                "PUT");
 
-        method("newBuilder(TEST_URI).GET().DELETE(fromString(\"\")).build().method() == DELETE",
-               () -> HttpRequest.newBuilder(TEST_URI).GET().DELETE(fromString("")),
+        method("newBuilder(TEST_URI).GET().DELETE().build().method() == DELETE",
+               () -> HttpRequest.newBuilder(TEST_URI).GET().DELETE(),
                "DELETE");
 
 
--- a/test/jdk/java/net/httpclient/HttpResponseInputStreamTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/HttpResponseInputStreamTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -21,12 +21,12 @@
  * questions.
  */
 
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
@@ -63,7 +63,7 @@
     @Test
     public static void testOnError() throws InterruptedException, ExecutionException {
         CountDownLatch latch = new CountDownLatch(1);
-        BodySubscriber<InputStream> isb = BodySubscriber.asInputStream();
+        BodySubscriber<InputStream> isb = BodySubscribers.ofInputStream();
         ErrorTestSubscription s = new ErrorTestSubscription(isb);
         CompletionStage<Throwable> cs =
                 isb.getBody().thenApplyAsync((is) -> s.accept(latch, is));
@@ -158,7 +158,7 @@
     public static void testCloseAndSubscribe()
             throws InterruptedException, ExecutionException
     {
-        BodySubscriber<InputStream> isb = BodySubscriber.asInputStream();
+        BodySubscriber<InputStream> isb = BodySubscribers.ofInputStream();
         TestCancelOnCloseSubscription s = new TestCancelOnCloseSubscription();
         InputStream is = isb.getBody()
                 .thenApply(HttpResponseInputStreamTest::close)
@@ -187,7 +187,7 @@
     public static void testSubscribeAndClose()
             throws InterruptedException, ExecutionException
     {
-        BodySubscriber<InputStream> isb = BodySubscriber.asInputStream();
+        BodySubscriber<InputStream> isb = BodySubscribers.ofInputStream();
         TestCancelOnCloseSubscription s = new TestCancelOnCloseSubscription();
         InputStream is = isb.getBody().toCompletableFuture().get();
         isb.onSubscribe(s);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/HttpServerAdapters.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,614 @@
+/*
+ * 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.
+ */
+
+import com.sun.net.httpserver.Filter;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+import java.net.InetAddress;
+import java.io.ByteArrayInputStream;
+import java.net.http.HttpClient.Version;
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.math.BigInteger;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+/**
+ * Defines an adaptation layers so that a test server handlers and filters
+ * can be implemented independently of the underlying server version.
+ * <p>
+ * For instance:
+ * <pre>{@code
+ *
+ *  URI http1URI, http2URI;
+ *
+ *  InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+ *  HttpTestServer server1 = HttpTestServer.of(HttpServer.create(sa, 0));
+ *  HttpTestContext context = server.addHandler(new HttpTestEchoHandler(), "/http1/echo");
+ *  http2URI = "http://localhost:" + server1.getAddress().getPort() + "/http1/echo";
+ *
+ *  Http2TestServer http2TestServer = new Http2TestServer("localhost", false, 0);
+ *  HttpTestServer server2 = HttpTestServer.of(http2TestServer);
+ *  server2.addHandler(new HttpTestEchoHandler(), "/http2/echo");
+ *  http1URI = "http://localhost:" + server2.getAddress().getPort() + "/http2/echo";
+ *
+ *  }</pre>
+ */
+public interface HttpServerAdapters {
+
+    static final boolean PRINTSTACK =
+            Boolean.getBoolean("jdk.internal.httpclient.debug");
+
+    static void uncheckedWrite(ByteArrayOutputStream baos, byte[] ba) {
+        try {
+            baos.write(ba);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    static void printBytes(PrintStream out, String prefix, byte[] bytes) {
+        int padding = 4 + 4 - (bytes.length % 4);
+        padding = padding > 4 ? padding - 4 : 4;
+        byte[] bigbytes = new byte[bytes.length + padding];
+        System.arraycopy(bytes, 0, bigbytes, padding, bytes.length);
+        out.println(prefix + bytes.length + " "
+                    + new BigInteger(bigbytes).toString(16));
+    }
+
+    /**
+     * A version agnostic adapter class for HTTP Headers.
+     */
+    public static abstract class HttpTestHeaders {
+        public abstract Optional<String> firstValue(String name);
+        public abstract void addHeader(String name, String value);
+        public abstract Set<String> keySet();
+        public abstract Set<Map.Entry<String, List<String>>> entrySet();
+        public abstract List<String> get(String name);
+        public abstract boolean containsKey(String name);
+
+        public static HttpTestHeaders of(Headers headers) {
+            return new Http1TestHeaders(headers);
+        }
+        public static HttpTestHeaders of(HttpHeadersImpl headers) {
+            return new Http2TestHeaders(headers);
+        }
+
+        private final static class Http1TestHeaders extends HttpTestHeaders {
+            private final Headers headers;
+            Http1TestHeaders(Headers h) { this.headers = h; }
+            @Override
+            public Optional<String> firstValue(String name) {
+                if (headers.containsKey(name)) {
+                    return Optional.ofNullable(headers.getFirst(name));
+                }
+                return Optional.empty();
+            }
+            @Override
+            public void addHeader(String name, String value) {
+                headers.add(name, value);
+            }
+
+            @Override
+            public Set<String> keySet() { return headers.keySet(); }
+            @Override
+            public Set<Map.Entry<String, List<String>>> entrySet() {
+                return headers.entrySet();
+            }
+            @Override
+            public List<String> get(String name) {
+                return headers.get(name);
+            }
+            @Override
+            public boolean containsKey(String name) {
+                return headers.containsKey(name);
+            }
+        }
+        private final static class Http2TestHeaders extends HttpTestHeaders {
+            private final HttpHeadersImpl headers;
+            Http2TestHeaders(HttpHeadersImpl h) { this.headers = h; }
+            @Override
+            public Optional<String> firstValue(String name) {
+                return headers.firstValue(name);
+            }
+            @Override
+            public void addHeader(String name, String value) {
+                headers.addHeader(name, value);
+            }
+            public Set<String> keySet() { return headers.map().keySet(); }
+            @Override
+            public Set<Map.Entry<String, List<String>>> entrySet() {
+                return headers.map().entrySet();
+            }
+            @Override
+            public List<String> get(String name) {
+                return headers.allValues(name);
+            }
+            @Override
+            public boolean containsKey(String name) {
+                return headers.firstValue(name).isPresent();
+            }
+        }
+    }
+
+    /**
+     * A version agnostic adapter class for HTTP Server Exchange.
+     */
+    public static abstract class HttpTestExchange {
+        public abstract Version getServerVersion();
+        public abstract Version getExchangeVersion();
+        public abstract InputStream   getRequestBody();
+        public abstract OutputStream  getResponseBody();
+        public abstract HttpTestHeaders getRequestHeaders();
+        public abstract HttpTestHeaders getResponseHeaders();
+        public abstract void sendResponseHeaders(int code, int contentLength) throws IOException;
+        public abstract URI getRequestURI();
+        public abstract String getRequestMethod();
+        public abstract void close();
+        public void serverPush(URI uri, HttpTestHeaders headers, byte[] body) {
+            ByteArrayInputStream bais = new ByteArrayInputStream(body);
+            serverPush(uri, headers, bais);
+        }
+        public void serverPush(URI uri, HttpTestHeaders headers, InputStream body) {
+            throw new UnsupportedOperationException("serverPush with " + getExchangeVersion());
+        }
+        public boolean serverPushAllowed() {
+            return false;
+        }
+        public static HttpTestExchange of(HttpExchange exchange) {
+            return new Http1TestExchange(exchange);
+        }
+        public static HttpTestExchange of(Http2TestExchange exchange) {
+            return new Http2TestExchangeImpl(exchange);
+        }
+
+        abstract void doFilter(Filter.Chain chain) throws IOException;
+
+        // implementations...
+        private static final class Http1TestExchange extends HttpTestExchange {
+            private final HttpExchange exchange;
+            Http1TestExchange(HttpExchange exch) {
+                this.exchange = exch;
+            }
+            @Override
+            public Version getServerVersion() { return Version.HTTP_1_1; }
+            @Override
+            public Version getExchangeVersion() { return Version.HTTP_1_1; }
+            @Override
+            public InputStream getRequestBody() {
+                return exchange.getRequestBody();
+            }
+            @Override
+            public OutputStream getResponseBody() {
+                return exchange.getResponseBody();
+            }
+            @Override
+            public HttpTestHeaders getRequestHeaders() {
+                return HttpTestHeaders.of(exchange.getRequestHeaders());
+            }
+            @Override
+            public HttpTestHeaders getResponseHeaders() {
+                return HttpTestHeaders.of(exchange.getResponseHeaders());
+            }
+            @Override
+            public void sendResponseHeaders(int code, int contentLength) throws IOException {
+                if (contentLength == 0) contentLength = -1;
+                else if (contentLength < 0) contentLength = 0;
+                exchange.sendResponseHeaders(code, contentLength);
+            }
+            @Override
+            void doFilter(Filter.Chain chain) throws IOException {
+                chain.doFilter(exchange);
+            }
+            @Override
+            public void close() { exchange.close(); }
+            @Override
+            public URI getRequestURI() { return exchange.getRequestURI(); }
+            @Override
+            public String getRequestMethod() { return exchange.getRequestMethod(); }
+            @Override
+            public String toString() {
+                return this.getClass().getSimpleName() + ": " + exchange.toString();
+            }
+        }
+
+        private static final class Http2TestExchangeImpl extends HttpTestExchange {
+            private final Http2TestExchange exchange;
+            Http2TestExchangeImpl(Http2TestExchange exch) {
+                this.exchange = exch;
+            }
+            @Override
+            public Version getServerVersion() { return Version.HTTP_2; }
+            @Override
+            public Version getExchangeVersion() { return Version.HTTP_2; }
+            @Override
+            public InputStream getRequestBody() {
+                return exchange.getRequestBody();
+            }
+            @Override
+            public OutputStream getResponseBody() {
+                return exchange.getResponseBody();
+            }
+            @Override
+            public HttpTestHeaders getRequestHeaders() {
+                return HttpTestHeaders.of(exchange.getRequestHeaders());
+            }
+            @Override
+            public HttpTestHeaders getResponseHeaders() {
+                return HttpTestHeaders.of(exchange.getResponseHeaders());
+            }
+            @Override
+            public void sendResponseHeaders(int code, int contentLength) throws IOException {
+                if (contentLength == 0) contentLength = -1;
+                else if (contentLength < 0) contentLength = 0;
+                exchange.sendResponseHeaders(code, contentLength);
+            }
+            @Override
+            public boolean serverPushAllowed() {
+                return exchange.serverPushAllowed();
+            }
+            @Override
+            public void serverPush(URI uri, HttpTestHeaders headers, InputStream body) {
+                HttpHeadersImpl headersImpl;
+                if (headers instanceof HttpTestHeaders.Http2TestHeaders) {
+                    headersImpl = ((HttpTestHeaders.Http2TestHeaders)headers).headers.deepCopy();
+                } else {
+                    headersImpl = new HttpHeadersImpl();
+                    for (Map.Entry<String, List<String>> e : headers.entrySet()) {
+                        String name = e.getKey();
+                        for (String v : e.getValue()) {
+                            headersImpl.addHeader(name, v);
+                        }
+                    }
+                }
+                exchange.serverPush(uri, headersImpl, body);
+            }
+            void doFilter(Filter.Chain filter) throws IOException {
+                throw new IOException("cannot use HTTP/1.1 filter with HTTP/2 server");
+            }
+            @Override
+            public void close() { exchange.close();}
+            @Override
+            public URI getRequestURI() { return exchange.getRequestURI(); }
+            @Override
+            public String getRequestMethod() { return exchange.getRequestMethod(); }
+            @Override
+            public String toString() {
+                return this.getClass().getSimpleName() + ": " + exchange.toString();
+            }
+        }
+
+    }
+
+
+    /**
+     * A version agnostic adapter class for HTTP Server Handlers.
+     */
+    public interface HttpTestHandler {
+        void handle(HttpTestExchange t) throws IOException;
+
+        default HttpHandler toHttpHandler() {
+            return (t) -> doHandle(HttpTestExchange.of(t));
+        }
+        default Http2Handler toHttp2Handler() {
+            return (t) -> doHandle(HttpTestExchange.of(t));
+        }
+        private void doHandle(HttpTestExchange t) throws IOException {
+            try {
+                handle(t);
+            } catch (Throwable x) {
+                System.out.println("WARNING: exception caught in HttpTestHandler::handle " + x);
+                System.err.println("WARNING: exception caught in HttpTestHandler::handle " + x);
+                if (PRINTSTACK && !expectException(t)) x.printStackTrace(System.out);
+                throw x;
+            }
+        }
+    }
+
+
+    public static class HttpTestEchoHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                byte[] bytes = is.readAllBytes();
+                printBytes(System.out,"Echo server got "
+                        + t.getExchangeVersion() + " bytes: ", bytes);
+                if (t.getRequestHeaders().firstValue("Content-type").isPresent()) {
+                    t.getResponseHeaders().addHeader("Content-type",
+                            t.getRequestHeaders().firstValue("Content-type").get());
+                }
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+
+    public static boolean expectException(HttpTestExchange e) {
+        HttpTestHeaders h = e.getRequestHeaders();
+        Optional<String> expectException = h.firstValue("X-expect-exception");
+        if (expectException.isPresent()) {
+            return expectException.get().equalsIgnoreCase("true");
+        }
+        return false;
+    }
+
+    /**
+     * A version agnostic adapter class for HTTP Server Filter Chains.
+     */
+    public abstract class HttpChain {
+
+        public abstract void doFilter(HttpTestExchange exchange) throws IOException;
+        public static HttpChain of(Filter.Chain chain) {
+            return new Http1Chain(chain);
+        }
+
+        public static HttpChain of(List<HttpTestFilter> filters, HttpTestHandler handler) {
+            return new Http2Chain(filters, handler);
+        }
+
+        private static class Http1Chain extends HttpChain {
+            final Filter.Chain chain;
+            Http1Chain(Filter.Chain chain) {
+                this.chain = chain;
+            }
+            @Override
+            public void doFilter(HttpTestExchange exchange) throws IOException {
+                try {
+                    exchange.doFilter(chain);
+                } catch (Throwable t) {
+                    System.out.println("WARNING: exception caught in Http1Chain::doFilter " + t);
+                    System.err.println("WARNING: exception caught in Http1Chain::doFilter " + t);
+                    if (PRINTSTACK && !expectException(exchange)) t.printStackTrace(System.out);
+                    throw t;
+                }
+            }
+        }
+
+        private static class Http2Chain extends HttpChain {
+            ListIterator<HttpTestFilter> iter;
+            HttpTestHandler handler;
+            Http2Chain(List<HttpTestFilter> filters, HttpTestHandler handler) {
+                this.iter = filters.listIterator();
+                this.handler = handler;
+            }
+            @Override
+            public void doFilter(HttpTestExchange exchange) throws IOException {
+                try {
+                    if (iter.hasNext()) {
+                        iter.next().doFilter(exchange, this);
+                    } else {
+                        handler.handle(exchange);
+                    }
+                } catch (Throwable t) {
+                    System.out.println("WARNING: exception caught in Http2Chain::doFilter " + t);
+                    System.err.println("WARNING: exception caught in Http2Chain::doFilter " + t);
+                    if (PRINTSTACK && !expectException(exchange)) t.printStackTrace(System.out);
+                    throw t;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * A version agnostic adapter class for HTTP Server Filters.
+     */
+    public abstract class HttpTestFilter {
+
+        public abstract String description();
+
+        public abstract void doFilter(HttpTestExchange exchange, HttpChain chain) throws IOException;
+
+        public Filter toFilter() {
+            return new Filter() {
+                @Override
+                public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
+                    HttpTestFilter.this.doFilter(HttpTestExchange.of(exchange), HttpChain.of(chain));
+                }
+                @Override
+                public String description() {
+                    return HttpTestFilter.this.description();
+                }
+            };
+        }
+    }
+
+    /**
+     * A version agnostic adapter class for HTTP Server Context.
+     */
+    public static abstract class HttpTestContext {
+        public abstract String getPath();
+        public abstract void addFilter(HttpTestFilter filter);
+        public abstract Version getVersion();
+
+        // will throw UOE if the server is HTTP/2
+        public abstract void setAuthenticator(com.sun.net.httpserver.Authenticator authenticator);
+    }
+
+    /**
+     * A version agnostic adapter class for HTTP Servers.
+     */
+    public static abstract class HttpTestServer {
+        private static final class ServerLogging {
+            private static final Logger logger = Logger.getLogger("com.sun.net.httpserver");
+            static void enableLogging() {
+                logger.setLevel(Level.FINE);
+                Stream.of(Logger.getLogger("").getHandlers())
+                        .forEach(h -> h.setLevel(Level.ALL));
+            }
+        }
+
+        public abstract void start();
+        public abstract void stop();
+        public abstract HttpTestContext addHandler(HttpTestHandler handler, String root);
+        public abstract InetSocketAddress getAddress();
+        public abstract Version getVersion();
+
+        public String serverAuthority() {
+            return InetAddress.getLoopbackAddress().getHostName() + ":"
+                    + getAddress().getPort();
+        }
+
+        public static HttpTestServer of(HttpServer server) {
+            return new Http1TestServer(server);
+        }
+
+        public static HttpTestServer of(Http2TestServer server) {
+            return new Http2TestServerImpl(server);
+        }
+
+        private static class Http1TestServer extends  HttpTestServer {
+            private final HttpServer impl;
+            Http1TestServer(HttpServer server) {
+                this.impl = server;
+            }
+            @Override
+            public void start() {
+                System.out.println("Http1TestServer: start");
+                impl.start();
+            }
+            @Override
+            public void stop() {
+                System.out.println("Http1TestServer: stop");
+                impl.stop(0);
+            }
+            @Override
+            public HttpTestContext addHandler(HttpTestHandler handler, String path) {
+                System.out.println("Http1TestServer[" + getAddress()
+                        + "]::addHandler " + handler + ", " + path);
+                return new Http1TestContext(impl.createContext(path, handler.toHttpHandler()));
+            }
+            @Override
+            public InetSocketAddress getAddress() {
+                return new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                        impl.getAddress().getPort());
+            }
+            public Version getVersion() { return Version.HTTP_1_1; }
+        }
+
+        private static class Http1TestContext extends HttpTestContext {
+            private final HttpContext context;
+            Http1TestContext(HttpContext ctxt) {
+                this.context = ctxt;
+            }
+            @Override public String getPath() {
+                return context.getPath();
+            }
+            @Override
+            public void addFilter(HttpTestFilter filter) {
+                System.out.println("Http1TestContext::addFilter " + filter.description());
+                context.getFilters().add(filter.toFilter());
+            }
+            @Override
+            public void setAuthenticator(com.sun.net.httpserver.Authenticator authenticator) {
+                context.setAuthenticator(authenticator);
+            }
+            @Override public Version getVersion() { return Version.HTTP_1_1; }
+        }
+
+        private static class Http2TestServerImpl extends  HttpTestServer {
+            private final Http2TestServer impl;
+            Http2TestServerImpl(Http2TestServer server) {
+                this.impl = server;
+            }
+            @Override
+            public void start() {
+                System.out.println("Http2TestServerImpl: start");
+                impl.start();
+            }
+            @Override
+            public void stop() {
+                System.out.println("Http2TestServerImpl: stop");
+                impl.stop();
+            }
+            @Override
+            public HttpTestContext addHandler(HttpTestHandler handler, String path) {
+                System.out.println("Http2TestServerImpl[" + getAddress()
+                                   + "]::addHandler " + handler + ", " + path);
+                Http2TestContext context = new Http2TestContext(handler, path);
+                impl.addHandler(context.toHttp2Handler(), path);
+                return context;
+            }
+            @Override
+            public InetSocketAddress getAddress() {
+                return new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                        impl.getAddress().getPort());
+            }
+            public Version getVersion() { return Version.HTTP_2; }
+        }
+
+        private static class Http2TestContext
+                extends HttpTestContext implements HttpTestHandler {
+            private final HttpTestHandler handler;
+            private final String path;
+            private final List<HttpTestFilter> filters = new CopyOnWriteArrayList<>();
+            Http2TestContext(HttpTestHandler hdl, String path) {
+                this.handler = hdl;
+                this.path = path;
+            }
+            @Override
+            public String getPath() { return path; }
+            @Override
+            public void addFilter(HttpTestFilter filter) {
+                System.out.println("Http2TestContext::addFilter " + filter.description());
+                filters.add(filter);
+            }
+            @Override
+            public void handle(HttpTestExchange exchange) throws IOException {
+                System.out.println("Http2TestContext::handle " + exchange);
+                HttpChain.of(filters, handler).doFilter(exchange);
+            }
+            @Override
+            public void setAuthenticator(com.sun.net.httpserver.Authenticator authenticator) {
+                throw new UnsupportedOperationException("Can't set HTTP/1.1 authenticator on HTTP/2 context");
+            }
+            @Override public Version getVersion() { return Version.HTTP_2; }
+        }
+    }
+
+    public static void enableServerLogging() {
+        System.setProperty("java.util.logging.SimpleFormatter.format",
+                "%4$s [%1$tb %1$td, %1$tl:%1$tM:%1$tS.%1$tN] %2$s: %5$s%6$s%n");
+        HttpTestServer.ServerLogging.enableLogging();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/HttpsTunnelTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import javax.net.ssl.SSLContext;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static java.lang.String.format;
+import static java.lang.System.out;
+
+/**
+ * @test
+ * @summary This test verifies that if an h2 connection going through a
+ *          proxy P is downgraded to HTTP/1.1, then a new h2 request
+ *          going to a different host through the same proxy will not
+ *          be preemptively downgraded. That, is the stack should attempt
+ *          a new h2 connection to the new host.
+ * @bug 8196967
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters DigestEchoServer HttpsTunnelTest
+ * @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
+ *          java.base/sun.net
+ * @run main/othervm -Djdk.internal.httpclient.debug=true HttpsTunnelTest
+ */
+
+public class HttpsTunnelTest implements HttpServerAdapters {
+
+    static final String data[] = {
+        "Lorem ipsum",
+        "dolor sit amet",
+        "consectetur adipiscing elit, sed do eiusmod tempor",
+        "quis nostrud exercitation ullamco",
+        "laboris nisi",
+        "ut",
+        "aliquip ex ea commodo consequat." +
+        "Duis aute irure dolor in reprehenderit in voluptate velit esse" +
+        "cillum dolore eu fugiat nulla pariatur.",
+        "Excepteur sint occaecat cupidatat non proident."
+    };
+
+    static final SSLContext context;
+    static {
+        try {
+            context = new SimpleSSLContext().get();
+            SSLContext.setDefault(context);
+        } catch (Exception x) {
+            throw new ExceptionInInitializerError(x);
+        }
+    }
+
+    HttpsTunnelTest() {
+    }
+
+    public HttpClient newHttpClient(ProxySelector ps) {
+        HttpClient.Builder builder = HttpClient
+                .newBuilder()
+                .sslContext(context)
+                .proxy(ps);
+        return builder.build();
+    }
+
+    public static void main(String[] args) throws Exception {
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        HttpsServer server1 = HttpsServer.create(sa, 0);
+        server1.setHttpsConfigurator(new HttpsConfigurator(context));
+        HttpTestServer http1Server =
+                HttpTestServer.of(server1);
+        http1Server.addHandler(new HttpTestEchoHandler(), "/");
+        http1Server.start();
+        HttpTestServer http2Server = HttpTestServer.of(
+                new Http2TestServer("localhost", true, 0));
+        http2Server.addHandler(new HttpTestEchoHandler(), "/");
+        http2Server.start();
+
+        DigestEchoServer.TunnelingProxy proxy = DigestEchoServer.createHttpsProxyTunnel(
+                DigestEchoServer.HttpAuthSchemeType.NONE);
+
+        try {
+            URI uri1 = new URI("https://" + http1Server.serverAuthority() + "/foo/https1");
+            URI uri2 = new URI("https://" + http2Server.serverAuthority() + "/foo/https2");
+            ProxySelector ps = ProxySelector.of(proxy.getProxyAddress());
+                    //HttpClient.Builder.NO_PROXY;
+            HttpsTunnelTest test = new HttpsTunnelTest();
+            HttpClient client = test.newHttpClient(ps);
+            out.println("Proxy is: " + ps.select(uri2));
+
+            List<String> lines = List.of(Arrays.copyOfRange(data, 0, data.length));
+            assert lines.size() == data.length;
+            String body = lines.stream().collect(Collectors.joining("\r\n"));
+            HttpRequest.BodyPublisher reqBody = HttpRequest.BodyPublishers.ofString(body);
+            HttpRequest req1 = HttpRequest
+                    .newBuilder(uri1)
+                    .version(Version.HTTP_2)
+                    .POST(reqBody)
+                    .build();
+            out.println("\nPosting to HTTP/1.1 server at: " + req1);
+            HttpResponse<Stream<String>> response = client.send(req1, BodyHandlers.ofLines());
+            out.println("Checking response...");
+            if (response.statusCode() != 200) {
+                throw new RuntimeException("Unexpected status code: " + response);
+            }
+            if (response.version() != Version.HTTP_1_1) {
+                throw new RuntimeException("Unexpected protocol version: "
+                        + response.version());
+            }
+            List<String> respLines = response.body().collect(Collectors.toList());
+            if (!lines.equals(respLines)) {
+                throw new RuntimeException("Unexpected response 1: " + respLines);
+            }
+            HttpRequest.BodyPublisher reqBody2 = HttpRequest.BodyPublishers.ofString(body);
+            HttpRequest req2 = HttpRequest
+                    .newBuilder(uri2)
+                    .version(Version.HTTP_2)
+                    .POST(reqBody2)
+                    .build();
+            out.println("\nPosting to HTTP/2 server at: " + req2);
+            response = client.send(req2, BodyHandlers.ofLines());
+            out.println("Checking response...");
+            if (response.statusCode() != 200) {
+                throw new RuntimeException("Unexpected status code: " + response);
+            }
+            if (response.version() != Version.HTTP_2) {
+                throw new RuntimeException("Unexpected protocol version: "
+                        + response.version());
+            }
+            respLines = response.body().collect(Collectors.toList());
+            if (!lines.equals(respLines)) {
+                throw new RuntimeException("Unexpected response 2: " + respLines);
+            }
+        } catch(Throwable t) {
+            out.println("Unexpected exception: exiting: " + t);
+            t.printStackTrace();
+            throw t;
+        } finally {
+            proxy.stop();
+            http1Server.stop();
+            http2Server.stop();
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ImmutableFlowItems.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests response body subscribers's onNext's Lists are unmodifiable,
+ *          and that the buffers are read-only
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @modules java.base/sun.net.www.http
+ *          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
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Flow;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.net.http.HttpClient;
+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.HttpResponse.BodySubscribers;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+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 ImmutableFlowItems {
+
+    SSLContext sslContext;
+    HttpServer httpTestServer;         // HTTP/1.1    [ 4 servers ]
+    HttpsServer httpsTestServer;       // HTTPS/1.1
+    Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
+    Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][]{
+                { httpURI_fixed   },
+                { httpURI_chunk   },
+                { httpsURI_fixed  },
+                { httpsURI_chunk  },
+                { http2URI_fixed  },
+                { http2URI_chunk  },
+                { https2URI_fixed },
+                { https2URI_chunk },
+        };
+    }
+
+    static final String BODY = "You'll never plough a field by turning it over in your mind.";
+
+    HttpClient newHttpClient() {
+        return HttpClient.newBuilder()
+                .sslContext(sslContext)
+                .build();
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsString(String uri) throws Exception {
+        HttpClient client = newHttpClient();
+
+        HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                .build();
+
+        BodyHandler<String> handler = new CRSBodyHandler();
+        client.sendAsync(req, handler)
+                .thenApply(HttpResponse::body)
+                .thenAccept(body -> assertEquals(body, BODY))
+                .join();
+    }
+
+    static class CRSBodyHandler implements BodyHandler<String> {
+        @Override
+        public BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
+            return new CRSBodySubscriber();
+        }
+    }
+
+    static class CRSBodySubscriber implements BodySubscriber<String> {
+        private final BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            ofString.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            assertUnmodifiableList(item);
+            long c = item.stream().filter(ByteBuffer::isReadOnly).count();
+            assertEquals(c, item.size(), "Unexpected writable buffer in: " +item);
+            ofString.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            ofString.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            ofString.onComplete();
+        }
+
+        @Override
+        public CompletionStage<String> getBody() {
+            return ofString.getBody();
+        }
+    }
+
+    static final Class<UnsupportedOperationException> UOE = UnsupportedOperationException.class;
+
+    static void assertUnmodifiableList(List<ByteBuffer> list) {
+        assertNotNull(list);
+        ByteBuffer b = list.get(0);
+        assertNotNull(b);
+        assertThrows(UOE, () -> list.add(ByteBuffer.wrap(new byte[0])));
+        assertThrows(UOE, () -> list.remove(b));
+    }
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpHandler h1_fixedLengthHandler = new HTTP1_FixedLengthHandler();
+        HttpHandler h1_chunkHandler = new HTTP1_ChunkedHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpServer.create(sa, 0);
+        httpTestServer.createContext("/http1/fixed", h1_fixedLengthHandler);
+        httpTestServer.createContext("/http1/chunk", h1_chunkHandler);
+        httpURI_fixed = "http://" + serverAuthority(httpTestServer) + "/http1/fixed";
+        httpURI_chunk = "http://" + serverAuthority(httpTestServer) + "/http1/chunk";
+
+        httpsTestServer = HttpsServer.create(sa, 0);
+        httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer.createContext("/https1/fixed", h1_fixedLengthHandler);
+        httpsTestServer.createContext("/https1/chunk", h1_chunkHandler);
+        httpsURI_fixed = "https://" + serverAuthority(httpsTestServer) + "/https1/fixed";
+        httpsURI_chunk = "https://" + serverAuthority(httpsTestServer) + "/https1/chunk";
+
+        // HTTP/2
+        Http2Handler h2_fixedLengthHandler = new HTTP2_FixedLengthHandler();
+        Http2Handler h2_chunkedHandler = new HTTP2_VariableHandler();
+
+        http2TestServer = new Http2TestServer("localhost", false, 0);
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
+
+        https2TestServer = new Http2TestServer("localhost", true, 0);
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop(0);
+        httpsTestServer.stop(0);
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    static class HTTP1_FixedLengthHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange t) throws IOException {
+            out.println("HTTP1_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = BODY.getBytes(UTF_8);
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+
+    static class HTTP1_ChunkedHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange t) throws IOException {
+            out.println("HTTP1_ChunkedHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = BODY.getBytes(UTF_8);
+                t.sendResponseHeaders(200, 0);
+                os.write(bytes);
+            }
+        }
+    }
+
+    static class HTTP2_FixedLengthHandler implements Http2Handler {
+        @Override
+        public void handle(Http2TestExchange t) throws IOException {
+            out.println("HTTP2_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = BODY.getBytes(UTF_8);
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+
+    static class HTTP2_VariableHandler implements Http2Handler {
+        @Override
+        public void handle(Http2TestExchange t) throws IOException {
+            out.println("HTTP2_VariableHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = BODY.getBytes(UTF_8);
+                t.sendResponseHeaders(200, 0); // variable
+                os.write(bytes);
+            }
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/ImmutableHeaders.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ImmutableHeaders.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /**
  * @test
  * @bug 8087112
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          jdk.httpserver
  * @run main/othervm ImmutableHeaders
  * @summary ImmutableHeaders
@@ -37,21 +37,26 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
-import jdk.incubator.http.*;
+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.BodyHandlers;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.List;
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 
 public class ImmutableHeaders {
 
     final static String RESPONSE = "Hello world";
 
     public static void main(String[] args) throws Exception {
-        HttpServer server = HttpServer.create(new InetSocketAddress(0), 10);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        HttpServer server = HttpServer.create(addr, 10);
         ExecutorService serverExecutor = Executors.newCachedThreadPool();
         ExecutorService clientExecutor = Executors.newCachedThreadPool();
         server.createContext("/test", new ImmutableHeadersHandler());
@@ -65,7 +70,7 @@
                                       .build();
 
         try {
-            URI uri = new URI("http://127.0.0.1:" + port + "/test/foo");
+            URI uri = new URI("http://localhost:" + port + "/test/foo");
             HttpRequest req = HttpRequest.newBuilder(uri)
                                          .headers("X-Foo", "bar")
                                          .headers("X-Bar", "foo")
@@ -81,7 +86,7 @@
                 throw new RuntimeException("Test failed");
             } catch (UnsupportedOperationException ex) {
             }
-            HttpResponse resp = client.send(req, discard(null));
+            HttpResponse resp = client.send(req, BodyHandlers.discarding());
             try {
                 HttpHeaders hd = resp.headers();
                 List<String> v = hd.allValues("X-Foo-Response");
--- a/test/jdk/java/net/httpclient/InterruptedBlockingSend.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/InterruptedBlockingSend.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -21,16 +21,19 @@
  * questions.
  */
 
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse.BodyHandlers;
 import static java.lang.System.out;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 
 /**
  * @test
  * @summary Basic test for interrupted blocking send
+ * @run main/othervm InterruptedBlockingSend
  */
 
 public class InterruptedBlockingSend {
@@ -39,15 +42,17 @@
 
     public static void main(String[] args) throws Exception {
         HttpClient client = HttpClient.newHttpClient();
-        try (ServerSocket ss = new ServerSocket(0, 20)) {
+        try (ServerSocket ss = new ServerSocket()) {
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             int port = ss.getLocalPort();
-            URI uri = new URI("http://127.0.0.1:" + port + "/");
+            URI uri = new URI("http://localhost:" + port + "/");
 
             HttpRequest request = HttpRequest.newBuilder(uri).build();
 
             Thread t = new Thread(() -> {
                 try {
-                    client.send(request, discard(null));
+                    client.send(request, BodyHandlers.discarding());
                 } catch (InterruptedException e) {
                     throwable = e;
                 } catch (Throwable th) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/InvalidInputStreamSubscriptionRequest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,561 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests an asynchronous BodySubscriber that completes
+ *          immediately with an InputStream which issues bad
+ *          requests
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext ReferenceTracker
+ * @modules java.base/sun.net.www.http
+ *          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 InvalidInputStreamSubscriptionRequest
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+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.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+
+public class InvalidInputStreamSubscriptionRequest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final int ITERATION_COUNT = 3;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+    static volatile boolean tasksFailed;
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    System.out.printf(now() + "Task %s failed: %s%n", id, t);
+                    System.err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.println("Failed tasks: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+            });
+            if (tasksFailed) {
+                System.out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    interface BHS extends Supplier<BodyHandler<InputStream>> {
+        static BHS of(BHS impl, String name) {
+            return new BHSImpl(impl, name);
+        }
+    }
+
+    static final class BHSImpl implements BHS {
+        final BHS supplier;
+        final String name;
+        BHSImpl(BHS impl, String name) {
+            this.supplier = impl;
+            this.name = name;
+        }
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        @Override
+        public BodyHandler<InputStream> get() {
+            return supplier.get();
+        }
+    }
+
+    static final Supplier<BodyHandler<InputStream>> OF_INPUTSTREAM =
+            BHS.of(BodyHandlers::ofInputStream, "BodyHandlers::ofInputStream");
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][]{
+                { httpURI_fixed,    false, OF_INPUTSTREAM },
+                { httpURI_chunk,    false, OF_INPUTSTREAM },
+                { httpsURI_fixed,   false, OF_INPUTSTREAM },
+                { httpsURI_chunk,   false, OF_INPUTSTREAM },
+                { http2URI_fixed,   false, OF_INPUTSTREAM },
+                { http2URI_chunk,   false, OF_INPUTSTREAM },
+                { https2URI_fixed,  false, OF_INPUTSTREAM },
+                { https2URI_chunk,  false, OF_INPUTSTREAM },
+
+                { httpURI_fixed,    true, OF_INPUTSTREAM },
+                { httpURI_chunk,    true, OF_INPUTSTREAM },
+                { httpsURI_fixed,   true, OF_INPUTSTREAM },
+                { httpsURI_chunk,   true, OF_INPUTSTREAM },
+                { http2URI_fixed,   true, OF_INPUTSTREAM },
+                { http2URI_chunk,   true, OF_INPUTSTREAM },
+                { https2URI_fixed,  true, OF_INPUTSTREAM },
+                { https2URI_chunk,  true, OF_INPUTSTREAM },
+        };
+    }
+
+    final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+    HttpClient newHttpClient() {
+        return TRACKER.track(HttpClient.newBuilder()
+                .proxy(HttpClient.Builder.NO_PROXY)
+                .executor(executor)
+                .sslContext(sslContext)
+                .build());
+    }
+
+    @Test(dataProvider = "variants")
+    public void testNoBody(String uri, boolean sameClient, BHS handlers)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<InputStream> handler = handlers.get();
+            BodyHandler<InputStream> badHandler = (rspinfo) ->
+                    new BadBodySubscriber<>(handler.apply(rspinfo));
+            try {
+                HttpResponse<InputStream> response = client.send(req, badHandler);
+                try (InputStream is = response.body()) {
+                    String body = new String(is.readAllBytes(), UTF_8);
+                    assertEquals(body, "");
+                    if (uri.endsWith("/chunk")
+                            && response.version() == HttpClient.Version.HTTP_1_1) {
+                        // with /fixed and 0 length
+                        // there's no need for any call to request()
+                        throw new RuntimeException("Expected IAE not thrown");
+                    }
+                }
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IOException && cause.getCause() != null) {
+                    cause = cause.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testNoBodyAsync(String uri, boolean sameClient, BHS handlers)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<InputStream> handler = handlers.get();
+            BodyHandler<InputStream> badHandler = (rspinfo) ->
+                    new BadBodySubscriber<>(handler.apply(rspinfo));
+            CompletableFuture<HttpResponse<InputStream>> response =
+                    client.sendAsync(req, badHandler);
+            CompletableFuture<String> result = response.thenCompose(
+                            (responsePublisher) -> {
+                                try (InputStream is = responsePublisher.body()) {
+                                    return CompletableFuture.completedFuture(
+                                            new String(is.readAllBytes(), UTF_8));
+                                } catch (Exception x) {
+                                    return CompletableFuture.failedFuture(x);
+                                }
+                            });
+            try {
+                // Get the final result and compare it with the expected body
+                assertEquals(result.get(), "");
+                if (uri.endsWith("/chunk")
+                        && response.get().version() == HttpClient.Version.HTTP_1_1) {
+                    // with /fixed and 0 length
+                    // there's no need for any call to request()
+                    throw new RuntimeException("Expected IAE not thrown");
+                }
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IOException && cause.getCause() != null) {
+                    cause = cause.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsString(String uri, boolean sameClient, BHS handlers)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
+                    .build();
+            BodyHandler<InputStream> handler = handlers.get();
+            BodyHandler<InputStream> badHandler = (rspinfo) ->
+                    new BadBodySubscriber<>(handler.apply(rspinfo));
+            try {
+                HttpResponse<InputStream> response = client.send(req, badHandler);
+                try (InputStream is = response.body()) {
+                    String body = new String(is.readAllBytes(), UTF_8);
+                    assertEquals(body, WITH_BODY);
+                    throw new RuntimeException("Expected IAE not thrown");
+                }
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IOException && cause.getCause() != null) {
+                    cause = cause.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsStringAsync(String uri, boolean sameClient, BHS handlers)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
+                    .build();
+            BodyHandler<InputStream> handler = handlers.get();
+            BodyHandler<InputStream> badHandler = (rspinfo) ->
+                    new BadBodySubscriber<>(handler.apply(rspinfo));
+            CompletableFuture<String> result = client.sendAsync(req, badHandler)
+                    .thenCompose((responsePublisher) -> {
+                        try (InputStream is = responsePublisher.body()) {
+                            return CompletableFuture.completedFuture(
+                                    new String(is.readAllBytes(), UTF_8));
+                        } catch (Exception x) {
+                            return CompletableFuture.failedFuture(x);
+                        }
+                    });
+            // Get the final result and compare it with the expected body
+            try {
+                String body = result.get();
+                assertEquals(body, WITH_BODY);
+                throw new RuntimeException("Expected IAE not thrown");
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IOException && cause.getCause() != null) {
+                    cause = cause.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    static final class BadSubscription implements Flow.Subscription {
+        Flow.Subscription subscription;
+        Executor executor;
+        BadSubscription(Flow.Subscription subscription) {
+            this.subscription = subscription;
+        }
+
+        @Override
+        public void request(long n) {
+            if (executor == null) {
+                subscription.request(-n);
+            } else {
+                executor.execute(() -> subscription.request(-n));
+            }
+        }
+
+        @Override
+        public void cancel() {
+            subscription.cancel();
+        }
+    }
+
+    static final class BadBodySubscriber<T> implements BodySubscriber<T> {
+        final BodySubscriber<T> subscriber;
+        BadBodySubscriber(BodySubscriber<T> subscriber) {
+            this.subscriber = subscriber;
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            return subscriber.getBody();
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            System.out.println("Subscription is: " + subscription);
+            subscriber.onSubscribe(new BadSubscription(subscription));
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            subscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            subscriber.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            subscriber.onComplete();
+        }
+    }
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h1_chunkHandler = new HTTP_VariableLengthHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler( h1_fixedLengthHandler, "/http1/fixed");
+        httpTestServer.addHandler(h1_chunkHandler,"/http1/chunk");
+        httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed";
+        httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
+        httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
+        httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed";
+        httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk";
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_VariableLengthHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        AssertionError fail = TRACKER.check(500);
+        try {
+            httpTestServer.stop();
+            httpsTestServer.stop();
+            http2TestServer.stop();
+            https2TestServer.stop();
+        } finally {
+            if (fail != null) {
+                throw fail;
+            }
+        }
+    }
+
+    static final String WITH_BODY = "Lorem ipsum dolor sit amet, consectetur" +
+            " adipiscing elit, sed do eiusmod tempor incididunt ut labore et" +
+            " dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" +
+            " exercitation ullamco laboris nisi ut aliquip ex ea" +
+            " commodo consequat. Duis aute irure dolor in reprehenderit in " +
+            "voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
+            " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui" +
+            " officia deserunt mollit anim id est laborum.";
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            if (t.getRequestURI().getPath().endsWith("/withBody")) {
+                byte[] bytes = WITH_BODY.getBytes(UTF_8);
+                t.sendResponseHeaders(200, bytes.length);  // body
+                try (OutputStream os = t.getResponseBody()) {
+                    os.write(bytes);
+                }
+            } else {
+                t.sendResponseHeaders(200, 0);  //no body
+            }
+        }
+    }
+
+    static class HTTP_VariableLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_VariableLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, -1);  //chunked or variable
+            if (t.getRequestURI().getPath().endsWith("/withBody")) {
+                byte[] bytes = WITH_BODY.getBytes(UTF_8);
+                try (OutputStream os = t.getResponseBody()) {
+                    int chunkLen = bytes.length/10;
+                    if (chunkLen == 0) {
+                        os.write(bytes);
+                    } else {
+                        int count = 0;
+                        for (int i=0; i<10; i++) {
+                            os.write(bytes, count, chunkLen);
+                            os.flush();
+                            count += chunkLen;
+                        }
+                        os.write(bytes, count, bytes.length % chunkLen);
+                        count += bytes.length % chunkLen;
+                        assert count == bytes.length;
+                    }
+                }
+            } else {
+                t.getResponseBody().close();   // no body
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/InvalidSSLContextTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test to ensure the HTTP client throws an appropriate SSL exception
+ *          when SSL context is not valid.
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm -Djdk.internal.httpclient.debug=true InvalidSSLContextTest
+ */
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.Assert;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+import static java.net.http.HttpClient.Version.HTTP_2;
+
+
+public class InvalidSSLContextTest {
+
+    SSLContext sslContext;
+    volatile SSLServerSocket sslServerSocket;
+    volatile String uri;
+
+    @DataProvider(name = "versions")
+    public Object[][] versions() {
+        return new Object[][]{
+                { HTTP_1_1 },
+                { HTTP_2   }
+        };
+    }
+
+    @Test(dataProvider = "versions")
+    public void testSync(Version version) throws Exception {
+        // client-side uses a different context to that of the server-side
+        HttpClient client = HttpClient.newBuilder()
+                .sslContext(SSLContext.getDefault())
+                .build();
+
+        HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
+                .version(version)
+                .build();
+
+        try {
+            HttpResponse<?> response = client.send(request, BodyHandlers.discarding());
+            Assert.fail("UNEXPECTED response" + response);
+        } catch (SSLException sslex) {
+            System.out.println("Caught expected: " + sslex);
+        }
+    }
+
+    @Test(dataProvider = "versions")
+    public void testAsync(Version version) throws Exception {
+        // client-side uses a different context to that of the server-side
+        HttpClient client = HttpClient.newBuilder()
+                .sslContext(SSLContext.getDefault())
+                .build();
+
+        HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
+                .version(version)
+                .build();
+
+        assertExceptionally(SSLException.class,
+                            client.sendAsync(request, BodyHandlers.discarding()));
+    }
+
+    static void assertExceptionally(Class<? extends Throwable> clazz,
+                                    CompletableFuture<?> stage) {
+        stage.handle((result, error) -> {
+            if (result != null) {
+                Assert.fail("UNEXPECTED result: " + result);
+                return null;
+            }
+            if (error instanceof CompletionException) {
+                Throwable cause = error.getCause();
+                if (cause == null) {
+                    Assert.fail("Unexpected null cause: " + error);
+                }
+                assertException(clazz, cause);
+            } else {
+                assertException(clazz, error);
+            }
+            return null;
+        }).join();
+    }
+
+    static void assertException(Class<? extends Throwable> clazz, Throwable t) {
+        if (t == null) {
+            Assert.fail("Expected " + clazz + ", caught nothing");
+        }
+        if (!clazz.isInstance(t)) {
+            Assert.fail("Expected " + clazz + ", caught " + t);
+        }
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // server-side uses a different context to that of the client-side
+        sslServerSocket = (SSLServerSocket)sslContext
+                .getServerSocketFactory()
+                .createServerSocket();
+        sslServerSocket.setReuseAddress(false);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        sslServerSocket.bind(addr);
+        uri = "https://localhost:" + sslServerSocket.getLocalPort() + "/";
+
+        Thread t = new Thread("SSL-Server-Side") {
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        SSLSocket s = (SSLSocket) sslServerSocket.accept();
+                        System.out.println("SERVER: accepted: " + s);
+                        // artificially slow down the handshake reply to mimic
+                        // a slow(ish) network, and hopefully delay the
+                        // SequentialScheduler on in the client.
+                        Thread.sleep(500);
+                        s.startHandshake();
+                        s.close();
+                        Assert.fail("SERVER: UNEXPECTED ");
+                    } catch (SSLHandshakeException he) {
+                        System.out.println("SERVER: caught expected " + he);
+                    } catch (IOException e) {
+                        System.out.println("SERVER: caught: " + e);
+                        if (!sslServerSocket.isClosed()) {
+                            throw new UncheckedIOException(e);
+                        }
+                        break;
+                    } catch (InterruptedException ie) {
+                        throw new RuntimeException(ie);
+                    }
+                }
+            }
+        };
+        t.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        sslServerSocket.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/InvalidSubscriptionRequest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,493 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8201186
+ * @summary Tests an asynchronous BodySubscriber that completes
+ *          immediately with a Publisher<List<ByteBuffer>> whose
+ *          subscriber issues bad requests
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext ReferenceTracker
+ * @modules java.base/sun.net.www.http
+ *          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 InvalidSubscriptionRequest
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+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.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Publisher;
+import java.util.function.Supplier;
+
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+
+public class InvalidSubscriptionRequest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final int ITERATION_COUNT = 3;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = Executors.newCachedThreadPool();
+
+    interface BHS extends Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> {
+        static BHS of(BHS impl, String name) {
+            return new BHSImpl(impl, name);
+        }
+    }
+
+    static final class BHSImpl implements BHS {
+        final BHS supplier;
+        final String name;
+        BHSImpl(BHS impl, String name) {
+            this.supplier = impl;
+            this.name = name;
+        }
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        @Override
+        public BodyHandler<Publisher<List<ByteBuffer>>> get() {
+            return supplier.get();
+        }
+    }
+
+    static final Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> OF_PUBLISHER_API =
+            BHS.of(BodyHandlers::ofPublisher, "BodyHandlers::ofPublisher");
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][]{
+                { httpURI_fixed,    false, OF_PUBLISHER_API },
+                { httpURI_chunk,    false, OF_PUBLISHER_API },
+                { httpsURI_fixed,   false, OF_PUBLISHER_API },
+                { httpsURI_chunk,   false, OF_PUBLISHER_API },
+                { http2URI_fixed,   false, OF_PUBLISHER_API },
+                { http2URI_chunk,   false, OF_PUBLISHER_API },
+                { https2URI_fixed,  false, OF_PUBLISHER_API },
+                { https2URI_chunk,  false, OF_PUBLISHER_API },
+
+                { httpURI_fixed,    true, OF_PUBLISHER_API },
+                { httpURI_chunk,    true, OF_PUBLISHER_API },
+                { httpsURI_fixed,   true, OF_PUBLISHER_API },
+                { httpsURI_chunk,   true, OF_PUBLISHER_API },
+                { http2URI_fixed,   true, OF_PUBLISHER_API },
+                { http2URI_chunk,   true, OF_PUBLISHER_API },
+                { https2URI_fixed,  true, OF_PUBLISHER_API },
+                { https2URI_chunk,  true, OF_PUBLISHER_API },
+        };
+    }
+
+    final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+    HttpClient newHttpClient() {
+        return TRACKER.track(HttpClient.newBuilder()
+                         .proxy(HttpClient.Builder.NO_PROXY)
+                         .executor(executor)
+                         .sslContext(sslContext)
+                         .build());
+    }
+
+    @Test(dataProvider = "variants")
+    public void testNoBody(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler);
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString = new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8));
+            // get the Publisher<List<ByteBuffer>> and
+            // subscribe to it.
+            response.body().subscribe(ofString);
+            // Get the final result and compare it with the expected body
+            try {
+                String body = ofString.getBody().toCompletableFuture().get();
+                assertEquals(body, "");
+                if (uri.endsWith("/chunk")
+                        && response.version() == HttpClient.Version.HTTP_1_1) {
+                    // with /fixed and 0 length
+                    // there's no need for any call to request()
+                    throw new RuntimeException("Expected IAE not thrown");
+                }
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testNoBodyAsync(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString =
+                    new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8));
+            CompletableFuture<HttpResponse<Publisher<List<ByteBuffer>>>> response =
+                    client.sendAsync(req, handler);
+            CompletableFuture<String> result = response.thenCompose(
+                            (responsePublisher) -> {
+                                // get the Publisher<List<ByteBuffer>> and
+                                // subscribe to it.
+                                responsePublisher.body().subscribe(ofString);
+                                return ofString.getBody();
+                            });
+            try {
+                // Get the final result and compare it with the expected body
+                assertEquals(result.get(), "");
+                if (uri.endsWith("/chunk")
+                        && response.get().version() == HttpClient.Version.HTTP_1_1) {
+                    // with /fixed and 0 length
+                    // there's no need for any call to request()
+                    throw new RuntimeException("Expected IAE not thrown");
+                }
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsString(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler);
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString = new BadBodySubscriber<>(
+                    BodySubscribers.ofString(UTF_8));
+            // get the Publisher<List<ByteBuffer>> and
+            // subscribe to it.
+            response.body().subscribe(ofString);
+            // Get the final result and compare it with the expected body
+            try {
+                String body = ofString.getBody().toCompletableFuture().get();
+                assertEquals(body, WITH_BODY);
+                throw new RuntimeException("Expected IAE not thrown");
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsStringAsync(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString =
+                    new BadBodySubscriber<>(BodySubscribers.ofString(UTF_8));
+            CompletableFuture<String> result = client.sendAsync(req, handler)
+                    .thenCompose((responsePublisher) -> {
+                        // get the Publisher<List<ByteBuffer>> and
+                        // subscribe to it.
+                        responsePublisher.body().subscribe(ofString);
+                        return ofString.getBody();
+                    });
+            // Get the final result and compare it with the expected body
+            try {
+                String body = result.get();
+                assertEquals(body, WITH_BODY);
+                throw new RuntimeException("Expected IAE not thrown");
+            } catch (Exception x) {
+                Throwable cause = x;
+                if (x instanceof CompletionException || x instanceof ExecutionException) {
+                    cause = x.getCause();
+                }
+                if (cause instanceof IllegalArgumentException) {
+                    System.out.println("Got expected exception: " + cause);
+                } else throw x;
+            }
+        }
+    }
+
+    static final class BadSubscription implements Flow.Subscription {
+        Flow.Subscription subscription;
+        Executor executor;
+        BadSubscription(Flow.Subscription subscription) {
+            this.subscription = subscription;
+        }
+
+        @Override
+        public void request(long n) {
+            if (executor == null) {
+                subscription.request(-n);
+            } else {
+                executor.execute(() -> subscription.request(-n));
+            }
+        }
+
+        @Override
+        public void cancel() {
+            subscription.cancel();
+        }
+    }
+
+    static final class BadBodySubscriber<T> implements BodySubscriber<T> {
+        final BodySubscriber<T> subscriber;
+        BadBodySubscriber(BodySubscriber<T> subscriber) {
+            this.subscriber = subscriber;
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            return subscriber.getBody();
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            System.out.println("Subscription is: " + subscription);
+            subscriber.onSubscribe(new BadSubscription(subscription));
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            subscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            subscriber.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            subscriber.onComplete();
+        }
+    }
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h1_chunkHandler = new HTTP_VariableLengthHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler( h1_fixedLengthHandler, "/http1/fixed");
+        httpTestServer.addHandler(h1_chunkHandler,"/http1/chunk");
+        httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed";
+        httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
+        httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
+        httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed";
+        httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk";
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_VariableLengthHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        AssertionError fail = TRACKER.check(500);
+        try {
+            httpTestServer.stop();
+            httpsTestServer.stop();
+            http2TestServer.stop();
+            https2TestServer.stop();
+        } finally {
+            if (fail != null) {
+                throw fail;
+            }
+        }
+    }
+
+    static final String WITH_BODY = "Lorem ipsum dolor sit amet, consectetur" +
+            " adipiscing elit, sed do eiusmod tempor incididunt ut labore et" +
+            " dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" +
+            " exercitation ullamco laboris nisi ut aliquip ex ea" +
+            " commodo consequat. Duis aute irure dolor in reprehenderit in " +
+            "voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
+            " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui" +
+            " officia deserunt mollit anim id est laborum.";
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            if (t.getRequestURI().getPath().endsWith("/withBody")) {
+                byte[] bytes = WITH_BODY.getBytes(UTF_8);
+                t.sendResponseHeaders(200, bytes.length);  // body
+                try (OutputStream os = t.getResponseBody()) {
+                    os.write(bytes);
+                }
+            } else {
+                t.sendResponseHeaders(200, 0);  //no body
+            }
+        }
+    }
+
+    static class HTTP_VariableLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_VariableLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, -1);  //chunked or variable
+            if (t.getRequestURI().getPath().endsWith("/withBody")) {
+                byte[] bytes = WITH_BODY.getBytes(UTF_8);
+                try (OutputStream os = t.getResponseBody()) {
+                    int chunkLen = bytes.length/10;
+                    if (chunkLen == 0) {
+                        os.write(bytes);
+                    } else {
+                        int count = 0;
+                        for (int i=0; i<10; i++) {
+                            os.write(bytes, count, chunkLen);
+                            os.flush();
+                            count += chunkLen;
+                        }
+                        os.write(bytes, count, bytes.length % chunkLen);
+                        count += bytes.length % chunkLen;
+                        assert count == bytes.length;
+                    }
+                }
+            } else {
+                t.getResponseBody().close();   // no body
+            }
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/LightWeightHttpServer.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/LightWeightHttpServer.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -38,6 +38,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.nio.file.Path;
 import java.util.HashSet;
@@ -81,7 +82,7 @@
         logger.addHandler(ch);
 
         String root = System.getProperty("test.src", ".") + "/docs";
-        InetSocketAddress addr = new InetSocketAddress(0);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         httpServer = HttpServer.create(addr, 0);
         if (httpServer instanceof HttpsServer) {
             throw new RuntimeException("should not be httpsserver");
@@ -118,8 +119,8 @@
         System.out.println("HTTP server port = " + port);
         httpsport = httpsServer.getAddress().getPort();
         System.out.println("HTTPS server port = " + httpsport);
-        httproot = "http://127.0.0.1:" + port + "/";
-        httpsroot = "https://127.0.0.1:" + httpsport + "/";
+        httproot = "http://localhost:" + port + "/";
+        httpsroot = "https://localhost:" + httpsport + "/";
 
         proxy = new ProxyServer(0, false);
         proxyPort = proxy.getPort();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/LineAdaptersCompileOnly.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+import java.util.concurrent.Flow;
+import java.util.function.Function;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import static java.util.function.Function.identity;
+import static java.nio.charset.StandardCharsets.*;
+
+/*
+ * @test
+ * @summary Basic test for Flow adapters with generic type parameters
+ * @compile LineAdaptersCompileOnly.java
+ */
+
+public class LineAdaptersCompileOnly {
+
+    public static void main(String[] args) {
+        makesSureDifferentGenericSignaturesCompile();
+    }
+
+    static void makesSureDifferentGenericSignaturesCompile() {
+        BodyHandlers.fromLineSubscriber(new StringSubscriber());
+        BodyHandlers.fromLineSubscriber(new CharSequenceSubscriber());
+        BodyHandlers.fromLineSubscriber(new ObjectSubscriber());
+
+        BodySubscribers.fromLineSubscriber(new StringSubscriber());
+        BodySubscribers.fromLineSubscriber(new CharSequenceSubscriber());
+        BodySubscribers.fromLineSubscriber(new ObjectSubscriber());
+
+        BodyHandlers.fromLineSubscriber(new StringSubscriber(),       identity(), "\n");
+        BodyHandlers.fromLineSubscriber(new CharSequenceSubscriber(), identity(), "\r\n");
+        BodyHandlers.fromLineSubscriber(new ObjectSubscriber(),       identity(), "\n");
+        BodyHandlers.fromLineSubscriber(new StringSubscriber(),       identity(), null);
+        BodyHandlers.fromLineSubscriber(new CharSequenceSubscriber(), identity(), null);
+        BodyHandlers.fromLineSubscriber(new ObjectSubscriber(),       identity(), null);
+
+        BodySubscribers.fromLineSubscriber(new StringSubscriber(),       identity(), UTF_8,    "\n");
+        BodySubscribers.fromLineSubscriber(new CharSequenceSubscriber(), identity(), UTF_16,   "\r\n");
+        BodySubscribers.fromLineSubscriber(new ObjectSubscriber(),       identity(), US_ASCII, "\n");
+        BodySubscribers.fromLineSubscriber(new StringSubscriber(),       identity(), UTF_8,    null);
+        BodySubscribers.fromLineSubscriber(new CharSequenceSubscriber(), identity(), UTF_16,   null);
+        BodySubscribers.fromLineSubscriber(new ObjectSubscriber(),       identity(), US_ASCII, null);
+    }
+
+    static class StringSubscriber implements Flow.Subscriber<String> {
+        @Override public void onSubscribe(Flow.Subscription subscription) { }
+        @Override public void onNext(String item) { }
+        @Override public void onError(Throwable throwable) { }
+        @Override public void onComplete() { }
+    }
+
+    static class CharSequenceSubscriber implements Flow.Subscriber<CharSequence> {
+        @Override public void onSubscribe(Flow.Subscription subscription) { }
+        @Override public void onNext(CharSequence item) { }
+        @Override public void onError(Throwable throwable) { }
+        @Override public void onComplete() { }
+    }
+
+    static class ObjectSubscriber implements Flow.Subscriber<Object> {
+        @Override public void onSubscribe(Flow.Subscription subscription) { }
+        @Override public void onNext(Object item) { }
+        @Override public void onError(Throwable throwable) { }
+        @Override public void onComplete() { }
+    }
+
+    // ---
+
+    static final Function<StringSubscriber,Integer> f1 = subscriber -> 1;
+    static final Function<StringSubscriber,Number> f2 = subscriber -> 2;
+    static final Function<StringSubscriberX,Integer> f3 = subscriber -> 3;
+    static final Function<StringSubscriberX,Number> f4 = subscriber -> 4;
+
+    static class StringSubscriberX extends StringSubscriber {
+        int getIntegerX() { return 5; }
+    }
+
+    static void makesSureDifferentGenericFunctionSignaturesCompile() {
+        BodyHandler<Integer> bh01 = BodyHandlers.fromLineSubscriber(new StringSubscriber(), s -> 6, "\n");
+        BodyHandler<Number>  bh02 = BodyHandlers.fromLineSubscriber(new StringSubscriber(), s -> 7, "\n");
+        BodyHandler<Integer> bh03 = BodyHandlers.fromLineSubscriber(new StringSubscriber(), f1, "\n");
+        BodyHandler<Number>  bh04 = BodyHandlers.fromLineSubscriber(new StringSubscriber(), f1, "\n");
+        BodyHandler<Number>  bh05 = BodyHandlers.fromLineSubscriber(new StringSubscriber(), f2, "\n");
+        BodyHandler<Integer> bh06 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), f1, "\n");
+        BodyHandler<Number>  bh07 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), f1, "\n");
+        BodyHandler<Number>  bh08 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), f2, "\n");
+        BodyHandler<Integer> bh09 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), StringSubscriberX::getIntegerX, "\n");
+        BodyHandler<Number>  bh10 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), StringSubscriberX::getIntegerX, "\n");
+        BodyHandler<Integer> bh11 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), f3, "\n");
+        BodyHandler<Number>  bh12 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), f3, "\n");
+        BodyHandler<Number>  bh13 = BodyHandlers.fromLineSubscriber(new StringSubscriberX(), f4, "\n");
+
+        BodySubscriber<Integer> bs01 = BodySubscribers.fromLineSubscriber(new StringSubscriber(), s -> 6, UTF_8, "\n");
+        BodySubscriber<Number>  bs02 = BodySubscribers.fromLineSubscriber(new StringSubscriber(), s -> 7, UTF_8, "\n");
+        BodySubscriber<Integer> bs03 = BodySubscribers.fromLineSubscriber(new StringSubscriber(), f1, UTF_8, "\n");
+        BodySubscriber<Number>  bs04 = BodySubscribers.fromLineSubscriber(new StringSubscriber(), f1, UTF_8, "\n");
+        BodySubscriber<Number>  bs05 = BodySubscribers.fromLineSubscriber(new StringSubscriber(), f2, UTF_8, "\n");
+        BodySubscriber<Integer> bs06 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), f1, UTF_8, "\n");
+        BodySubscriber<Number>  bs07 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), f1, UTF_8, "\n");
+        BodySubscriber<Number>  bs08 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), f2, UTF_8, "\n");
+        BodySubscriber<Integer> bs09 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), StringSubscriberX::getIntegerX, UTF_8, "\n");
+        BodySubscriber<Number>  bs10 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), StringSubscriberX::getIntegerX, UTF_8, "\n");
+        BodySubscriber<Integer> bs11 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), f3, UTF_8, "\n");
+        BodySubscriber<Number>  bs12 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), f3, UTF_8, "\n");
+        BodySubscriber<Number>  bs13 = BodySubscribers.fromLineSubscriber(new StringSubscriberX(), f4, UTF_8, "\n");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/LineBodyHandlerTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,697 @@
+/*
+ * 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Flow;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLContext;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static java.nio.charset.StandardCharsets.UTF_16;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.net.http.HttpRequest.BodyPublishers.ofString;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+
+/*
+ * @test
+ * @summary Basic tests for line adapter subscribers as created by
+ *          the BodyHandlers returned by BodyHandler::fromLineSubscriber
+ *          and BodyHandler::asLines
+ * @modules java.base/sun.net.www.http
+ *          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
+ * @build Http2TestServer LineBodyHandlerTest HttpServerAdapters
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm LineBodyHandlerTest
+ */
+
+public class LineBodyHandlerTest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    @DataProvider(name = "uris")
+    public Object[][] variants() {
+        return new Object[][]{
+                { httpURI   },
+                { httpsURI  },
+                { http2URI  },
+                { https2URI },
+        };
+    }
+
+    static final Class<NullPointerException> NPE = NullPointerException.class;
+    static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
+
+    @Test
+    public void testNull() {
+        assertThrows(NPE, () -> BodyHandlers.fromLineSubscriber(null));
+        assertNotNull(BodyHandlers.fromLineSubscriber(new StringSubscriber()));
+        assertThrows(NPE, () -> BodyHandlers.fromLineSubscriber(null, Function.identity(), "\n"));
+        assertThrows(NPE, () -> BodyHandlers.fromLineSubscriber(new StringSubscriber(), null, "\n"));
+        assertNotNull(BodyHandlers.fromLineSubscriber(new StringSubscriber(), Function.identity(), null));
+        assertThrows(NPE, () -> BodyHandlers.fromLineSubscriber(null, null, "\n"));
+        assertThrows(NPE, () -> BodyHandlers.fromLineSubscriber(null, Function.identity(), null));
+        assertThrows(NPE, () -> BodyHandlers.fromLineSubscriber(new StringSubscriber(), null, null));
+
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, Function.identity(),
+                Charset.defaultCharset(), System.lineSeparator()));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(new StringSubscriber(), null,
+                Charset.defaultCharset(), System.lineSeparator()));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(new StringSubscriber(), Function.identity(),
+                null, System.lineSeparator()));
+        assertNotNull(BodySubscribers.fromLineSubscriber(new StringSubscriber(), Function.identity(),
+                Charset.defaultCharset(), null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, null,
+                Charset.defaultCharset(), System.lineSeparator()));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, Function.identity(),
+                null, System.lineSeparator()));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, Function.identity(),
+                Charset.defaultCharset(), null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(new StringSubscriber(), null,
+                null, System.lineSeparator()));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(new StringSubscriber(), null,
+                Charset.defaultCharset(), null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(new StringSubscriber(), Function.identity(),
+                null, null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(new StringSubscriber(), null, null, null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, Function.identity(),
+                null, null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, null,
+                Charset.defaultCharset(), null));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, null,
+                null, System.lineSeparator()));
+        assertThrows(NPE, () -> BodySubscribers.fromLineSubscriber(null, null, null, null));
+    }
+
+    @Test
+    public void testIAE() {
+        assertThrows(IAE, () -> BodyHandlers.fromLineSubscriber(new StringSubscriber(), Function.identity(),""));
+        assertThrows(IAE, () -> BodyHandlers.fromLineSubscriber(new CharSequenceSubscriber(), Function.identity(),""));
+        assertThrows(IAE, () -> BodyHandlers.fromLineSubscriber(new ObjectSubscriber(), Function.identity(), ""));
+        assertThrows(IAE, () -> BodySubscribers.fromLineSubscriber(new StringSubscriber(), Function.identity(),
+                    StandardCharsets.UTF_8, ""));
+        assertThrows(IAE, () -> BodySubscribers.fromLineSubscriber(new CharSequenceSubscriber(), Function.identity(),
+                    StandardCharsets.UTF_16, ""));
+        assertThrows(IAE, () -> BodySubscribers.fromLineSubscriber(new ObjectSubscriber(), Function.identity(),
+                    StandardCharsets.US_ASCII, ""));
+    }
+
+    private static final List<String> lines(String text, String eol) {
+        if (eol == null) {
+            return new BufferedReader(new StringReader(text)).lines().collect(Collectors.toList());
+        } else {
+            String replaced = text.replace(eol, "|");
+            int i=0;
+            while(replaced.endsWith("||")) {
+                replaced = replaced.substring(0,replaced.length()-1);
+                i++;
+            }
+            List<String> res = List.of(replaced.split("\\|"));
+            if (i > 0) {
+                res = new ArrayList<>(res);
+                for (int j=0; j<i; j++) res.add("");
+            }
+            return res;
+        }
+    }
+
+    @Test(dataProvider = "uris")
+    void testStringWithFinisher(String url) {
+        String body = "May the luck of the Irish be with you!";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext)
+                .build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        StringSubscriber subscriber = new StringSubscriber();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandlers.fromLineSubscriber(
+                        subscriber, Supplier::get, "\n"));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
+        String text = response.body();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, body);
+        assertEquals(subscriber.list, lines(body, "\n"));
+    }
+
+    @Test(dataProvider = "uris")
+    void testAsStream(String url) {
+        String body = "May the luck of the Irish be with you!";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext)
+                .build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandlers.ofLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
+        Stream<String> stream = response.body();
+        List<String> list = stream.collect(Collectors.toList());
+        String text = list.stream().collect(Collectors.joining("|"));
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, body);
+        assertEquals(list, List.of(body));
+        assertEquals(list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testStringWithFinisher2(String url) {
+        String body = "May the luck\r\n\r\n of the Irish be with you!";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        StringSubscriber subscriber = new StringSubscriber();
+        CompletableFuture<HttpResponse<Void>> cf
+                = client.sendAsync(request,
+                                   BodyHandlers.fromLineSubscriber(subscriber));
+        assertNoObtrusion(cf);
+        HttpResponse<Void> response = cf.join();
+        String text = subscriber.get();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, body.replace("\r\n", "\n"));
+        assertEquals(subscriber.list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testAsStreamWithCRLF(String url) {
+        String body = "May the luck\r\n\r\n of the Irish be with you!";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandlers.ofLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
+        Stream<String> stream = response.body();
+        List<String> list = stream.collect(Collectors.toList());
+        String text = list.stream().collect(Collectors.joining("|"));
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, "May the luck|| of the Irish be with you!");
+        assertEquals(list, List.of("May the luck",
+                                   "",
+                                   " of the Irish be with you!"));
+        assertEquals(list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testStringWithFinisherBlocking(String url) throws Exception {
+        String body = "May the luck of the Irish be with you!";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body)).build();
+
+        StringSubscriber subscriber = new StringSubscriber();
+        HttpResponse<String> response = client.send(request,
+                BodyHandlers.fromLineSubscriber(subscriber, Supplier::get, "\n"));
+        String text = response.body();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, "May the luck of the Irish be with you!");
+        assertEquals(subscriber.list, lines(body, "\n"));
+    }
+
+    @Test(dataProvider = "uris")
+    void testStringWithoutFinisherBlocking(String url) throws Exception {
+        String body = "May the luck of the Irish be with you!";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body)).build();
+
+        StringSubscriber subscriber = new StringSubscriber();
+        HttpResponse<Void> response = client.send(request,
+                BodyHandlers.fromLineSubscriber(subscriber));
+        String text = subscriber.get();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, "May the luck of the Irish be with you!");
+        assertEquals(subscriber.list, lines(body, null));
+    }
+
+    // Subscriber<Object>
+
+    @Test(dataProvider = "uris")
+    void testAsStreamWithMixedCRLF(String url) {
+        String body = "May\r\n the wind\r\n always be\rat your back.\r\r";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandlers.ofLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
+        Stream<String> stream = response.body();
+        List<String> list = stream.collect(Collectors.toList());
+        String text = list.stream().collect(Collectors.joining("|"));
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May| the wind| always be|at your back.|");
+        assertEquals(list, List.of("May",
+                                   " the wind",
+                                   " always be",
+                                   "at your back.",
+                                   ""));
+        assertEquals(list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testAsStreamWithMixedCRLF_UTF8(String url) {
+        String body = "May\r\n the wind\r\n always be\rat your back.\r\r";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .header("Content-type", "text/text; charset=UTF-8")
+                .POST(BodyPublishers.ofString(body, UTF_8)).build();
+
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandlers.ofLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
+        Stream<String> stream = response.body();
+        List<String> list = stream.collect(Collectors.toList());
+        String text = list.stream().collect(Collectors.joining("|"));
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May| the wind| always be|at your back.|");
+        assertEquals(list, List.of("May",
+                                   " the wind",
+                                   " always be",
+                                   "at your back.", ""));
+        assertEquals(list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testAsStreamWithMixedCRLF_UTF16(String url) {
+        String body = "May\r\n the wind\r\n always be\rat your back.\r\r";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .header("Content-type", "text/text; charset=UTF-16")
+                .POST(BodyPublishers.ofString(body, UTF_16)).build();
+
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandlers.ofLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
+        Stream<String> stream = response.body();
+        List<String> list = stream.collect(Collectors.toList());
+        String text = list.stream().collect(Collectors.joining("|"));
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May| the wind| always be|at your back.|");
+        assertEquals(list, List.of("May",
+                                   " the wind",
+                                   " always be",
+                                   "at your back.",
+                                   ""));
+        assertEquals(list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testObjectWithFinisher(String url) {
+        String body = "May\r\n the wind\r\n always be\rat your back.";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandlers.fromLineSubscriber(
+                        subscriber, ObjectSubscriber::get, "\r\n"));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
+        String text = response.body();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May\n the wind\n always be\rat your back.");
+        assertEquals(subscriber.list, List.of("May",
+                                              " the wind",
+                                              " always be\rat your back."));
+        assertEquals(subscriber.list, lines(body, "\r\n"));
+    }
+
+    @Test(dataProvider = "uris")
+    void testObjectWithFinisher_UTF16(String url) {
+        String body = "May\r\n the wind\r\n always be\rat your back.\r\r";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .header("Content-type", "text/text; charset=UTF-16")
+                .POST(BodyPublishers.ofString(body, UTF_16)).build();
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandlers.fromLineSubscriber(
+                        subscriber, ObjectSubscriber::get, null));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
+        String text = response.body();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May\n the wind\n always be\nat your back.\n");
+        assertEquals(subscriber.list, List.of("May",
+                                              " the wind",
+                                              " always be",
+                                              "at your back.",
+                                              ""));
+        assertEquals(subscriber.list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testObjectWithoutFinisher(String url) {
+        String body = "May\r\n the wind\r\n always be\rat your back.";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext)
+                .build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        CompletableFuture<HttpResponse<Void>> cf
+                = client.sendAsync(request,
+                                   BodyHandlers.fromLineSubscriber(subscriber));
+        assertNoObtrusion(cf);
+        HttpResponse<Void> response = cf.join();
+        String text = subscriber.get();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May\n the wind\n always be\nat your back.");
+        assertEquals(subscriber.list, List.of("May",
+                                              " the wind",
+                                              " always be",
+                                              "at your back."));
+        assertEquals(subscriber.list, lines(body, null));
+    }
+
+    @Test(dataProvider = "uris")
+    void testObjectWithFinisherBlocking(String url) throws Exception {
+        String body = "May\r\n the wind\r\n always be\nat your back.";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext)
+                .build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        HttpResponse<String> response = client.send(request,
+                BodyHandlers.fromLineSubscriber(subscriber,
+                                               ObjectSubscriber::get,
+                                   "\r\n"));
+        String text = response.body();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May\n the wind\n always be\nat your back.");
+        assertEquals(subscriber.list, List.of("May",
+                                              " the wind",
+                                              " always be\nat your back."));
+        assertEquals(subscriber.list, lines(body, "\r\n"));
+    }
+
+    @Test(dataProvider = "uris")
+    void testObjectWithoutFinisherBlocking(String url) throws Exception {
+        String body = "May\r\n the wind\r\n always be\nat your back.";
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext)
+                .build();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(body))
+                .build();
+
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        HttpResponse<Void> response = client.send(request,
+                BodyHandlers.fromLineSubscriber(subscriber));
+        String text = subscriber.get();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertTrue(text.length() != 0);  // what else can be asserted!
+        assertEquals(text, "May\n the wind\n always be\nat your back.");
+        assertEquals(subscriber.list, List.of("May",
+                                              " the wind",
+                                              " always be",
+                                              "at your back."));
+        assertEquals(subscriber.list, lines(body, null));
+    }
+
+    static private final String LINE = "Bient\u00f4t nous plongerons dans les" +
+            " fr\u00f4\ud801\udc00des t\u00e9n\u00e8bres, ";
+
+    static private final String bigtext() {
+        StringBuilder res = new StringBuilder((LINE.length() + 1) * 50);
+        for (int i = 0; i<50; i++) {
+            res.append(LINE);
+            if (i%2 == 0) res.append("\r\n");
+        }
+        return res.toString();
+    }
+
+    @Test(dataProvider = "uris")
+    void testBigTextFromLineSubscriber(String url) {
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext)
+                .build();
+        String bigtext = bigtext();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(bigtext))
+                .build();
+
+        StringSubscriber subscriber = new StringSubscriber();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandlers.fromLineSubscriber(
+                        subscriber, Supplier::get, "\r\n"));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
+        String text = response.body();
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, bigtext.replace("\r\n", "\n"));
+        assertEquals(subscriber.list, lines(bigtext, "\r\n"));
+    }
+
+    @Test(dataProvider = "uris")
+    void testBigTextAsStream(String url) {
+        HttpClient client = HttpClient.newBuilder().sslContext(sslContext)
+                .build();
+        String bigtext = bigtext();
+        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
+                .POST(BodyPublishers.ofString(bigtext))
+                .build();
+
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandlers.ofLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
+        Stream<String> stream = response.body();
+        List<String> list = stream.collect(Collectors.toList());
+        String text = list.stream().collect(Collectors.joining("|"));
+        System.out.println(text);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(text, bigtext.replace("\r\n", "|"));
+        assertEquals(list, List.of(bigtext.split("\r\n")));
+        assertEquals(list, lines(bigtext, null));
+    }
+
+    /** An abstract Subscriber that converts all received data into a String. */
+    static abstract class AbstractSubscriber implements Supplier<String> {
+        protected volatile Flow.Subscription subscription;
+        protected final StringBuilder baos = new StringBuilder();
+        protected volatile String text;
+        protected volatile RuntimeException error;
+        protected final List<Object> list = new CopyOnWriteArrayList<>();
+
+        public void onSubscribe(Flow.Subscription subscription) {
+            this.subscription = subscription;
+            subscription.request(Long.MAX_VALUE);
+        }
+        public void onError(Throwable throwable) {
+            System.out.println(this + " onError: " + throwable);
+            error = new RuntimeException(throwable);
+        }
+        public void onComplete() {
+            System.out.println(this + " onComplete");
+            text = baos.toString();
+        }
+        @Override public String get() {
+            if (error != null) throw error;
+            return text;
+        }
+    }
+
+    static class StringSubscriber extends AbstractSubscriber
+            implements Flow.Subscriber<String>, Supplier<String>
+    {
+        @Override public void onNext(String item) {
+            System.out.print(this + " onNext: \"" + item + "\"");
+            if (baos.length() != 0) baos.append('\n');
+            baos.append(item);
+            list.add(item);
+        }
+    }
+
+    static class CharSequenceSubscriber extends AbstractSubscriber
+            implements Flow.Subscriber<CharSequence>, Supplier<String>
+    {
+        @Override public void onNext(CharSequence item) {
+            System.out.print(this + " onNext: " + item);
+            if (baos.length() != 0) baos.append('\n');
+            baos.append(item);
+            list.add(item);
+        }
+    }
+
+    static class ObjectSubscriber extends AbstractSubscriber
+            implements Flow.Subscriber<Object>, Supplier<String>
+    {
+        @Override public void onNext(Object item) {
+            System.out.print(this + " onNext: " + item);
+            if (baos.length() != 0) baos.append('\n');
+            baos.append(item);
+            list.add(item);
+        }
+    }
+
+
+    static void uncheckedWrite(ByteArrayOutputStream baos, byte[] ba) {
+        try {
+            baos.write(ba);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(new HttpTestEchoHandler(), "/http1/echo");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/echo";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(new HttpTestEchoHandler(),"/https1/echo");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/echo";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(new HttpTestEchoHandler(), "/http2/echo");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/echo";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(new HttpTestEchoHandler(), "/https2/echo");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/echo";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    static void printBytes(PrintStream out, String prefix, byte[] bytes) {
+        int padding = 4 + 4 - (bytes.length % 4);
+        padding = padding > 4 ? padding - 4 : 4;
+        byte[] bigbytes = new byte[bytes.length + padding];
+        System.arraycopy(bytes, 0, bigbytes, padding, bytes.length);
+        out.println(prefix + bytes.length + " "
+                    + new BigInteger(bigbytes).toString(16));
+    }
+
+    private static void assertNoObtrusion(CompletableFuture<?> cf) {
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeException(new RuntimeException()));
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeValue(null));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/LineStreamsAndSurrogatesTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,311 @@
+/*
+ * 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.MalformedInputException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.SubmissionPublisher;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.testng.annotations.Test;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.charset.StandardCharsets.UTF_16;
+import static org.testng.Assert.assertEquals;
+
+/*
+ * @test
+ * @summary tests for BodySubscribers returned by asLines.
+ *       In particular tests that surrogate characters are handled
+ *       correctly.
+ * @modules java.net.http java.logging
+ * @run testng/othervm LineStreamsAndSurrogatesTest
+ */
+
+public class LineStreamsAndSurrogatesTest {
+
+
+    static final Class<NullPointerException> NPE = NullPointerException.class;
+
+    private static final List<String> lines(String text) {
+        return new BufferedReader(new StringReader(text)).lines().collect(Collectors.toList());
+    }
+
+    @Test
+    void testUncomplete() throws Exception {
+        // Uses U+10400 which is encoded as the surrogate pair U+D801 U+DC00
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r les\n\n" +
+                " fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres\ud801\udc00";
+        Charset charset = UTF_8;
+
+        BodySubscriber<Stream<String>> bodySubscriber = BodySubscribers.ofLines(charset);
+        AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        Runnable run = () -> {
+            try {
+                SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+                byte[] sbytes = text.getBytes(charset);
+                byte[] bytes = Arrays.copyOfRange(sbytes, 0, sbytes.length - 1);
+                publisher.subscribe(bodySubscriber);
+                System.out.println("Publishing " + bytes.length + " bytes");
+                for (int i = 0; i < bytes.length; i++) {
+                    // ensure that surrogates are split over several buffers.
+                    publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+                }
+                publisher.close();
+            } catch(Throwable t) {
+                errorRef.set(t);
+            }
+        };
+        Thread thread = new Thread(run,"Publishing");
+        thread.start();
+        try {
+            Stream<String> stream = bodySubscriber.getBody().toCompletableFuture().get();
+            List<String> list = stream.collect(Collectors.toList());
+            String resp = list.stream().collect(Collectors.joining(""));
+            System.out.println("***** Got: " + resp);
+
+            byte[] sbytes = text.getBytes(UTF_8);
+            byte[] bytes = Arrays.copyOfRange(sbytes, 0, sbytes.length - 1);
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+            BufferedReader reader = new BufferedReader(new InputStreamReader(bais, charset));
+            String resp2 = reader.lines().collect(Collectors.joining(""));
+            System.out.println("***** Got2: " + resp2);
+
+            assertEquals(resp, resp2);
+            assertEquals(list, List.of("Bient\u00f4t",
+                                       " nous plongerons",
+                                       " dans",
+                                       " les",
+                                       "",
+                                       " fr\u00f4\ud801\udc00des",
+                                       " t\u00e9n\u00e8bres\ufffd"));
+        } catch (ExecutionException x) {
+            Throwable cause = x.getCause();
+            if (cause instanceof MalformedInputException) {
+                throw new RuntimeException("Unexpected MalformedInputException", cause);
+            }
+            throw x;
+        }
+        if (errorRef.get() != null) {
+            throw new RuntimeException("Unexpected exception", errorRef.get());
+        }
+    }
+
+    @Test
+    void testStream1() throws Exception {
+        // Uses U+10400 which is encoded as the surrogate pair U+D801 U+DC00
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r\r les\n\n" +
+                " fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres";
+        Charset charset = UTF_8;
+
+        BodySubscriber<Stream<String>> bodySubscriber = BodySubscribers.ofLines(charset);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(charset);
+        AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        Runnable run = () -> {
+            try {
+                publisher.subscribe(bodySubscriber);
+                System.out.println("Publishing " + bytes.length + " bytes");
+                for (int i = 0; i < bytes.length; i++) {
+                    // ensure that surrogates are split over several buffers.
+                    publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+                }
+                publisher.close();
+            } catch(Throwable t) {
+                errorRef.set(t);
+            }
+        };
+
+        Stream<String> stream = bodySubscriber.getBody().toCompletableFuture().get();
+        Thread thread = new Thread(run,"Publishing");
+        thread.start();
+        List<String> list = stream.collect(Collectors.toList());
+        String resp = list.stream().collect(Collectors.joining("|"));
+        System.out.println("***** Got: " + resp);
+        assertEquals(resp, text.replace("\r\n", "|")
+                               .replace("\n","|")
+                               .replace("\r","|"));
+        assertEquals(list, List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans",
+                "",
+                " les",
+                "",
+                " fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres"));
+        assertEquals(list, lines(text));
+        if (errorRef.get() != null) {
+            throw new RuntimeException("Unexpected exception", errorRef.get());
+        }
+    }
+
+
+    @Test
+    void testStream2() throws Exception {
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r\r" +
+                " les fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres\r\r";
+        Charset charset = UTF_8;
+
+        BodySubscriber<Stream<String>> bodySubscriber = BodySubscribers.ofLines(charset);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(charset);
+        AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        Runnable run = () -> {
+            try {
+                publisher.subscribe(bodySubscriber);
+                System.out.println("Publishing " + bytes.length + " bytes");
+                for (int i = 0; i < bytes.length; i++) {
+                    // ensure that surrogates are split over several buffers.
+                    publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+                }
+                publisher.close();
+            } catch(Throwable t) {
+                errorRef.set(t);
+            }
+        };
+
+        Stream<String> stream = bodySubscriber.getBody().toCompletableFuture().get();
+        Thread thread = new Thread(run,"Publishing");
+        thread.start();
+        List<String> list = stream.collect(Collectors.toList());
+        String resp = list.stream().collect(Collectors.joining(""));
+        System.out.println("***** Got: " + resp);
+        String expected = Stream.of(text.split("\r\n|\r|\n"))
+                .collect(Collectors.joining(""));
+        assertEquals(resp, expected);
+        assertEquals(list, List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans",
+                "",
+                " les fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres",
+                ""));
+        assertEquals(list, lines(text));
+        if (errorRef.get() != null) {
+            throw new RuntimeException("Unexpected exception", errorRef.get());
+        }
+    }
+
+    @Test
+    void testStream3_UTF16() throws Exception {
+        // Uses U+10400 which is encoded as the surrogate pair U+D801 U+DC00
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r\r" +
+                " les\n\n fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres";
+        Charset charset = UTF_16;
+
+        BodySubscriber<Stream<String>> bodySubscriber = BodySubscribers.ofLines(charset);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(charset);
+        AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        Runnable run = () -> {
+            try {
+                publisher.subscribe(bodySubscriber);
+                System.out.println("Publishing " + bytes.length + " bytes");
+                for (int i = 0; i < bytes.length; i++) {
+                    // ensure that surrogates are split over several buffers.
+                    publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+                }
+                publisher.close();
+            } catch(Throwable t) {
+                errorRef.set(t);
+            }
+        };
+
+        Stream<String> stream = bodySubscriber.getBody().toCompletableFuture().get();
+        Thread thread = new Thread(run,"Publishing");
+        thread.start();
+        List<String> list = stream.collect(Collectors.toList());
+        String resp = list.stream().collect(Collectors.joining(""));
+        System.out.println("***** Got: " + resp);
+        assertEquals(resp, text.replace("\n","").replace("\r",""));
+        assertEquals(list, List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans",
+                "",
+                " les",
+                "",
+                " fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres"));
+        assertEquals(list, lines(text));
+        if (errorRef.get() != null) {
+            throw new RuntimeException("Unexpected exception", errorRef.get());
+        }
+    }
+
+
+    @Test
+    void testStream4_UTF16() throws Exception {
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r\r" +
+                " les fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres\r\r";
+        Charset charset = UTF_16;
+
+        BodySubscriber<Stream<String>> bodySubscriber = BodySubscribers.ofLines(charset);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(charset);
+        AtomicReference<Throwable> errorRef = new AtomicReference<>();
+        Runnable run = () -> {
+            try {
+                publisher.subscribe(bodySubscriber);
+                System.out.println("Publishing " + bytes.length + " bytes");
+                for (int i = 0; i < bytes.length; i++) {
+                    // ensure that surrogates are split over several buffers.
+                    publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+                }
+                publisher.close();
+            } catch(Throwable t) {
+                errorRef.set(t);
+            }
+        };
+
+        Stream<String> stream = bodySubscriber.getBody().toCompletableFuture().get();
+        Thread thread = new Thread(run,"Publishing");
+        thread.start();
+        List<String> list = stream.collect(Collectors.toList());
+        String resp = list.stream().collect(Collectors.joining(""));
+        System.out.println("***** Got: " + resp);
+        String expected = Stream.of(text.split("\r\n|\r|\n"))
+                .collect(Collectors.joining(""));
+        assertEquals(resp, expected);
+        assertEquals(list, List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans",
+                "",
+                " les fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres",
+                ""));
+        assertEquals(list, lines(text));
+        if (errorRef.get() != null) {
+            throw new RuntimeException("Unexpected exception", errorRef.get());
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/LineSubscribersAndSurrogatesTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,379 @@
+/*
+ * 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.nio.charset.MalformedInputException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Flow;
+import java.util.concurrent.SubmissionPublisher;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.testng.annotations.Test;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.charset.StandardCharsets.UTF_16;
+import static org.testng.Assert.assertEquals;
+
+/*
+ * @test
+ * @summary tests for BodySubscribers returned by fromLineSubscriber.
+ *       In particular tests that surrogate characters are handled
+ *       correctly.
+ * @modules java.net.http java.logging
+ * @run testng/othervm LineSubscribersAndSurrogatesTest
+ */
+
+public class LineSubscribersAndSurrogatesTest {
+
+
+    static final Class<NullPointerException> NPE = NullPointerException.class;
+
+    private static final List<String> lines(String text, String eol) {
+        if (eol == null) {
+            return new BufferedReader(new StringReader(text)).lines().collect(Collectors.toList());
+        } else {
+            String replaced = text.replace(eol, "|");
+            int i=0;
+            while(replaced.endsWith("||")) {
+                replaced = replaced.substring(0,replaced.length()-1);
+                i++;
+            }
+            List<String> res = List.of(replaced.split("\\|"));
+            if (i > 0) {
+                res = new ArrayList<>(res);
+                for (int j=0; j<i; j++) res.add("");
+            }
+            return res;
+        }
+    }
+
+    @Test
+    void testIncomplete() throws Exception {
+        // Uses U+10400 which is encoded as the surrogate pair U+D801 U+DC00
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r" +
+                " les\n\n fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres\ud801\udc00";
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        BodySubscriber<String> bodySubscriber = BodySubscribers.fromLineSubscriber(
+                subscriber, Supplier::get, UTF_8, null);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] sbytes = text.getBytes(UTF_8);
+        byte[] bytes = Arrays.copyOfRange(sbytes,0, sbytes.length - 1);
+        publisher.subscribe(bodySubscriber);
+        System.out.println("Publishing " + bytes.length + " bytes");
+        for (int i=0; i<bytes.length; i++) {
+            // ensure that surrogates are split over several buffers.
+            publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+        }
+        publisher.close();
+        try {
+            String resp = bodySubscriber.getBody().toCompletableFuture().get();
+            System.out.println("***** Got: " + resp);
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+            BufferedReader reader = new BufferedReader(new InputStreamReader(bais, UTF_8));
+            String resp2 = reader.lines().collect(Collectors.joining(""));
+            assertEquals(resp, resp2);
+            assertEquals(subscriber.list, List.of("Bient\u00f4t",
+                    " nous plongerons",
+                    " dans",
+                    " les",
+                    "",
+                    " fr\u00f4\ud801\udc00des",
+                    " t\u00e9n\u00e8bres\ufffd"));
+        } catch (ExecutionException x) {
+            Throwable cause = x.getCause();
+            if (cause instanceof MalformedInputException) {
+                throw new RuntimeException("Unexpected MalformedInputException thrown", cause);
+            }
+            throw x;
+        }
+    }
+
+
+    @Test
+    void testStringWithFinisherLF() throws Exception {
+        // Uses U+10400 which is encoded as the surrogate pair U+D801 U+DC00
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r" +
+                " les\n\n fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres\r";
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        BodySubscriber<String> bodySubscriber = BodySubscribers.fromLineSubscriber(
+                subscriber, Supplier::get, UTF_8, "\n");
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(UTF_8);
+        publisher.subscribe(bodySubscriber);
+        System.out.println("Publishing " + bytes.length + " bytes");
+        for (int i=0; i<bytes.length; i++) {
+            // ensure that surrogates are split over several buffers.
+            publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+        }
+        publisher.close();
+        String resp = bodySubscriber.getBody().toCompletableFuture().get();
+        System.out.println("***** Got: " + resp);
+        List<String> expected = List.of("Bient\u00f4t\r",
+                " nous plongerons\r",
+                " dans\r les",
+                "",
+                " fr\u00f4\ud801\udc00des\r",
+                " t\u00e9n\u00e8bres\r");
+        assertEquals(subscriber.list, expected);
+        assertEquals(resp, Stream.of(text.split("\n")).collect(Collectors.joining("")));
+        assertEquals(resp, expected.stream().collect(Collectors.joining("")));
+        assertEquals(subscriber.list, lines(text, "\n"));
+    }
+
+
+    @Test
+    void testStringWithFinisherCR() throws Exception {
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r" +
+                " les fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres\r\r";
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        BodySubscriber<String> bodySubscriber = BodySubscribers.fromLineSubscriber(
+                subscriber, Supplier::get, UTF_8, "\r");
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(UTF_8);
+        publisher.subscribe(bodySubscriber);
+        System.out.println("Publishing " + bytes.length + " bytes");
+        for (int i=0; i<bytes.length; i++) {
+            // ensure that surrogates are split over several buffers.
+            publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+        }
+        publisher.close();
+        String resp = bodySubscriber.getBody().toCompletableFuture().get();
+        System.out.println("***** Got: " + resp);
+        assertEquals(resp, text.replace("\r", ""));
+        assertEquals(subscriber.list, List.of("Bient\u00f4t",
+                "\n nous plongerons",
+                "\n dans",
+                " les fr\u00f4\ud801\udc00des",
+                "\n t\u00e9n\u00e8bres",
+                ""));
+        assertEquals(subscriber.list, lines(text, "\r"));
+    }
+
+    @Test
+    void testStringWithFinisherCRLF() throws Exception {
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r" +
+                " les fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres";
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        BodySubscriber<String> bodySubscriber = BodySubscribers.fromLineSubscriber(
+                subscriber, Supplier::get, UTF_8, "\r\n");
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(UTF_8);
+        publisher.subscribe(bodySubscriber);
+        System.out.println("Publishing " + bytes.length + " bytes");
+        for (int i=0; i<bytes.length; i++) {
+            // ensure that surrogates are split over several buffers.
+            publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+        }
+        publisher.close();
+        String resp = bodySubscriber.getBody().toCompletableFuture().get();
+        System.out.println("***** Got: " + resp);
+        assertEquals(resp, text.replace("\r\n",""));
+        assertEquals(subscriber.list, List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans\r les fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres"));
+        assertEquals(subscriber.list, lines(text, "\r\n"));
+    }
+
+
+    @Test
+    void testStringWithFinisherBR() throws Exception {
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r" +
+                " les\r\r fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres";
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        BodySubscriber<String> bodySubscriber = BodySubscribers.fromLineSubscriber(
+                subscriber, Supplier::get, UTF_8, null);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(UTF_8);
+        publisher.subscribe(bodySubscriber);
+        System.out.println("Publishing " + bytes.length + " bytes");
+        for (int i=0; i<bytes.length; i++) {
+            // ensure that surrogates are split over several buffers.
+            publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+        }
+        publisher.close();
+        String resp = bodySubscriber.getBody().toCompletableFuture().get();
+        System.out.println("***** Got: " + resp);
+        List<String> expected = List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans",
+                " les",
+                "",
+                " fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres");
+        assertEquals(subscriber.list, expected);
+        assertEquals(resp, expected.stream().collect(Collectors.joining("")));
+        assertEquals(subscriber.list, lines(text, null));
+    }
+
+    @Test
+    void testStringWithFinisherBR_UTF_16() throws Exception {
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r" +
+                " les\r\r fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres\r\r";
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        BodySubscriber<String> bodySubscriber = BodySubscribers.fromLineSubscriber(
+                subscriber, Supplier::get, UTF_16, null);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(UTF_16);
+        publisher.subscribe(bodySubscriber);
+        System.out.println("Publishing " + bytes.length + " bytes");
+        for (int i=0; i<bytes.length; i++) {
+            // ensure that surrogates are split over several buffers.
+            publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+        }
+        publisher.close();
+        String resp = bodySubscriber.getBody().toCompletableFuture().get();
+        System.out.println("***** Got: " + resp);
+        List<String> expected = List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans",
+                " les",
+                "",
+                " fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres",
+                "");
+        assertEquals(resp, expected.stream().collect(Collectors.joining("")));
+        assertEquals(subscriber.list, expected);
+        assertEquals(subscriber.list, lines(text, null));
+    }
+
+    void testStringWithoutFinisherBR() throws Exception {
+        String text = "Bient\u00f4t\r\n nous plongerons\r\n dans\r" +
+                " les\r\r fr\u00f4\ud801\udc00des\r\n t\u00e9n\u00e8bres";
+        ObjectSubscriber subscriber = new ObjectSubscriber();
+        BodySubscriber<Void> bodySubscriber = BodySubscribers.fromLineSubscriber(subscriber);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        byte[] bytes = text.getBytes(UTF_8);
+        publisher.subscribe(bodySubscriber);
+        System.out.println("Publishing " + bytes.length + " bytes");
+        for (int i = 0; i < bytes.length; i++) {
+            // ensure that surrogates are split over several buffers.
+            publisher.submit(List.of(ByteBuffer.wrap(bytes, i, 1)));
+        }
+        publisher.close();
+        Void resp = bodySubscriber.getBody().toCompletableFuture().get();
+        System.out.println("***** Got: " + resp);
+        List<String> expected = List.of("Bient\u00f4t",
+                " nous plongerons",
+                " dans",
+                " les",
+                "",
+                " fr\u00f4\ud801\udc00des",
+                " t\u00e9n\u00e8bres");
+        assertEquals(subscriber.text, expected.stream().collect(Collectors.joining("")));
+        assertEquals(subscriber.list, expected);
+        assertEquals(subscriber.list, lines(text, null));
+    }
+
+
+    /** An abstract Subscriber that converts all received data into a String. */
+    static abstract class AbstractSubscriber implements Supplier<String> {
+        protected final List<Object> list = new CopyOnWriteArrayList<>();
+        protected volatile Flow.Subscription subscription;
+        protected final StringBuilder baos = new StringBuilder();
+        protected volatile String text;
+        protected volatile RuntimeException error;
+
+        public void onSubscribe(Flow.Subscription subscription) {
+            this.subscription = subscription;
+            subscription.request(Long.MAX_VALUE);
+        }
+        public void onError(Throwable throwable) {
+            System.out.println(this + " onError: " + throwable);
+            error = new RuntimeException(throwable);
+        }
+        public void onComplete() {
+            System.out.println(this + " onComplete");
+            text = baos.toString();
+        }
+        @Override public String get() {
+            if (error != null) throw error;
+            return text;
+        }
+        public final List<?> list() {
+            return list;
+        }
+    }
+
+    static class StringSubscriber extends AbstractSubscriber
+            implements Flow.Subscriber<String>, Supplier<String>
+    {
+        @Override public void onNext(String item) {
+            System.out.println(this + " onNext: \""
+                    + item.replace("\n","\\n")
+                          .replace("\r", "\\r")
+                    + "\"");
+            baos.append(item);
+            list.add(item);
+        }
+    }
+
+    static class CharSequenceSubscriber extends AbstractSubscriber
+            implements Flow.Subscriber<CharSequence>, Supplier<String>
+    {
+        @Override public void onNext(CharSequence item) {
+            System.out.println(this + " onNext: \""
+                    + item.toString().replace("\n","\\n")
+                    .replace("\r", "\\r")
+                    + "\"");
+            baos.append(item);
+            list.add(item);
+        }
+    }
+
+    static class ObjectSubscriber extends AbstractSubscriber
+            implements Flow.Subscriber<Object>, Supplier<String>
+    {
+        @Override public void onNext(Object item) {
+            System.out.println(this + " onNext: \""
+                    + item.toString().replace("\n","\\n")
+                    .replace("\r", "\\r")
+                    + "\"");
+            baos.append(item);
+            list.add(item);
+        }
+    }
+
+
+    static void uncheckedWrite(ByteArrayOutputStream baos, byte[] ba) {
+        try {
+            baos.write(ba);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+}
--- a/test/jdk/java/net/httpclient/ManyRequests.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ManyRequests.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8087112 8180044
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary/ /
@@ -47,10 +47,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.util.Arrays;
 import java.util.Formatter;
 import java.util.HashMap;
@@ -61,8 +64,6 @@
 import java.util.concurrent.CompletableFuture;
 import javax.net.ssl.SSLContext;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromByteArray;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asByteArray;
 
 public class ManyRequests {
 
@@ -78,7 +79,7 @@
                          + ", XFixed=" + XFIXED);
         SSLContext ctx = new SimpleSSLContext().get();
 
-        InetSocketAddress addr = new InetSocketAddress(0);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         HttpsServer server = HttpsServer.create(addr, 0);
         server.setHttpsConfigurator(new Configurator(ctx));
 
@@ -129,7 +130,7 @@
 
     static void test(HttpsServer server, HttpClient client) throws Exception {
         int port = server.getAddress().getPort();
-        URI baseURI = new URI("https://127.0.0.1:" + port + "/foo/x");
+        URI baseURI = new URI("https://localhost:" + port + "/foo/x");
         server.createContext("/foo", new TestEchoHandler());
         server.start();
 
@@ -144,7 +145,7 @@
             URI uri = new URI(baseURI.toString() + String.valueOf(i+1));
             HttpRequest r = HttpRequest.newBuilder(uri)
                                        .header("XFixed", "true")
-                                       .POST(fromByteArray(buf))
+                                       .POST(BodyPublishers.ofByteArray(buf))
                                        .build();
             bodies.put(r, buf);
 
@@ -152,7 +153,7 @@
                 limiter.whenOkToSend()
                        .thenCompose((v) -> {
                            System.out.println("Client: sendAsync: " + r.uri());
-                           return client.sendAsync(r, asByteArray());
+                           return client.sendAsync(r, BodyHandlers.ofByteArray());
                        })
                        .thenCompose((resp) -> {
                            limiter.requestComplete();
--- a/test/jdk/java/net/httpclient/ManyRequests2.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ManyRequests2.java	Tue Apr 17 08:54:17 2018 -0700
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8087112 8180044
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary/ /
--- a/test/jdk/java/net/httpclient/ManyRequestsLegacy.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ManyRequestsLegacy.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary/ /
@@ -48,32 +48,29 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.HttpURLConnection;
+import java.net.InetAddress;
 import java.net.URI;
 import java.net.URLConnection;
-import java.security.NoSuchAlgorithmException;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSession;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpClient.Version;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+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.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
 import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.Formatter;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Random;
-import java.util.concurrent.ExecutorService;
 import java.util.logging.Logger;
 import java.util.logging.Level;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromByteArray;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asByteArray;
 
 public class ManyRequestsLegacy {
 
@@ -94,7 +91,7 @@
                     return true;
                 }
             });
-        InetSocketAddress addr = new InetSocketAddress(0);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         HttpsServer server = HttpsServer.create(addr, 0);
         server.setHttpsConfigurator(new Configurator(ctx));
 
@@ -116,7 +113,7 @@
     static final boolean XFIXED = Boolean.getBoolean("test.XFixed");
 
     static class LegacyHttpClient {
-        static final class LegacyHttpResponse extends HttpResponse<byte[]> {
+        static final class LegacyHttpResponse implements HttpResponse<byte[]> {
             final HttpRequest request;
             final byte[] response;
             final int statusCode;
@@ -139,12 +136,8 @@
             @Override
             public byte[] body() {return response;}
             @Override
-            public SSLParameters sslParameters() {
-                try {
-                    return SSLContext.getDefault().getDefaultSSLParameters();
-                } catch (NoSuchAlgorithmException ex) {
-                    throw new UnsupportedOperationException(ex);
-                }
+            public Optional<SSLSession> sslSession() {
+                return Optional.empty(); // for now
             }
             @Override
             public URI uri() { return request.uri();}
@@ -225,7 +218,7 @@
 
     static void test(HttpsServer server, LegacyHttpClient client) throws Exception {
         int port = server.getAddress().getPort();
-        URI baseURI = new URI("https://127.0.0.1:" + port + "/foo/x");
+        URI baseURI = new URI("https://localhost:" + port + "/foo/x");
         server.createContext("/foo", new TestEchoHandler());
         server.start();
 
@@ -240,7 +233,7 @@
             URI uri = new URI(baseURI.toString() + String.valueOf(i+1));
             HttpRequest r = HttpRequest.newBuilder(uri)
                                        .header("XFixed", "true")
-                                       .POST(fromByteArray(buf))
+                                       .POST(BodyPublishers.ofByteArray(buf))
                                        .build();
             bodies.put(r, buf);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/MappingResponseSubscriber.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,348 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests mapped response subscriber
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @modules java.base/sun.net.www.http
+ *          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
+ *      MappingResponseSubscriber
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.net.http.HttpClient;
+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.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscribers;
+import  java.net.http.HttpResponse.BodySubscriber;
+import java.util.function.Function;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class MappingResponseSubscriber {
+
+    SSLContext sslContext;
+    HttpServer httpTestServer;         // HTTP/1.1    [ 4 servers ]
+    HttpsServer httpsTestServer;       // HTTPS/1.1
+    Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
+    Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final int ITERATION_COUNT = 10;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = Executors.newCachedThreadPool();
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][]{
+                { httpURI_fixed,    false },
+                { httpURI_chunk,    false },
+                { httpsURI_fixed,   false },
+                { httpsURI_chunk,   false },
+                { http2URI_fixed,   false },
+                { http2URI_chunk,   false },
+                { https2URI_fixed,  false,},
+                { https2URI_chunk,  false },
+
+                { httpURI_fixed,    true },
+                { httpURI_chunk,    true },
+                { httpsURI_fixed,   true },
+                { httpsURI_chunk,   true },
+                { http2URI_fixed,   true },
+                { http2URI_chunk,   true },
+                { https2URI_fixed,  true,},
+                { https2URI_chunk,  true },
+        };
+    }
+
+    HttpClient newHttpClient() {
+        return HttpClient.newBuilder()
+                         .executor(executor)
+                         .sslContext(sslContext)
+                         .build();
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsBytes(String uri, boolean sameClient) throws Exception {
+        HttpClient client = null;
+        for (int i = 0; i < ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                                         .build();
+            BodyHandler<byte[]> handler = new CRSBodyHandler();
+            HttpResponse<byte[]> response = client.send(req, handler);
+            byte[] body = response.body();
+            assertEquals(body, bytes);
+        }
+    }
+
+    static class CRSBodyHandler implements BodyHandler<byte[]> {
+        @Override
+        public BodySubscriber<byte[]> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
+            return BodySubscribers.mapping(
+                new CRSBodySubscriber(), (s) -> s.getBytes(UTF_8)
+            );
+        }
+    }
+
+    static class CRSBodySubscriber implements BodySubscriber<String> {
+        private final BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
+        volatile boolean onSubscribeCalled;
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            //out.println("onSubscribe ");
+            onSubscribeCalled = true;
+            ofString.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+           // out.println("onNext " + item);
+            assertTrue(onSubscribeCalled);
+            ofString.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            //out.println("onError");
+            assertTrue(onSubscribeCalled);
+            ofString.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            //out.println("onComplete");
+            assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
+            ofString.onComplete();
+        }
+
+        @Override
+        public CompletionStage<String> getBody() {
+            return ofString.getBody();
+        }
+    }
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpHandler h1_fixedLengthHandler = new HTTP1_FixedLengthHandler();
+        HttpHandler h1_chunkHandler = new HTTP1_ChunkedHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpServer.create(sa, 0);
+        httpTestServer.createContext("/http1/fixed", h1_fixedLengthHandler);
+        httpTestServer.createContext("/http1/chunk", h1_chunkHandler);
+        httpURI_fixed = "http://" + serverAuthority(httpTestServer) + "/http1/fixed";
+        httpURI_chunk = "http://" + serverAuthority(httpTestServer) + "/http1/chunk";
+
+        httpsTestServer = HttpsServer.create(sa, 0);
+        httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer.createContext("/https1/fixed", h1_fixedLengthHandler);
+        httpsTestServer.createContext("/https1/chunk", h1_chunkHandler);
+        httpsURI_fixed = "https://" + serverAuthority(httpsTestServer) + "/https1/fixed";
+        httpsURI_chunk = "https://" + serverAuthority(httpsTestServer) + "/https1/chunk";
+
+        // HTTP/2
+        Http2Handler h2_fixedLengthHandler = new HTTP2_FixedLengthHandler();
+        Http2Handler h2_chunkedHandler = new HTTP2_VariableHandler();
+
+        http2TestServer = new Http2TestServer("localhost", false, 0);
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
+
+        https2TestServer = new Http2TestServer("localhost", true, 0);
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop(0);
+        httpsTestServer.stop(0);
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    static byte[] bytes;
+
+    static {
+        bytes = new byte[128 * 1024];
+        int b = 'A';
+
+        for (int i=0; i< bytes.length; i++) {
+            bytes[i] = (byte)b;
+            b = b == 'Z'? 'A' : b + 1;
+        }
+    }
+
+    static class HTTP1_FixedLengthHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange t) throws IOException {
+            out.println("HTTP1_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, bytes.length);  //no body
+            OutputStream os = t.getResponseBody();
+            os.write(bytes);
+            os.close();
+        }
+    }
+
+    static class HTTP1_ChunkedHandler implements HttpHandler {
+        @Override
+        public void handle(HttpExchange t) throws IOException {
+            out.println("HTTP1_ChunkedHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, 0); // chunked
+            OutputStream os = t.getResponseBody();
+            os.write(bytes);
+            os.close();
+        }
+    }
+
+    static class HTTP2_FixedLengthHandler implements Http2Handler {
+        @Override
+        public void handle(Http2TestExchange t) throws IOException {
+            out.println("HTTP2_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, 0); // chunked
+            OutputStream os = t.getResponseBody();
+            os.write(bytes);
+            os.close();
+        }
+    }
+
+    static class HTTP2_VariableHandler implements Http2Handler {
+        @Override
+        public void handle(Http2TestExchange t) throws IOException {
+            out.println("HTTP2_VariableHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, bytes.length);  //no body
+            OutputStream os = t.getResponseBody();
+            os.write(bytes);
+            os.close();
+        }
+    }
+
+    // -- Compile only. Verifies generic signatures
+
+    static final Function<CharSequence,Integer> f1 = subscriber -> 1;
+    static final Function<CharSequence,Number> f2 = subscriber -> 2;
+    static final Function<String,Integer> f3 = subscriber -> 3;
+    static final Function<String,Number> f4 = subscriber -> 4;
+
+    public void compileOnly() throws Exception {
+        HttpClient client = null;
+        HttpRequest req = null;
+
+        HttpResponse<Integer> r1 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> 1));
+        HttpResponse<Number>  r2 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> 1));
+        HttpResponse<String>  r3 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> "s"));
+        HttpResponse<CharSequence> r4 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), s -> "s"));
+
+        HttpResponse<Integer> x1 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f1));
+        HttpResponse<Number>  x2 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f1));
+        HttpResponse<Number>  x3 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f2));
+        HttpResponse<Integer> x4 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f3));
+        HttpResponse<Number>  x5 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f3));
+        HttpResponse<Number>  x7 = client.send(req, (ri) ->
+                BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), f4));
+    }
+}
--- a/test/jdk/java/net/httpclient/MessageHeadersTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/MessageHeadersTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /**
  * @test
  * @bug 8164704
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          jdk.httpserver
  *          java.base/sun.net.www
  * @run main MessageHeadersTest
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/MethodsTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+import jdk.internal.net.http.common.HttpHeadersImpl;
+
+import java.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.URI;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Optional;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+
+
+/**
+ * @test
+ * @bug 8199135
+ * @modules java.net.http/jdk.internal.net.http.common
+ * @summary Basic test for method names
+ */
+public class MethodsTest {
+
+    static final URI TEST_URI = URI.create("http://www.foo.com/");
+    static final String FORBIDDEN = "()<>@,;:\\\"/[]?={} \t\r\n";
+    static final HttpClient client = HttpClient.newBuilder().proxy(NO_PROXY).build();
+
+    static void bad(String name) throws IOException, InterruptedException {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.method(name, HttpRequest.BodyPublishers.noBody());
+            throw new RuntimeException("Expected IAE for method:" + name);
+        } catch (IllegalArgumentException expected) {
+            System.out.println("Got expected IAE: " + expected);
+        }
+        try {
+            HttpRequest req = new HttpRequest() {
+                @Override public Optional<BodyPublisher> bodyPublisher() {
+                    return Optional.of(BodyPublishers.noBody());
+                }
+                @Override public String method() {
+                    return name;
+                }
+                @Override public Optional<Duration> timeout() {
+                    return Optional.empty();
+                }
+                @Override public boolean expectContinue() {
+                    return false;
+                }
+                @Override public URI uri() {
+                    return TEST_URI;
+                }
+                @Override public Optional<HttpClient.Version> version() {
+                    return Optional.empty();
+                }
+                @Override public HttpHeaders headers() {
+                    return new HttpHeadersImpl();
+                }
+            };
+            client.send(req, HttpResponse.BodyHandlers.ofString());
+            throw new RuntimeException("Expected IAE for method:" + name);
+        } catch (IllegalArgumentException expected) {
+            System.out.println("Got expected IAE: " + expected);
+        }
+    }
+
+    static void good(String name) {
+        HttpRequest.Builder builder = HttpRequest.newBuilder(TEST_URI);
+        try {
+            builder.method(name, HttpRequest.BodyPublishers.noBody());
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException("Unexpected IAE for header:" + name);
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        bad("bad:method");
+        bad("Foo\n");
+        good("X-Foo!");
+        good("Bar~");
+        good("x");
+        bad(" ");
+        bad("x y");
+        bad("x\t");
+        bad("Bar\r\n");
+        good("Hello#world");
+        good("Qwer#ert");
+        bad("m\u00e9thode");
+        for (char c =0; c < 256 ; c++) {
+            if (c < 32 || FORBIDDEN.indexOf(c) > -1 || c >= 127) {
+                bad("me" + c + "thod");
+                bad(c + "thod");
+                bad("me" + c);
+            } else {
+                good("me" + c + "thod");
+                good(c + "thod");
+                good("me" + c);
+            }
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/MockServer.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/MockServer.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -21,12 +21,16 @@
  * questions.
  */
 
+import com.sun.net.httpserver.HttpServer;
+
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import javax.net.ServerSocketFactory;
 import javax.net.ssl.SSLServerSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
@@ -37,6 +41,7 @@
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
 
 /**
  * A cut-down Http/1 Server for testing various error situations
@@ -60,7 +65,7 @@
     // to the test client.
    final String root;
 
-    // waits up to 20 seconds for something to happen
+    // waits up to 2000 seconds for something to happen
     // dont use this unless certain activity coming.
     public Connection activity() {
         for (int i = 0; i < 80 * 100; i++) {
@@ -160,7 +165,7 @@
                         cleanup();
                         return;
                     }
-                    String s0 = new String(buf, 0, n, StandardCharsets.ISO_8859_1);
+                    String s0 = new String(buf, 0, n, ISO_8859_1);
                     s = s + s0;
                     int i;
                     while ((i=s.indexOf(CRLF)) != -1) {
@@ -195,7 +200,7 @@
             for (int i=0; i<headers.length; i+=2) {
                 r1 += headers[i] + ": " + headers[i+1] + CRLF;
             }
-            int clen = body == null ? 0 : body.length();
+            int clen = body == null ? 0 : body.getBytes(ISO_8859_1).length;
             r1 += "Content-Length: " + Integer.toString(clen) + CRLF;
             r1 += CRLF;
             if (body != null) {
@@ -208,7 +213,7 @@
         public void sendIncompleteHttpResponseBody(int code) throws IOException {
             String body = "Hello World Helloworld Goodbye World";
             String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF;
-            int clen = body.length() + 10;
+            int clen = body.getBytes(ISO_8859_1).length + 10;
             r1 += "Content-Length: " + Integer.toString(clen) + CRLF;
             r1 += CRLF;
             if (body != null) {
@@ -225,7 +230,18 @@
         }
 
         public void send(String r) throws IOException {
-            os.write(r.getBytes(StandardCharsets.ISO_8859_1));
+            try {
+                os.write(r.getBytes(ISO_8859_1));
+            } catch (IOException x) {
+                IOException suppressed =
+                        new IOException("MockServer["
+                            + ss.getLocalPort()
+                            +"] Failed while writing bytes: "
+                            +  x.getMessage());
+                x.addSuppressed(suppressed);
+                System.err.println("WARNING: " + suppressed);
+                throw x;
+            }
         }
 
         public synchronized void close() {
@@ -275,7 +291,9 @@
     }
 
     MockServer(int port, ServerSocketFactory factory, String root) throws IOException {
-        ss = factory.createServerSocket(port);
+        ss = factory.createServerSocket();
+        ss.setReuseAddress(false);
+        ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         this.root = root; // if specified, any request which don't have this value
                           // in their statusLine will be rejected.
         sockets = Collections.synchronizedList(new LinkedList<>());
@@ -301,11 +319,15 @@
         return ss.getLocalPort();
     }
 
+    String serverAuthority() {
+        return InetAddress.getLoopbackAddress().getHostName() + ":" + port();
+    }
+
     public String getURL() {
         if (ss instanceof SSLServerSocket) {
-            return "https://127.0.0.1:" + port() + "/foo/";
+            return "https://" + serverAuthority() + "/foo/";
         } else {
-            return "http://127.0.0.1:" + port() + "/foo/";
+            return "http://" + serverAuthority() + "/foo/";
         }
     }
 
--- a/test/jdk/java/net/httpclient/MultiAuthTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/MultiAuthTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -23,7 +23,7 @@
 
 /**
  * @test
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          jdk.httpserver
  * @run main/othervm MultiAuthTest
  * @summary Basic Authentication test with multiple clients issuing
@@ -39,15 +39,18 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.PasswordAuthentication;
 import java.net.URI;
-import jdk.incubator.http.*;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
@@ -59,7 +62,8 @@
     static final String POST_BODY = "This is the POST body " + UUID.randomUUID();
 
     static HttpServer createServer(ExecutorService e, BasicAuthenticator sa) throws Exception {
-        HttpServer server = HttpServer.create(new InetSocketAddress(0), 10);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        HttpServer server = HttpServer.create(addr, 10);
         Handler h = new Handler();
         HttpContext serverContext = server.createContext("/test", h);
         serverContext.setAuthenticator(sa);
@@ -93,7 +97,7 @@
         HttpClient client3 = HttpClient.newHttpClient();
 
         try {
-            URI uri = new URI("http://127.0.0.1:" + port + "/test/foo");
+            URI uri = new URI("http://localhost:" + port + "/test/foo");
             System.out.println("URI: " + uri);
 
             System.out.println("\nTesting with client #1, Authenticator #1");
@@ -173,9 +177,12 @@
 
         HttpResponse resp;
         try {
-            resp = client.send(req, asString());
+            resp = client.send(req, BodyHandlers.ofString());
             ok = resp.statusCode() == 200 &&
                 resp.body().equals(RESPONSE);
+            if (resp.statusCode() == 401 || resp.statusCode() == 407) {
+                throw new IOException(String.valueOf(resp));
+            }
             if (expectFailure != null) {
                 throw new RuntimeException("Expected " + expectFailure.getName()
                          +" not raised");
@@ -195,7 +202,7 @@
                  + " count=" + ca.count.get() + " (expected=" + expectCount+")");
 
         // repeat same request, should succeed but no additional authenticator calls
-        resp = client.send(req, asString());
+        resp = client.send(req, BodyHandlers.ofString());
         ok = resp.statusCode() == 200 &&
                 resp.body().equals(RESPONSE);
 
@@ -205,9 +212,9 @@
 
         // try a POST
         req = HttpRequest.newBuilder(uri)
-                         .POST(fromString(POST_BODY))
+                         .POST(BodyPublishers.ofString(POST_BODY))
                          .build();
-        resp = client.send(req, asString());
+        resp = client.send(req, BodyHandlers.ofString());
         ok = resp.statusCode() == 200;
 
         if (!ok || ca.count.get() != expectCount)
--- a/test/jdk/java/net/httpclient/NoBodyPartOne.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/NoBodyPartOne.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -28,26 +28,27 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
- * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=all NoBodyPartOne
+ *          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
  */
 
 import java.net.URI;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
 import org.testng.annotations.Test;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asByteArray;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asFile;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
@@ -62,9 +63,10 @@
                 client = newHttpClient();
 
             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
-                                         .PUT(fromString(SIMPLE_STRING))
+                                         .PUT(BodyPublishers.ofString(SIMPLE_STRING))
                                          .build();
-            BodyHandler<String> handler = i % 2 == 0 ? asString() : asString(UTF_8);
+            BodyHandler<String> handler = i % 2 == 0 ? BodyHandlers.ofString()
+                                                     : BodyHandlers.ofString(UTF_8);
             HttpResponse<String> response = client.send(req, handler);
             String body = response.body();
             assertEquals(body, "");
@@ -82,10 +84,10 @@
                 client = newHttpClient();
 
             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
-                                         .PUT(fromString(SIMPLE_STRING))
+                                         .PUT(BodyPublishers.ofString(SIMPLE_STRING))
                                          .build();
             Path p = Paths.get("NoBody_testAsFile.txt");
-            HttpResponse<Path> response = client.send(req, asFile(p));
+            HttpResponse<Path> response = client.send(req, BodyHandlers.ofFile(p));
             Path bodyPath = response.body();
             assertTrue(Files.exists(bodyPath));
             assertEquals(Files.size(bodyPath), 0);
@@ -103,9 +105,9 @@
                 client = newHttpClient();
 
             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
-                                         .PUT(fromString(SIMPLE_STRING))
+                                         .PUT(BodyPublishers.ofString(SIMPLE_STRING))
                                          .build();
-            HttpResponse<byte[]> response = client.send(req, asByteArray());
+            HttpResponse<byte[]> response = client.send(req, BodyHandlers.ofByteArray());
             byte[] body = response.body();
             assertEquals(body.length, 0);
         }
--- a/test/jdk/java/net/httpclient/NoBodyPartTwo.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/NoBodyPartTwo.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -28,26 +28,25 @@
  * @library /lib/testlibrary http2/server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
- * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=all NoBodyPartTwo
+ *          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
  */
 
 import java.io.InputStream;
 import java.net.URI;
 import java.util.Optional;
 import java.util.function.Consumer;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import org.testng.annotations.Test;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asByteArray;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asByteArrayConsumer;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asInputStream;
-import static jdk.incubator.http.HttpResponse.BodyHandler.buffering;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
@@ -64,14 +63,14 @@
                 client = newHttpClient();
 
             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
-                    .PUT(fromString(SIMPLE_STRING))
+                    .PUT(BodyPublishers.ofString(SIMPLE_STRING))
                     .build();
             Consumer<Optional<byte[]>>  consumer = oba -> {
                 consumerHasBeenCalled = true;
                 oba.ifPresent(ba -> fail("Unexpected non-empty optional:" + ba));
             };
             consumerHasBeenCalled = false;
-            client.send(req, asByteArrayConsumer(consumer));
+            client.send(req, BodyHandlers.ofByteArrayConsumer(consumer));
             assertTrue(consumerHasBeenCalled);
         }
         // We have created many clients here. Try to speed up their release.
@@ -87,9 +86,9 @@
                 client = newHttpClient();
 
             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
-                    .PUT(fromString(SIMPLE_STRING))
+                    .PUT(BodyPublishers.ofString(SIMPLE_STRING))
                     .build();
-            HttpResponse<InputStream> response = client.send(req, asInputStream());
+            HttpResponse<InputStream> response = client.send(req, BodyHandlers.ofInputStream());
             byte[] body = response.body().readAllBytes();
             assertEquals(body.length, 0);
         }
@@ -106,9 +105,10 @@
                 client = newHttpClient();
 
             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
-                    .PUT(fromString(SIMPLE_STRING))
+                    .PUT(BodyPublishers.ofString(SIMPLE_STRING))
                     .build();
-            HttpResponse<byte[]> response = client.send(req, buffering(asByteArray(), 1024));
+            HttpResponse<byte[]> response = client.send(req,
+                    BodyHandlers.buffering(BodyHandlers.ofByteArray(), 1024));
             byte[] body = response.body();
             assertEquals(body.length, 0);
         }
@@ -125,10 +125,10 @@
                 client = newHttpClient();
 
             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
-                    .PUT(fromString(SIMPLE_STRING))
+                    .PUT(BodyPublishers.ofString(SIMPLE_STRING))
                     .build();
             Object obj = new Object();
-            HttpResponse<Object> response = client.send(req, discard(obj));
+            HttpResponse<Object> response = client.send(req, BodyHandlers.replacing(obj));
             assertEquals(response.body(), obj);
         }
         // We have created many clients here. Try to speed up their release.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+/**
+ * @test
+ * @summary this test verifies that a client may provides authorization
+ *          headers directly when connecting with a server, and
+ *          it verifies that the client honor the jdk.http.auth.*.disabledSchemes
+ *          net properties.
+ * @bug 8087112
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient
+ *        ReferenceTracker ProxyAuthDisabledSchemes
+ * @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
+ *          java.base/sun.net
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Basic,Digest
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=Digest,Basic
+ *                   ProxyAuthDisabledSchemes
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Basic
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=Basic
+ *                   ProxyAuthDisabledSchemes CLEAR PROXY
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Digest
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=Digest
+ *                   ProxyAuthDisabledSchemes CLEAR PROXY
+ */
+
+public class ProxyAuthDisabledSchemes {
+    public static void main(String[] args) throws Exception {
+        DigestEchoClient.main(args);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 8087112
+ * @summary this test verifies that a client may provides authorization
+ *          headers directly when connecting with a server over SSL, and
+ *          it verifies that the client honor the jdk.http.auth.*.disabledSchemes
+ *          net properties.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient
+ *        ReferenceTracker ProxyAuthDisabledSchemesSSL
+ * @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
+ *          java.base/sun.net
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Basic,Digest
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=Digest,Basic
+ *                   ProxyAuthDisabledSchemesSSL SSL
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Basic
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=Basic
+ *                   ProxyAuthDisabledSchemesSSL SSL PROXY
+ * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Digest
+ *                   -Djdk.http.auth.tunneling.disabledSchemes=Digest
+ *                   ProxyAuthDisabledSchemesSSL SSL PROXY
+ */
+
+public class ProxyAuthDisabledSchemesSSL {
+    public static void main(String[] args) throws Exception {
+        assert "SSL".equals(args[0]);
+        DigestEchoClient.main(args);
+    }
+}
--- a/test/jdk/java/net/httpclient/ProxyAuthTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ProxyAuthTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -27,7 +27,7 @@
  * @test
  * @bug 8163561
  * @modules java.base/sun.net.www
- *          jdk.incubator.httpclient
+ *          java.net.http
  * @summary Verify that Proxy-Authenticate header is correctly handled
  * @run main/othervm ProxyAuthTest
  */
@@ -39,6 +39,7 @@
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.net.Authenticator;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.PasswordAuthentication;
 import java.net.Proxy;
@@ -47,27 +48,29 @@
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.util.Base64;
 import java.util.List;
 import sun.net.www.MessageHeader;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 
 public class ProxyAuthTest {
     private static final String AUTH_USER = "user";
     private static final String AUTH_PASSWORD = "password";
 
     public static void main(String[] args) throws Exception {
-        try (ServerSocket ss = new ServerSocket(0)) {
+        try (ServerSocket ss = new ServerSocket()) {
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             int port = ss.getLocalPort();
             MyProxy proxy = new MyProxy(ss);
             (new Thread(proxy)).start();
             System.out.println("Proxy listening port " + port);
 
             Auth auth = new Auth();
-            InetSocketAddress paddr = new InetSocketAddress("localhost", port);
+            InetSocketAddress paddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
 
             URI uri = new URI("http://www.google.ie/");
             CountingProxySelector ps = CountingProxySelector.of(paddr);
@@ -76,7 +79,7 @@
                                           .authenticator(auth)
                                           .build();
             HttpRequest req = HttpRequest.newBuilder(uri).GET().build();
-            HttpResponse<?> resp = client.sendAsync(req, discard(null)).get();
+            HttpResponse<?> resp = client.sendAsync(req, BodyHandlers.discarding()).get();
             if (resp.statusCode() != 404) {
                 throw new RuntimeException("Unexpected status code: " + resp.statusCode());
             }
--- a/test/jdk/java/net/httpclient/ProxyServer.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ProxyServer.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -48,7 +48,9 @@
 
     public ProxyServer(Integer port, Boolean debug) throws IOException {
         this.debug = debug;
-        listener = new ServerSocket(port);
+        listener = new ServerSocket();
+        listener.setReuseAddress(false);
+        listener.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port));
         this.port = listener.getLocalPort();
         setName("ProxyListener");
         setDaemon(true);
--- a/test/jdk/java/net/httpclient/ProxyTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ProxyTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -47,13 +47,15 @@
 import java.nio.charset.StandardCharsets;
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSession;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import jdk.testlibrary.SimpleSSLContext;
 
 /**
@@ -64,7 +66,7 @@
  *          Verifies that downgrading from HTTP/2 to HTTP/1.1 works through
  *          an SSL Tunnel connection when the client is HTTP/2 and the server
  *          and proxy are HTTP/1.1
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  * @library /lib/testlibrary/
  * @build jdk.testlibrary.SimpleSSLContext ProxyTest
  * @run main/othervm ProxyTest
@@ -102,7 +104,8 @@
         });
 
         server.setHttpsConfigurator(new Configurator(SSLContext.getDefault()));
-        server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        server.bind(addr, 0);
         return server;
     }
 
@@ -162,7 +165,8 @@
     {
         System.out.println("Server is: " + server.getAddress().toString());
         System.out.println("Verifying communication with server");
-        URI uri = new URI("https:/" + server.getAddress().toString() + PATH + "x");
+        URI uri = new URI("https://localhost:"
+                          + server.getAddress().getPort() + PATH + "x");
         try (InputStream is = uri.toURL().openConnection().getInputStream()) {
             String resp = new String(is.readAllBytes(), StandardCharsets.UTF_8);
             System.out.println(resp);
@@ -177,7 +181,8 @@
         try {
             System.out.println("Proxy started");
             Proxy p = new Proxy(Proxy.Type.HTTP,
-                    InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));
+                    InetSocketAddress.createUnresolved("localhost",
+                            proxy.getAddress().getPort()));
             System.out.println("Verifying communication with proxy");
             HttpURLConnection conn = (HttpURLConnection)uri.toURL().openConnection(p);
             try (InputStream is = conn.getInputStream()) {
@@ -192,7 +197,8 @@
             System.out.println("Setting up request with HttpClient for version: "
                     + version.name());
             CountingProxySelector ps = CountingProxySelector.of(
-                    InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));
+                    InetSocketAddress.createUnresolved("localhost",
+                            proxy.getAddress().getPort()));
             HttpClient client = HttpClient.newBuilder()
                 .version(version)
                 .proxy(ps)
@@ -204,7 +210,7 @@
 
             System.out.println("Sending request with HttpClient");
             HttpResponse<String> response
-                = client.send(request, HttpResponse.BodyHandler.asString());
+                = client.send(request, HttpResponse.BodyHandlers.ofString());
             System.out.println("Got response");
             String resp = response.body();
             System.out.println("Received: " + resp);
@@ -226,10 +232,14 @@
         final ServerSocket ss;
         final boolean DEBUG = false;
         final HttpServer serverImpl;
+        final CopyOnWriteArrayList<CompletableFuture<Void>> connectionCFs
+                = new CopyOnWriteArrayList<>();
+        private volatile boolean stopped;
         TunnelingProxy(HttpServer serverImpl) throws IOException {
             this.serverImpl = serverImpl;
             ss = new ServerSocket();
             accept = new Thread(this::accept);
+            accept.setDaemon(true);
         }
 
         void start() throws IOException {
@@ -238,7 +248,8 @@
         }
 
         // Pipe the input stream to the output stream.
-        private synchronized Thread pipe(InputStream is, OutputStream os, char tag) {
+        private synchronized Thread pipe(InputStream is, OutputStream os,
+                                         char tag, CompletableFuture<Void> end) {
             return new Thread("TunnelPipe("+tag+")") {
                 @Override
                 public void run() {
@@ -258,13 +269,16 @@
                         }
                     } catch (IOException ex) {
                         if (DEBUG) ex.printStackTrace(System.out);
+                    } finally {
+                        end.complete(null);
                     }
                 }
             };
         }
 
         public InetSocketAddress getAddress() {
-            return new InetSocketAddress(ss.getInetAddress(), ss.getLocalPort());
+            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                                         ss.getLocalPort());
         }
 
         // This is a bit shaky. It doesn't handle continuation
@@ -289,18 +303,14 @@
         public void accept() {
             Socket clientConnection = null;
             try {
-                while (true) {
+                while (!stopped) {
                     System.out.println("Tunnel: Waiting for client");
-                    Socket previous = clientConnection;
+                    Socket toClose;
                     try {
-                        clientConnection = ss.accept();
+                        toClose = clientConnection = ss.accept();
                     } catch (IOException io) {
                         if (DEBUG) io.printStackTrace(System.out);
                         break;
-                    } finally {
-                        // we have only 1 client at a time, so it is safe
-                        // to close the previous connection here
-                        if (previous != null) previous.close();
                     }
                     System.out.println("Tunnel: Client accepted");
                     Socket targetConnection = null;
@@ -325,7 +335,7 @@
 
                         // Open target connection
                         targetConnection = new Socket(
-                                serverImpl.getAddress().getAddress(),
+                                InetAddress.getLoopbackAddress(),
                                 serverImpl.getAddress().getPort());
 
                         // Then send the 200 OK response to the client
@@ -334,26 +344,45 @@
                         pw.print("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
                         pw.flush();
                     } else {
-                        // This should not happen.
-                        throw new IOException("Tunnel: Unexpected status line: "
-                                           + requestLine);
+                        // This should not happen. If it does then just print an
+                        // error - both on out and err, and close the accepted
+                        // socket
+                        System.out.println("WARNING: Tunnel: Unexpected status line: "
+                                + requestLine + " received by "
+                                + ss.getLocalSocketAddress()
+                                + " from "
+                                + toClose.getRemoteSocketAddress()
+                                + " - closing accepted socket");
+                        // Print on err
+                        System.err.println("WARNING: Tunnel: Unexpected status line: "
+                                + requestLine + " received by "
+                                + ss.getLocalSocketAddress()
+                                + " from "
+                                + toClose.getRemoteSocketAddress());
+                        // close accepted socket.
+                        toClose.close();
+                        System.err.println("Tunnel: accepted socket closed.");
+                        continue;
                     }
 
                     // Pipe the input stream of the client connection to the
                     // output stream of the target connection and conversely.
                     // Now the client and target will just talk to each other.
                     System.out.println("Tunnel: Starting tunnel pipes");
-                    Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+');
-                    Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-');
+                    CompletableFuture<Void> end, end1, end2;
+                    Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+',
+                            end1 = new CompletableFuture<>());
+                    Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-',
+                            end2 = new CompletableFuture<>());
+                    end = CompletableFuture.allOf(end1, end2);
+                    end.whenComplete(
+                            (r,t) -> {
+                                try { toClose.close(); } catch (IOException x) { }
+                                finally {connectionCFs.remove(end);}
+                            });
+                    connectionCFs.add(end);
                     t1.start();
                     t2.start();
-
-                    // We have only 1 client... wait until it has finished before
-                    // accepting a new connection request.
-                    // System.out.println("Tunnel: Waiting for pipes to close");
-                    // t1.join();
-                    // t2.join();
-                    System.out.println("Tunnel: Done - waiting for next client");
                 }
             } catch (Throwable ex) {
                 try {
@@ -362,10 +391,14 @@
                     ex.addSuppressed(ex1);
                 }
                 ex.printStackTrace(System.err);
+            } finally {
+                System.out.println("Tunnel: exiting (stopped=" + stopped + ")");
+                connectionCFs.forEach(cf -> cf.complete(null));
             }
         }
 
-        void stop() throws IOException {
+        public void stop() throws IOException {
+            stopped = true;
             ss.close();
         }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/RedirectMethodChange.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Method change during redirection
+ * @modules java.base/sun.net.www.http
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
+ *          jdk.httpserver
+ * @library /lib/testlibrary /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm RedirectMethodChange
+ */
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static org.testng.Assert.assertEquals;
+
+public class RedirectMethodChange implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpClient client;
+
+    HttpTestServer httpTestServer;        // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;       // HTTPS/1.1
+    HttpTestServer http2TestServer;       // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;      // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    static final String RESPONSE = "Hello world";
+    static final String POST_BODY = "This is the POST body 123909090909090";
+
+    static HttpRequest.BodyPublisher getRequestBodyFor(String method) {
+        switch (method) {
+            case "GET":
+            case "DELETE":
+            case "HEAD":
+                return BodyPublishers.noBody();
+            case "POST":
+            case "PUT":
+                return BodyPublishers.ofString(POST_BODY);
+            default:
+                throw new AssertionError("Unknown method:" + method);
+        }
+    }
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][] {
+                { httpURI, "GET",  301, "GET"  },
+                { httpURI, "GET",  302, "GET"  },
+                { httpURI, "GET",  303, "GET"  },
+                { httpURI, "GET",  307, "GET"  },
+                { httpURI, "GET",  308, "GET"  },
+                { httpURI, "POST", 301, "GET"  },
+                { httpURI, "POST", 302, "GET"  },
+                { httpURI, "POST", 303, "GET"  },
+                { httpURI, "POST", 307, "POST" },
+                { httpURI, "POST", 308, "POST" },
+                { httpURI, "PUT",  301, "PUT"  },
+                { httpURI, "PUT",  302, "PUT"  },
+                { httpURI, "PUT",  303, "GET"  },
+                { httpURI, "PUT",  307, "PUT"  },
+                { httpURI, "PUT",  308, "PUT"  },
+
+                { httpsURI, "GET",  301, "GET"  },
+                { httpsURI, "GET",  302, "GET"  },
+                { httpsURI, "GET",  303, "GET"  },
+                { httpsURI, "GET",  307, "GET"  },
+                { httpsURI, "GET",  308, "GET"  },
+                { httpsURI, "POST", 301, "GET"  },
+                { httpsURI, "POST", 302, "GET"  },
+                { httpsURI, "POST", 303, "GET"  },
+                { httpsURI, "POST", 307, "POST" },
+                { httpsURI, "POST", 308, "POST" },
+                { httpsURI, "PUT",  301, "PUT"  },
+                { httpsURI, "PUT",  302, "PUT"  },
+                { httpsURI, "PUT",  303, "GET"  },
+                { httpsURI, "PUT",  307, "PUT"  },
+                { httpsURI, "PUT",  308, "PUT"  },
+
+                { http2URI, "GET",  301, "GET"  },
+                { http2URI, "GET",  302, "GET"  },
+                { http2URI, "GET",  303, "GET"  },
+                { http2URI, "GET",  307, "GET"  },
+                { http2URI, "GET",  308, "GET"  },
+                { http2URI, "POST", 301, "GET"  },
+                { http2URI, "POST", 302, "GET"  },
+                { http2URI, "POST", 303, "GET"  },
+                { http2URI, "POST", 307, "POST" },
+                { http2URI, "POST", 308, "POST" },
+                { http2URI, "PUT",  301, "PUT"  },
+                { http2URI, "PUT",  302, "PUT"  },
+                { http2URI, "PUT",  303, "GET"  },
+                { http2URI, "PUT",  307, "PUT"  },
+                { http2URI, "PUT",  308, "PUT"  },
+
+                { https2URI, "GET",  301, "GET"  },
+                { https2URI, "GET",  302, "GET"  },
+                { https2URI, "GET",  303, "GET"  },
+                { https2URI, "GET",  307, "GET"  },
+                { https2URI, "GET",  308, "GET"  },
+                { https2URI, "POST", 301, "GET"  },
+                { https2URI, "POST", 302, "GET"  },
+                { https2URI, "POST", 303, "GET"  },
+                { https2URI, "POST", 307, "POST" },
+                { https2URI, "POST", 308, "POST" },
+                { https2URI, "PUT",  301, "PUT"  },
+                { https2URI, "PUT",  302, "PUT"  },
+                { https2URI, "PUT",  303, "GET"  },
+                { https2URI, "PUT",  307, "PUT"  },
+                { https2URI, "PUT",  308, "PUT"  },
+        };
+    }
+
+    @Test(dataProvider = "variants")
+    public void test(String uriString,
+                     String method,
+                     int redirectCode,
+                     String expectedMethod)
+        throws Exception
+    {
+        HttpRequest req = HttpRequest.newBuilder(URI.create(uriString))
+                .method(method, getRequestBodyFor(method))
+                .header("X-Redirect-Code", Integer.toString(redirectCode))
+                .header("X-Expect-Method", expectedMethod)
+                .build();
+        HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());
+
+        System.out.println("Response: " + resp + ", body: " + resp.body());
+        assertEquals(resp.statusCode(), 200);
+        assertEquals(resp.body(), RESPONSE);
+    }
+
+    // -- Infrastructure
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        client = HttpClient.newBuilder()
+                .followRedirects(HttpClient.Redirect.NORMAL)
+                .sslContext(sslContext)
+                .build();
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        String targetURI = "http://" + httpTestServer.serverAuthority() + "/http1/redirect/rmt";
+        RedirMethodChgeHandler handler = new RedirMethodChgeHandler(targetURI);
+        httpTestServer.addHandler(handler, "/http1/");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/test/rmt";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        targetURI = "https://" + httpsTestServer.serverAuthority() + "/https1/redirect/rmt";
+        handler = new RedirMethodChgeHandler(targetURI);
+        httpsTestServer.addHandler(handler,"/https1/");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/test/rmt";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        targetURI = "http://" + http2TestServer.serverAuthority() + "/http2/redirect/rmt";
+        handler = new RedirMethodChgeHandler(targetURI);
+        http2TestServer.addHandler(handler, "/http2/");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/test/rmt";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        targetURI = "https://" + https2TestServer.serverAuthority() + "/https2/redirect/rmt";
+        handler = new RedirMethodChgeHandler(targetURI);
+        https2TestServer.addHandler(handler, "/https2/");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/test/rmt";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    /**
+     * Stateful handler.
+     *
+     * Request to "<protocol>/test/rmt" is first, with the following checked
+     * headers:
+     *   X-Redirect-Code: nnn    <the redirect code to send back>
+     *   X-Expect-Method: the method that the client should use for the next request
+     *
+     * The following request should be to "<protocol>/redirect/rmt" and should
+     * use the method indicated previously. If all ok, return a 200 response.
+     * Otherwise 50X error.
+     */
+    static class RedirMethodChgeHandler implements HttpTestHandler {
+
+        boolean inTest;
+        String expectedMethod;
+
+        final String targetURL;
+        RedirMethodChgeHandler(String targetURL) {
+            this.targetURL = targetURL;
+        }
+
+        boolean readAndCheckBody(HttpTestExchange e) throws IOException {
+            String method = e.getRequestMethod();
+            String requestBody;
+            try (InputStream is = e.getRequestBody()) {
+                requestBody = new String(is.readAllBytes(), US_ASCII);
+            }
+            if ((method.equals("POST") || method.equals("PUT"))
+                    && !requestBody.equals(POST_BODY)) {
+                Throwable ex = new RuntimeException("Unexpected request body for "
+                        + method + ": [" + requestBody +"]");
+                ex.printStackTrace();
+                e.sendResponseHeaders(503, 0);
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public synchronized void handle(HttpTestExchange he) throws IOException {
+            boolean newtest = he.getRequestURI().getPath().endsWith("/test/rmt");
+            if ((newtest && inTest) || (!newtest && !inTest)) {
+                Throwable ex = new RuntimeException("Unexpected newtest:" + newtest
+                        + ", inTest:" + inTest +  ", for " + he.getRequestURI());
+                ex.printStackTrace();
+                he.sendResponseHeaders(500, 0);
+                return;
+            }
+
+            if (newtest) {
+                HttpTestHeaders hdrs = he.getRequestHeaders();
+                String value = hdrs.firstValue("X-Redirect-Code").get();
+                int redirectCode = Integer.parseInt(value);
+                expectedMethod = hdrs.firstValue("X-Expect-Method").get();
+                if (!readAndCheckBody(he))
+                    return;
+                hdrs = he.getResponseHeaders();
+                hdrs.addHeader("Location", targetURL);
+                he.sendResponseHeaders(redirectCode, 0);
+                inTest = true;
+            } else {
+                // should be the redirect
+                if (!he.getRequestURI().getPath().endsWith("/redirect/rmt")) {
+                    Throwable ex = new RuntimeException("Unexpected redirected request, got:"
+                            + he.getRequestURI());
+                    ex.printStackTrace();
+                    he.sendResponseHeaders(501, 0);
+                } else if (!he.getRequestMethod().equals(expectedMethod)) {
+                    Throwable ex = new RuntimeException("Expected: " + expectedMethod
+                            + " Got: " + he.getRequestMethod());
+                    ex.printStackTrace();
+                    he.sendResponseHeaders(504, 0);
+                } else {
+                    if (!readAndCheckBody(he))
+                        return;
+                    he.sendResponseHeaders(200, RESPONSE.length());
+                    try (OutputStream os = he.getResponseBody()) {
+                        os.write(RESPONSE.getBytes(US_ASCII));
+                    }
+                }
+                inTest = false;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/RedirectWithCookie.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,233 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test for cookie handling when redirecting
+ * @modules java.base/sun.net.www.http
+ *          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 /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm
+ *       -Djdk.httpclient.HttpClient.log=trace,headers,requests
+ *       RedirectWithCookie
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CookieManager;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Redirect;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class RedirectWithCookie implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;        // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;       // HTTPS/1.1
+    HttpTestServer http2TestServer;       // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;      // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    static final String MESSAGE = "BasicRedirectTest message body";
+    static final int ITERATIONS = 3;
+
+    @DataProvider(name = "positive")
+    public Object[][] positive() {
+        return new Object[][] {
+                { httpURI,    },
+                { httpsURI,   },
+                { http2URI,   },
+                { https2URI,  },
+        };
+    }
+
+    @Test(dataProvider = "positive")
+    void test(String uriString) throws Exception {
+        out.printf("%n---- starting (%s) ----%n", uriString);
+        HttpClient client = HttpClient.newBuilder()
+                .followRedirects(Redirect.ALWAYS)
+                .cookieHandler(new CookieManager())
+                .sslContext(sslContext)
+                .build();
+        assert client.cookieHandler().isPresent();
+
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri).build();
+        out.println("Initial request: " + request.uri());
+
+        for (int i=0; i< ITERATIONS; i++) {
+            out.println("iteration: " + i);
+            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+
+            out.println("  Got response: " + response);
+            out.println("  Got body Path: " + response.body());
+
+            assertEquals(response.statusCode(), 200);
+            assertEquals(response.body(), MESSAGE);
+            // asserts redirected URI in response.request().uri()
+            assertTrue(response.uri().getPath().endsWith("message"));
+            assertPreviousRedirectResponses(request, response);
+        }
+    }
+
+    static void assertPreviousRedirectResponses(HttpRequest initialRequest,
+                                                HttpResponse<?> finalResponse) {
+        // there must be at least one previous response
+        finalResponse.previousResponse()
+                .orElseThrow(() -> new RuntimeException("no previous response"));
+
+        HttpResponse<?> response = finalResponse;
+        do {
+            URI uri = response.uri();
+            response = response.previousResponse().get();
+            assertTrue(300 <= response.statusCode() && response.statusCode() <= 309,
+                    "Expected 300 <= code <= 309, got:" + response.statusCode());
+            assertEquals(response.body(), null, "Unexpected body: " + response.body());
+            String locationHeader = response.headers().firstValue("Location")
+                    .orElseThrow(() -> new RuntimeException("no previous Location"));
+            assertTrue(uri.toString().endsWith(locationHeader),
+                    "URI: " + uri + ", Location: " + locationHeader);
+
+        } while (response.previousResponse().isPresent());
+
+        // initial
+        assertEquals(initialRequest, response.request(),
+                String.format("Expected initial request [%s] to equal last prev req [%s]",
+                        initialRequest, response.request()));
+    }
+
+    // -- Infrastructure
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(new CookieRedirectHandler(), "/http1/cookie/");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/cookie/redirect";
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(new CookieRedirectHandler(),"/https1/cookie/");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/cookie/redirect";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(new CookieRedirectHandler(), "/http2/cookie/");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/cookie/redirect";
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(new CookieRedirectHandler(), "/https2/cookie/");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/cookie/redirect";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    static class CookieRedirectHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            System.out.println("CookieRedirectHandler for: " + t.getRequestURI());
+            readAllRequestData(t);
+
+            // redirecting
+            if (t.getRequestURI().getPath().endsWith("redirect")) {
+                String url = t.getRequestURI().resolve("message").toString();
+                t.getResponseHeaders().addHeader("Location", url);
+                t.getResponseHeaders().addHeader("Set-Cookie",
+                                                 "CUSTOMER=WILE_E_COYOTE");
+                t.sendResponseHeaders(302, 0);
+                return;
+            }
+
+            // not redirecting
+            try (OutputStream os = t.getResponseBody()) {
+                List<String> cookie = t.getRequestHeaders().get("Cookie");
+
+                if (cookie == null || cookie.size() == 0) {
+                    String msg = "No cookie header present";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else if (!cookie.get(0).equals("CUSTOMER=WILE_E_COYOTE")) {
+                    String msg = "Incorrect cookie header value:[" + cookie.get(0) + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else {
+                    assert cookie.get(0).equals("CUSTOMER=WILE_E_COYOTE");
+                    byte[] bytes = MESSAGE.getBytes(UTF_8);
+                    t.sendResponseHeaders(200, bytes.length);
+                    os.write(bytes);
+                }
+            }
+        }
+    }
+
+    static void readAllRequestData(HttpTestExchange t) throws IOException {
+        try (InputStream is = t.getRequestBody()) {
+            is.readAllBytes();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ReferenceTracker.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+import jdk.internal.net.http.common.OperationTrackers;
+import jdk.internal.net.http.common.OperationTrackers.Tracker;
+
+import java.net.http.HttpClient;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.stream.Collectors;
+
+/**
+ * A small helper class to help track clients which still
+ * have pending operations at the end of a test.
+ */
+public class ReferenceTracker {
+    private final ConcurrentLinkedQueue<Tracker> TRACKERS
+            = new ConcurrentLinkedQueue<Tracker>();
+
+    public static final ReferenceTracker INSTANCE
+            = new ReferenceTracker();
+
+    public HttpClient track(HttpClient client) {
+        Tracker tracker = OperationTrackers.getTracker(client);
+        assert tracker != null;
+        TRACKERS.add(tracker);
+        return client;
+    }
+
+    public long getTrackedClientCount() {
+        return TRACKERS.size();
+    }
+
+    public StringBuilder diagnose(StringBuilder warnings) {
+        for (Tracker tracker : TRACKERS) {
+            checkOutstandingOperations(warnings, tracker);
+        }
+        return warnings;
+    }
+
+    public boolean hasOutstandingOperations() {
+        return TRACKERS.stream().anyMatch(t -> t.getOutstandingOperations() > 0);
+    }
+
+    public long getOutstandingOperationsCount() {
+        return TRACKERS.stream()
+                .map(Tracker::getOutstandingOperations)
+                .filter(n -> n > 0)
+                .collect(Collectors.summingLong(n -> n));
+    }
+
+    public long getOutstandingClientCount() {
+        return TRACKERS.stream()
+                .map(Tracker::getOutstandingOperations)
+                .filter(n -> n > 0)
+                .count();
+    }
+
+    public AssertionError check(long graceDelayMs) {
+        AssertionError fail = null;
+        if (hasOutstandingOperations()) {
+            try {
+                Thread.sleep(graceDelayMs);
+            } catch (InterruptedException x) {
+                // OK
+            }
+            StringBuilder warnings = diagnose(new StringBuilder());
+            addSummary(warnings);
+            if (hasOutstandingOperations()) {
+                fail = new AssertionError(warnings.toString());
+            }
+        } else {
+            System.out.println("PASSED: No outstanding operations found in "
+                    + getTrackedClientCount() + " clients");
+        }
+        return fail;
+    }
+
+    private void addSummary(StringBuilder warning) {
+        long activeClients = getOutstandingClientCount();
+        long operations = getOutstandingOperationsCount();
+        long tracked = getTrackedClientCount();
+        if (warning.length() > 0) warning.append("\n");
+        int pos = warning.length();
+        warning.append("Found ")
+                .append(activeClients)
+                .append(" client still active, with ")
+                .append(operations)
+                .append(" operations still pending out of ")
+                .append(tracked)
+                .append(" tracked clients.");
+        System.out.println(warning.toString().substring(pos));
+        System.err.println(warning.toString().substring(pos));
+    }
+
+    private static void checkOutstandingOperations(StringBuilder warning, Tracker tracker) {
+        if (tracker.getOutstandingOperations() > 0) {
+            if (warning.length() > 0) warning.append("\n");
+            int pos = warning.length();
+            warning.append("WARNING: tracker for " + tracker.getName() + " has outstanding operations:");
+            warning.append("\n\tPending HTTP/1.1 operations: " + tracker.getOutstandingHttpOperations());
+            warning.append("\n\tPending HTTP/2 streams: " + tracker.getOutstandingHttp2Streams());
+            warning.append("\n\tPending WebSocket operations: " + tracker.getOutstandingWebSocketOperations());
+            warning.append("\n\tTotal pending operations: " + tracker.getOutstandingOperations());
+            warning.append("\n\tFacade referenced: " + tracker.isFacadeReferenced());
+            System.out.println(warning.toString().substring(pos));
+            System.err.println(warning.toString().substring(pos));
+        }
+    }
+
+}
--- a/test/jdk/java/net/httpclient/RequestBodyTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/RequestBodyTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,31 +24,37 @@
 /*
  * @test
  * @bug 8087112
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary/ /test/lib
  * @compile ../../../com/sun/net/httpserver/LogFilter.java
  * @compile ../../../com/sun/net/httpserver/EchoHandler.java
  * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @build LightWeightHttpServer
  * @build jdk.test.lib.Platform
  * @build jdk.test.lib.util.FileUtils
- * @build LightWeightHttpServer
- * @build jdk.testlibrary.SimpleSSLContext
  * @run testng/othervm RequestBodyTest
+ * @run testng/othervm/java.security.policy=RequestBodyTest.policy RequestBodyTest
  */
 
 import java.io.*;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -61,8 +67,6 @@
 import static java.lang.System.out;
 import static java.nio.charset.StandardCharsets.*;
 import static java.nio.file.StandardOpenOption.*;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.*;
-import static jdk.incubator.http.HttpResponse.BodyHandler.*;
 
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
@@ -191,28 +195,28 @@
 
         switch (requestBodyType) {
             case BYTE_ARRAY:
-                rb.POST(fromByteArray(fileAsBytes));
+                rb.POST(BodyPublishers.ofByteArray(fileAsBytes));
                 break;
             case BYTE_ARRAY_OFFSET:
-                rb.POST(fromByteArray(fileAsBytes,
-                                      DEFAULT_OFFSET,
-                                      fileAsBytes.length * DEFAULT_LENGTH_FACTOR));
+                rb.POST(BodyPublishers.ofByteArray(fileAsBytes,
+                        DEFAULT_OFFSET,
+                        fileAsBytes.length * DEFAULT_LENGTH_FACTOR));
                 break;
             case BYTE_ARRAYS:
                 Iterable<byte[]> iterable = Arrays.asList(fileAsBytes);
-                rb.POST(fromByteArrays(iterable));
+                rb.POST(BodyPublishers.ofByteArrays(iterable));
                 break;
             case FILE:
-                rb.POST(fromFile(file));
+                rb.POST(BodyPublishers.ofFile(file));
                 break;
             case INPUTSTREAM:
-                rb.POST(fromInputStream(fileInputStreamSupplier(file)));
+                rb.POST(BodyPublishers.ofInputStream(fileInputStreamSupplier(file)));
                 break;
             case STRING:
-                rb.POST(fromString(fileAsString));
+                rb.POST(BodyPublishers.ofString(fileAsString));
                 break;
             case STRING_WITH_CHARSET:
-                rb.POST(fromString(new String(fileAsBytes), Charset.defaultCharset()));
+                rb.POST(BodyPublishers.ofString(new String(fileAsBytes), Charset.defaultCharset()));
                 break;
             default:
                 throw new AssertionError("Unknown request body:" + requestBodyType);
@@ -242,8 +246,8 @@
 
         switch (responseBodyType) {
             case BYTE_ARRAY:
-                BodyHandler<byte[]> bh = asByteArray();
-                if (bufferResponseBody) bh = buffering(bh, 50);
+                BodyHandler<byte[]> bh = BodyHandlers.ofByteArray();
+                if (bufferResponseBody) bh = BodyHandlers.buffering(bh, 50);
                 HttpResponse<byte[]> bar = getResponse(client, request, bh, async);
                 assertEquals(bar.statusCode(), 200);
                 assertEquals(bar.body(), fileAsBytes);
@@ -251,8 +255,8 @@
             case BYTE_ARRAY_CONSUMER:
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 Consumer<Optional<byte[]>> consumer = o -> consumerBytes(o, baos);
-                BodyHandler<Void> bh1 = asByteArrayConsumer(consumer);
-                if (bufferResponseBody) bh1 = buffering(bh1, 49);
+                BodyHandler<Void> bh1 = BodyHandlers.ofByteArrayConsumer(consumer);
+                if (bufferResponseBody) bh1 = BodyHandlers.buffering(bh1, 49);
                 HttpResponse<Void> v = getResponse(client, request, bh1, async);
                 byte[] ba = baos.toByteArray();
                 assertEquals(v.statusCode(), 200);
@@ -260,38 +264,38 @@
                 break;
             case DISCARD:
                 Object o = new Object();
-                BodyHandler<Object> bh2 = discard(o);
-                if (bufferResponseBody) bh2 = buffering(bh2, 51);
+                BodyHandler<Object> bh2 = BodyHandlers.replacing(o);
+                if (bufferResponseBody) bh2 = BodyHandlers.buffering(bh2, 51);
                 HttpResponse<Object> or = getResponse(client, request, bh2, async);
                 assertEquals(or.statusCode(), 200);
                 assertSame(or.body(), o);
                 break;
             case FILE:
-                BodyHandler<Path> bh3 = asFile(tempFile);
-                if (bufferResponseBody) bh3 = buffering(bh3, 48);
+                BodyHandler<Path> bh3 = BodyHandlers.ofFile(tempFile);
+                if (bufferResponseBody) bh3 = BodyHandlers.buffering(bh3, 48);
                 HttpResponse<Path> fr = getResponse(client, request, bh3, async);
                 assertEquals(fr.statusCode(), 200);
                 assertEquals(Files.size(tempFile), fileAsString.length());
                 assertEquals(Files.readAllBytes(tempFile), fileAsBytes);
                 break;
             case FILE_WITH_OPTION:
-                BodyHandler<Path> bh4 = asFile(tempFile, CREATE_NEW, WRITE);
-                if (bufferResponseBody) bh4 = buffering(bh4, 52);
+                BodyHandler<Path> bh4 = BodyHandlers.ofFile(tempFile, CREATE_NEW, WRITE);
+                if (bufferResponseBody) bh4 = BodyHandlers.buffering(bh4, 52);
                 fr = getResponse(client, request, bh4, async);
                 assertEquals(fr.statusCode(), 200);
                 assertEquals(Files.size(tempFile), fileAsString.length());
                 assertEquals(Files.readAllBytes(tempFile), fileAsBytes);
                 break;
             case STRING:
-                BodyHandler<String> bh5 = asString();
-                if(bufferResponseBody) bh5 = buffering(bh5, 47);
+                BodyHandler<String> bh5 = BodyHandlers.ofString();
+                if(bufferResponseBody) bh5 = BodyHandlers.buffering(bh5, 47);
                 HttpResponse<String> sr = getResponse(client, request, bh5, async);
                 assertEquals(sr.statusCode(), 200);
                 assertEquals(sr.body(), fileAsString);
                 break;
             case STRING_WITH_CHARSET:
-                BodyHandler<String> bh6 = asString(StandardCharsets.UTF_8);
-                if (bufferResponseBody) bh6 = buffering(bh6, 53);
+                BodyHandler<String> bh6 = BodyHandlers.ofString(StandardCharsets.UTF_8);
+                if (bufferResponseBody) bh6 = BodyHandlers.buffering(bh6, 53);
                 HttpResponse<String> r = getResponse(client, request, bh6, async);
                 assertEquals(r.statusCode(), 200);
                 assertEquals(r.body(), fileAsString);
@@ -328,9 +332,11 @@
             @Override
             public FileInputStream get() {
                 try {
-                    return new FileInputStream(file.toFile());
-                } catch (FileNotFoundException x) {
-                    throw new UncheckedIOException(x);
+                    PrivilegedExceptionAction<FileInputStream> pa =
+                            () -> new FileInputStream(file.toFile());
+                    return AccessController.doPrivileged(pa);
+                } catch (PrivilegedActionException x) {
+                    throw new UncheckedIOException((IOException)x.getCause());
                 }
             }
         };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/RequestBodyTest.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,52 @@
+//
+// 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.
+//
+
+// for JTwork//classes/0/test/lib/jdk/test/lib/util/FileUtils.class
+grant codeBase "file:${test.classes}/../../../../test/lib/-" {
+    permission java.util.PropertyPermission "*", "read";
+    permission java.io.FilePermission "RequestBodyTest.tmp", "read,delete";
+};
+
+// for JTwork/classes/0/lib/testlibrary/jdk/testlibrary/SimpleSSLContext.class
+grant codeBase "file:${test.classes}/../../../../lib/testlibrary/-" {
+    permission java.util.PropertyPermission "test.src.path", "read";
+    permission java.io.FilePermission "${test.src}/../../../lib/testlibrary/jdk/testlibrary/testkeys", "read";
+};
+
+grant codeBase "file:${test.classes}/*" {
+    permission java.io.FilePermission "${test.src}${/}docs${/}files${/}smallfile.txt", "read";
+    permission java.io.FilePermission "${test.src}${/}docs${/}files${/}notsobigfile.txt", "read";
+    permission java.io.FilePermission "RequestBodyTest.tmp", "read,write,delete";
+
+    permission java.net.URLPermission "http://localhost:*/echo/foo",   "POST";
+    permission java.net.URLPermission "https://localhost:*/echo/foo",  "POST";
+
+    // for HTTP/1.1 server logging
+    permission java.util.logging.LoggingPermission "control";
+
+    // needed to grant the HTTP server
+    permission java.net.SocketPermission "localhost:*", "accept,resolve";
+
+    permission java.util.PropertyPermission "*", "read";
+    permission java.lang.RuntimePermission "modifyThread";
+};
--- a/test/jdk/java/net/httpclient/RequestBuilderTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/RequestBuilderTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -28,18 +28,22 @@
  */
 
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.List;
-import jdk.incubator.http.HttpRequest;
+import java.util.Map;
+import java.util.Set;
+
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
 import static java.time.Duration.ofNanos;
 import static java.time.Duration.ofMinutes;
 import static java.time.Duration.ofSeconds;
 import static java.time.Duration.ZERO;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.noBody;
-import static jdk.incubator.http.HttpRequest.newBuilder;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+import static java.net.http.HttpClient.Version.HTTP_2;
+import static java.net.http.HttpRequest.newBuilder;
 import static org.testng.Assert.*;
+
 import org.testng.annotations.Test;
 
 public class RequestBuilderTest {
@@ -54,7 +58,9 @@
     @Test
     public void testDefaults() {
         List<HttpRequest.Builder> builders = List.of(newBuilder().uri(uri),
-                                                     newBuilder(uri));
+                                                     newBuilder(uri),
+                                                     newBuilder().copy().uri(uri),
+                                                     newBuilder(uri).copy());
         for (HttpRequest.Builder builder : builders) {
             assertFalse(builder.build().expectContinue());
             assertEquals(builder.build().method(), "GET");
@@ -90,7 +96,6 @@
         assertThrows(NPE, () -> builder.setHeader("name", null));
         assertThrows(NPE, () -> builder.setHeader(null, "value"));
         assertThrows(NPE, () -> builder.timeout(null));
-        assertThrows(NPE, () -> builder.DELETE(null));
         assertThrows(NPE, () -> builder.POST(null));
         assertThrows(NPE, () -> builder.PUT(null));
     }
@@ -132,51 +137,51 @@
         assertEquals(request.method(), "GET");
         assertTrue(!request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).POST(fromString("")).GET().build();
+        request = newBuilder(uri).POST(BodyPublishers.ofString("")).GET().build();
         assertEquals(request.method(), "GET");
         assertTrue(!request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).PUT(fromString("")).GET().build();
+        request = newBuilder(uri).PUT(BodyPublishers.ofString("")).GET().build();
         assertEquals(request.method(), "GET");
         assertTrue(!request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).DELETE(fromString("")).GET().build();
+        request = newBuilder(uri).DELETE().GET().build();
         assertEquals(request.method(), "GET");
         assertTrue(!request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).POST(fromString("")).build();
+        request = newBuilder(uri).POST(BodyPublishers.ofString("")).build();
         assertEquals(request.method(), "POST");
         assertTrue(request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).PUT(fromString("")).build();
+        request = newBuilder(uri).PUT(BodyPublishers.ofString("")).build();
         assertEquals(request.method(), "PUT");
         assertTrue(request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).DELETE(fromString("")).build();
+        request = newBuilder(uri).DELETE().build();
         assertEquals(request.method(), "DELETE");
-        assertTrue(request.bodyPublisher().isPresent());
+        assertTrue(!request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).GET().POST(fromString("")).build();
+        request = newBuilder(uri).GET().POST(BodyPublishers.ofString("")).build();
         assertEquals(request.method(), "POST");
         assertTrue(request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).GET().PUT(fromString("")).build();
+        request = newBuilder(uri).GET().PUT(BodyPublishers.ofString("")).build();
         assertEquals(request.method(), "PUT");
         assertTrue(request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).GET().DELETE(fromString("")).build();
+        request = newBuilder(uri).GET().DELETE().build();
         assertEquals(request.method(), "DELETE");
-        assertTrue(request.bodyPublisher().isPresent());
+        assertTrue(!request.bodyPublisher().isPresent());
 
         // CONNECT is disallowed in the implementation, since it is used for
         // tunneling, and is handled separately for security checks.
-        assertThrows(IAE, () -> newBuilder(uri).method("CONNECT", noBody()).build());
+        assertThrows(IAE, () -> newBuilder(uri).method("CONNECT", BodyPublishers.noBody()).build());
 
-        request = newBuilder(uri).method("GET", noBody()).build();
+        request = newBuilder(uri).method("GET", BodyPublishers.noBody()).build();
         assertEquals(request.method(), "GET");
         assertTrue(request.bodyPublisher().isPresent());
 
-        request = newBuilder(uri).method("POST", fromString("")).build();
+        request = newBuilder(uri).method("POST", BodyPublishers.ofString("")).build();
         assertEquals(request.method(), "POST");
         assertTrue(request.bodyPublisher().isPresent());
     }
@@ -307,11 +312,55 @@
         }
     }
 
+    private static final Set<String> RESTRICTED = Set.of("connection", "content-length",
+            "date", "expect", "from", "host", "origin",
+            "referer", "upgrade", "via", "warning",
+            "Connection", "Content-Length",
+            "DATE", "eXpect", "frOm", "hosT", "origIN",
+            "ReFerer", "upgradE", "vIa", "Warning",
+            "CONNection", "CONTENT-LENGTH",
+            "Date", "EXPECT", "From", "Host", "Origin",
+            "Referer", "Upgrade", "Via", "WARNING");
+
+    interface WithHeader {
+        HttpRequest.Builder withHeader(HttpRequest.Builder builder, String name, String value);
+    }
+
+    @Test
+    public void testRestricted()  throws URISyntaxException {
+        URI uri = new URI("http://localhost:80/test/");
+        Map<String, WithHeader> lambdas = Map.of(
+                "Builder::header",    HttpRequest.Builder::header,
+                "Builder::headers",   (b, n, v) -> b.headers(n,v),
+                "Builder::setHeader", HttpRequest.Builder::setHeader
+                );
+        for (Map.Entry<String, WithHeader> e : lambdas.entrySet()) {
+            System.out.println("Testing restricted headers with " + e.getKey());
+            WithHeader f = e.getValue();
+            for (String name : RESTRICTED) {
+                String value = name + "-value";
+                HttpRequest req = f.withHeader(HttpRequest.newBuilder(uri)
+                        .GET(), "x-" + name, value).build();
+                String v = req.headers().firstValue("x-" + name).orElseThrow(
+                        () -> new RuntimeException("header x-" + name + " not set"));
+                assertEquals(v, value);
+                try {
+                    f.withHeader(HttpRequest.newBuilder(uri)
+                            .GET(), name, value).build();
+                    throw new RuntimeException("Expected IAE not thrown for " + name);
+                } catch (IllegalArgumentException x) {
+                    System.out.println("Got expected IAE for " + name + ": " + x);
+                }
+            }
+        }
+    }
+
+
     @Test
     public void testCopy() {
         HttpRequest.Builder builder = newBuilder(uri).expectContinue(true)
                                                      .header("A", "B")
-                                                     .POST(fromString(""))
+                                                     .POST(BodyPublishers.ofString(""))
                                                      .timeout(ofSeconds(30))
                                                      .version(HTTP_1_1);
         HttpRequest.Builder copy = builder.copy();
@@ -329,6 +378,17 @@
         assertEquals(copyRequest.timeout().get(), ofSeconds(30));
         assertTrue(copyRequest.version().isPresent());
         assertEquals(copyRequest.version().get(), HTTP_1_1);
+
+        // lazy set URI ( maybe builder as a template )
+        copyRequest = newBuilder().copy().uri(uri).build();
+        assertEquals(copyRequest.uri(), uri);
+
+        builder = newBuilder().header("C", "D");
+        copy = builder.copy();
+        copy.uri(uri);
+        copyRequest = copy.build();
+        assertEquals(copyRequest.uri(), uri);
+        assertEquals(copyRequest.headers().firstValue("C").get(), "D");
     }
 
     @Test
@@ -361,13 +421,13 @@
         assertEquals(builder.build(), builder.build());
         assertEquals(builder.build(), newBuilder(uri).build());
 
-        builder.POST(noBody());
+        builder.POST(BodyPublishers.noBody());
         assertEquals(builder.build(), builder.build());
-        assertEquals(builder.build(), newBuilder(uri).POST(noBody()).build());
-        assertEquals(builder.build(), newBuilder(uri).POST(fromString("")).build());
+        assertEquals(builder.build(), newBuilder(uri).POST(BodyPublishers.noBody()).build());
+        assertEquals(builder.build(), newBuilder(uri).POST(BodyPublishers.ofString("")).build());
         assertNotEquals(builder.build(), newBuilder(uri).build());
         assertNotEquals(builder.build(), newBuilder(uri).GET().build());
-        assertNotEquals(builder.build(), newBuilder(uri).PUT(noBody()).build());
+        assertNotEquals(builder.build(), newBuilder(uri).PUT(BodyPublishers.noBody()).build());
 
         builder = newBuilder(uri).header("x", "y");
         assertEquals(builder.build(), builder.build());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ResponsePublisher.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,516 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8201186
+ * @summary Tests an asynchronous BodySubscriber that completes
+ *          immediately with a Publisher<List<ByteBuffer>>
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @modules java.base/sun.net.www.http
+ *          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 ResponsePublisher
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+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.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class ResponsePublisher implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final int ITERATION_COUNT = 3;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = Executors.newCachedThreadPool();
+
+    interface BHS extends Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> {
+        static BHS of(BHS impl, String name) {
+            return new BHSImpl(impl, name);
+        }
+    }
+
+    static final class BHSImpl implements BHS {
+        final BHS supplier;
+        final String name;
+        BHSImpl(BHS impl, String name) {
+            this.supplier = impl;
+            this.name = name;
+        }
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        @Override
+        public BodyHandler<Publisher<List<ByteBuffer>>> get() {
+            return supplier.get();
+        }
+    }
+
+    static final Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> OF_PUBLISHER_API =
+            BHS.of(BodyHandlers::ofPublisher, "BodyHandlers::ofPublisher");
+    static final Supplier<BodyHandler<Publisher<List<ByteBuffer>>>> OF_PUBLISHER_TEST =
+            BHS.of(PublishingBodyHandler::new, "PublishingBodyHandler::new");
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][]{
+                { httpURI_fixed,    false, OF_PUBLISHER_API },
+                { httpURI_chunk,    false, OF_PUBLISHER_API },
+                { httpsURI_fixed,   false, OF_PUBLISHER_API },
+                { httpsURI_chunk,   false, OF_PUBLISHER_API },
+                { http2URI_fixed,   false, OF_PUBLISHER_API },
+                { http2URI_chunk,   false, OF_PUBLISHER_API },
+                { https2URI_fixed,  false, OF_PUBLISHER_API },
+                { https2URI_chunk,  false, OF_PUBLISHER_API },
+
+                { httpURI_fixed,    true, OF_PUBLISHER_API },
+                { httpURI_chunk,    true, OF_PUBLISHER_API },
+                { httpsURI_fixed,   true, OF_PUBLISHER_API },
+                { httpsURI_chunk,   true, OF_PUBLISHER_API },
+                { http2URI_fixed,   true, OF_PUBLISHER_API },
+                { http2URI_chunk,   true, OF_PUBLISHER_API },
+                { https2URI_fixed,  true, OF_PUBLISHER_API },
+                { https2URI_chunk,  true, OF_PUBLISHER_API },
+
+                { httpURI_fixed,    false, OF_PUBLISHER_TEST },
+                { httpURI_chunk,    false, OF_PUBLISHER_TEST },
+                { httpsURI_fixed,   false, OF_PUBLISHER_TEST },
+                { httpsURI_chunk,   false, OF_PUBLISHER_TEST },
+                { http2URI_fixed,   false, OF_PUBLISHER_TEST },
+                { http2URI_chunk,   false, OF_PUBLISHER_TEST },
+                { https2URI_fixed,  false, OF_PUBLISHER_TEST },
+                { https2URI_chunk,  false, OF_PUBLISHER_TEST },
+
+                { httpURI_fixed,    true, OF_PUBLISHER_TEST },
+                { httpURI_chunk,    true, OF_PUBLISHER_TEST },
+                { httpsURI_fixed,   true, OF_PUBLISHER_TEST },
+                { httpsURI_chunk,   true, OF_PUBLISHER_TEST },
+                { http2URI_fixed,   true, OF_PUBLISHER_TEST },
+                { http2URI_chunk,   true, OF_PUBLISHER_TEST },
+                { https2URI_fixed,  true, OF_PUBLISHER_TEST },
+                { https2URI_chunk,  true, OF_PUBLISHER_TEST },
+        };
+    }
+
+    final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+    HttpClient newHttpClient() {
+        return TRACKER.track(HttpClient.newBuilder()
+                         .executor(executor)
+                         .sslContext(sslContext)
+                         .build());
+    }
+
+    @Test(dataProvider = "variants")
+    public void testExceptions(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler);
+            try {
+                response.body().subscribe(null);
+                throw new RuntimeException("Expected NPE not thrown");
+            } catch (NullPointerException x) {
+                System.out.println("Got expected NPE: " + x);
+            }
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
+            response.body().subscribe(ofString);
+
+            BodySubscriber<String> ofString2 = BodySubscribers.ofString(UTF_8);
+            response.body().subscribe(ofString2);
+            try {
+                ofString2.getBody().toCompletableFuture().join();
+                throw new RuntimeException("Expected ISE not thrown");
+            } catch (CompletionException x) {
+                Throwable cause = x.getCause();
+                if (cause instanceof  IllegalStateException) {
+                    System.out.println("Got expected ISE: " + cause);
+                } else {
+                    throw x;
+                }
+            }
+            // Get the final result and compare it with the expected body
+            String body = ofString.getBody().toCompletableFuture().get();
+            assertEquals(body, "");
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testNoBody(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler);
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
+            // get the Publisher<List<ByteBuffer>> and
+            // subscribe to it.
+            response.body().subscribe(ofString);
+            // Get the final result and compare it with the expected body
+            String body = ofString.getBody().toCompletableFuture().get();
+            assertEquals(body, "");
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testNoBodyAsync(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
+            CompletableFuture<String> result =
+                    client.sendAsync(req, handler).thenCompose(
+                            (responsePublisher) -> {
+                                // get the Publisher<List<ByteBuffer>> and
+                                // subscribe to it.
+                                responsePublisher.body().subscribe(ofString);
+                                return ofString.getBody();
+                            });
+            // Get the final result and compare it with the expected body
+            assertEquals(result.get(), "");
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsString(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            HttpResponse<Publisher<List<ByteBuffer>>> response = client.send(req, handler);
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
+            // get the Publisher<List<ByteBuffer>> and
+            // subscribe to it.
+            response.body().subscribe(ofString);
+            // Get the final result and compare it with the expected body
+            String body = ofString.getBody().toCompletableFuture().get();
+            assertEquals(body, WITH_BODY);
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testAsStringAsync(String uri, boolean sameClient, BHS handlers) throws Exception {
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri+"/withBody"))
+                    .build();
+            BodyHandler<Publisher<List<ByteBuffer>>> handler = handlers.get();
+            // We can reuse our BodySubscribers implementations to subscribe to the
+            // Publisher<List<ByteBuffer>>
+            BodySubscriber<String> ofString = BodySubscribers.ofString(UTF_8);
+            CompletableFuture<String> result = client.sendAsync(req, handler)
+                    .thenCompose((responsePublisher) -> {
+                        // get the Publisher<List<ByteBuffer>> and
+                        // subscribe to it.
+                        responsePublisher.body().subscribe(ofString);
+                        return ofString.getBody();
+                    });
+            // Get the final result and compare it with the expected body
+            String body = result.get();
+            assertEquals(body, WITH_BODY);
+        }
+    }
+
+    // A BodyHandler that returns PublishingBodySubscriber instances
+    static class PublishingBodyHandler implements BodyHandler<Publisher<List<ByteBuffer>>> {
+        @Override
+        public BodySubscriber<Publisher<List<ByteBuffer>>> apply(HttpResponse.ResponseInfo rinfo) {
+            assertEquals(rinfo.statusCode(), 200);
+            return new PublishingBodySubscriber();
+        }
+    }
+
+    // A BodySubscriber that returns a Publisher<List<ByteBuffer>>
+    static class PublishingBodySubscriber implements BodySubscriber<Publisher<List<ByteBuffer>>> {
+        private final CompletableFuture<Flow.Subscription> subscriptionCF = new CompletableFuture<>();
+        private final CompletableFuture<Flow.Subscriber<? super List<ByteBuffer>>> subscribedCF = new CompletableFuture<>();
+        private AtomicReference<Flow.Subscriber<? super List<ByteBuffer>>> subscriberRef = new AtomicReference<>();
+        private final CompletionStage<Publisher<List<ByteBuffer>>> body =
+                subscriptionCF.thenCompose((s) -> CompletableFuture.completedStage(this::subscribe));
+                //CompletableFuture.completedStage(this::subscribe);
+
+        private void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
+            Objects.requireNonNull(subscriber, "subscriber must not be null");
+            if (subscriberRef.compareAndSet(null, subscriber)) {
+                subscriptionCF.thenAccept((s) -> {
+                    subscriber.onSubscribe(s);
+                    subscribedCF.complete(subscriber);
+                });
+            } else {
+                subscriber.onSubscribe(new Flow.Subscription() {
+                    @Override public void request(long n) { }
+                    @Override public void cancel() { }
+                });
+                subscriber.onError(
+                        new IllegalStateException("This publisher has already one subscriber"));
+            }
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            subscriptionCF.complete(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            assert subscriptionCF.isDone(); // cannot be called before onSubscribe()
+            Flow.Subscriber<? super List<ByteBuffer>> subscriber = subscriberRef.get();
+            assert subscriber != null; // cannot be called before subscriber calls request(1)
+            subscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            assert subscriptionCF.isDone(); // cannot be called before onSubscribe()
+            // onError can be called before request(1), and therefore can
+            // be called before subscriberRef is set.
+            subscribedCF.thenAccept(s -> s.onError(throwable));
+        }
+
+        @Override
+        public void onComplete() {
+            assert subscriptionCF.isDone(); // cannot be called before onSubscribe()
+            // onComplete can be called before request(1), and therefore can
+            // be called before subscriberRef is set.
+            subscribedCF.thenAccept(s -> s.onComplete());
+        }
+
+        @Override
+        public CompletionStage<Publisher<List<ByteBuffer>>> getBody() {
+            return body;
+        }
+    }
+
+    static String serverAuthority(HttpServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h1_chunkHandler = new HTTP_VariableLengthHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler( h1_fixedLengthHandler, "/http1/fixed");
+        httpTestServer.addHandler(h1_chunkHandler,"/http1/chunk");
+        httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed";
+        httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
+        httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
+        httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed";
+        httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk";
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_VariableLengthHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        Thread.sleep(100);
+        AssertionError fail = TRACKER.check(500);
+        try {
+            httpTestServer.stop();
+            httpsTestServer.stop();
+            http2TestServer.stop();
+            https2TestServer.stop();
+        } finally {
+            if (fail != null) {
+                throw fail;
+            }
+        }
+    }
+
+    static final String WITH_BODY = "Lorem ipsum dolor sit amet, consectetur" +
+            " adipiscing elit, sed do eiusmod tempor incididunt ut labore et" +
+            " dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" +
+            " exercitation ullamco laboris nisi ut aliquip ex ea" +
+            " commodo consequat. Duis aute irure dolor in reprehenderit in " +
+            "voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
+            " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui" +
+            " officia deserunt mollit anim id est laborum.";
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            if (t.getRequestURI().getPath().endsWith("/withBody")) {
+                byte[] bytes = WITH_BODY.getBytes(UTF_8);
+                t.sendResponseHeaders(200, bytes.length);  // body
+                try (OutputStream os = t.getResponseBody()) {
+                    os.write(bytes);
+                }
+            } else {
+                t.sendResponseHeaders(200, 0);  //no body
+            }
+        }
+    }
+
+    static class HTTP_VariableLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_VariableLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, -1);  //chunked or variable
+            if (t.getRequestURI().getPath().endsWith("/withBody")) {
+                byte[] bytes = WITH_BODY.getBytes(UTF_8);
+                try (OutputStream os = t.getResponseBody()) {
+                    int chunkLen = bytes.length/10;
+                    if (chunkLen == 0) {
+                        os.write(bytes);
+                    } else {
+                        int count = 0;
+                        for (int i=0; i<10; i++) {
+                            os.write(bytes, count, chunkLen);
+                            os.flush();
+                            count += chunkLen;
+                        }
+                        os.write(bytes, count, bytes.length % chunkLen);
+                        count += bytes.length % chunkLen;
+                        assert count == bytes.length;
+                    }
+                }
+            } else {
+                t.getResponseBody().close();   // no body
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/RetryWithCookie.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8199943
+ * @summary Test for cookie handling when retrying after close
+ * @modules java.base/sun.net.www.http
+ *          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 /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext ReferenceTracker
+ * @run testng/othervm
+ *       -Djdk.httpclient.HttpClient.log=trace,headers,requests
+ *       RetryWithCookie
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.CookieManager;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Redirect;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class RetryWithCookie implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;        // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;       // HTTPS/1.1
+    HttpTestServer http2TestServer;       // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;      // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    static final String MESSAGE = "BasicRedirectTest message body";
+    static final int ITERATIONS = 3;
+
+    @DataProvider(name = "positive")
+    public Object[][] positive() {
+        return new Object[][] {
+                { httpURI,    },
+                { httpsURI,   },
+                { http2URI,   },
+                { https2URI,  },
+        };
+    }
+
+    static final AtomicLong requestCounter = new AtomicLong();
+    final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+
+    @Test(dataProvider = "positive")
+    void test(String uriString) throws Exception {
+        out.printf("%n---- starting (%s) ----%n", uriString);
+        CookieManager cookieManager = new CookieManager();
+        HttpClient client = HttpClient.newBuilder()
+                .followRedirects(Redirect.ALWAYS)
+                .cookieHandler(cookieManager)
+                .sslContext(sslContext)
+                .build();
+        TRACKER.track(client);
+        assert client.cookieHandler().isPresent();
+
+        URI uri = URI.create(uriString);
+        List<String> cookies = new ArrayList<>();
+        cookies.add("CUSTOMER=ARTHUR_DENT");
+        Map<String, List<String>> cookieHeaders = new HashMap<>();
+        cookieHeaders.put("Set-Cookie", cookies);
+        cookieManager.put(uri, cookieHeaders);
+
+        HttpRequest request = HttpRequest.newBuilder(uri)
+                .header("X-uuid", "uuid-" + requestCounter.incrementAndGet())
+                .build();
+        out.println("Initial request: " + request.uri());
+
+        for (int i=0; i< ITERATIONS; i++) {
+            out.println("iteration: " + i);
+            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+
+            out.println("  Got response: " + response);
+            out.println("  Got body Path: " + response.body());
+
+            assertEquals(response.statusCode(), 200);
+            assertEquals(response.body(), MESSAGE);
+            assertEquals(response.headers().allValues("X-Request-Cookie"), cookies);
+            request = HttpRequest.newBuilder(uri)
+                    .header("X-uuid", "uuid-" + requestCounter.incrementAndGet())
+                    .build();
+        }
+    }
+
+    // -- Infrastructure
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(new CookieRetryHandler(), "/http1/cookie/");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/cookie/retry";
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(new CookieRetryHandler(),"/https1/cookie/");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/cookie/retry";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(new CookieRetryHandler(), "/http2/cookie/");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/cookie/retry";
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(new CookieRetryHandler(), "/https2/cookie/");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/cookie/retry";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        Thread.sleep(100);
+        AssertionError fail = TRACKER.check(500);
+        try {
+            httpTestServer.stop();
+            httpsTestServer.stop();
+            http2TestServer.stop();
+            https2TestServer.stop();
+        } finally {
+            if (fail != null) throw fail;
+        }
+    }
+
+    static class CookieRetryHandler implements HttpTestHandler {
+        ConcurrentHashMap<String,String> closedRequests = new ConcurrentHashMap<>();
+
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            System.out.println("CookieRetryHandler for: " + t.getRequestURI());
+
+            List<String> uuids = t.getRequestHeaders().get("X-uuid");
+            if (uuids == null || uuids.size() != 1) {
+                readAllRequestData(t);
+                try (OutputStream os = t.getResponseBody()) {
+                    String msg = "Incorrect uuid header values:[" + uuids + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                }
+                return;
+            }
+
+            String uuid = uuids.get(0);
+            // retrying
+            if (closedRequests.putIfAbsent(uuid, t.getRequestURI().toString()) == null) {
+                if (t.getExchangeVersion() == HttpClient.Version.HTTP_1_1) {
+                    // Throwing an exception here only causes a retry
+                    // with HTTP_1_1 - where it forces the server to close
+                    // the connection.
+                    // For HTTP/2 then throwing an IOE would cause the server
+                    // to close the stream, and throwing anything else would
+                    // cause it to close the connection, but neither would
+                    // cause the client to retry.
+                    // So we simply do not try to retry with HTTP/2 and just verify
+                    // we have received the expected cookie
+                    throw new IOException("Closing on first request");
+                }
+            }
+
+            // not retrying
+            readAllRequestData(t);
+            try (OutputStream os = t.getResponseBody()) {
+                List<String> cookie = t.getRequestHeaders().get("Cookie");
+
+                if (cookie == null || cookie.size() == 0) {
+                    String msg = "No cookie header present";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else if (!cookie.get(0).equals("CUSTOMER=ARTHUR_DENT")) {
+                    String msg = "Incorrect cookie header value:[" + cookie.get(0) + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else if (cookie.size() > 1) {
+                    String msg = "Incorrect cookie header values:[" + cookie + "]";
+                    (new RuntimeException(msg)).printStackTrace();
+                    t.sendResponseHeaders(500, -1);
+                    os.write(msg.getBytes(UTF_8));
+                } else {
+                    assert cookie.get(0).equals("CUSTOMER=ARTHUR_DENT");
+                    byte[] bytes = MESSAGE.getBytes(UTF_8);
+                    for (String value : cookie) {
+                        t.getResponseHeaders().addHeader("X-Request-Cookie", value);
+                    }
+                    t.sendResponseHeaders(200, bytes.length);
+                    os.write(bytes);
+                }
+            }
+
+            closedRequests.remove(uuid);
+        }
+    }
+
+    static void readAllRequestData(HttpTestExchange t) throws IOException {
+        try (InputStream is = t.getRequestBody()) {
+            is.readAllBytes();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ServerCloseTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,410 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests that our client deals correctly with servers that
+ *          close the connection right after sending the last byte.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters EncodedCharsInURI
+ * @modules java.base/sun.net.www.http
+ *          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 ServerCloseTest
+ */
+//*        -Djdk.internal.httpclient.debug=true
+
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublisher;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class ServerCloseTest implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    DummyServer    httpDummyServer;    // HTTP/1.1    [ 2 servers ]
+    DummyServer    httpsDummyServer;   // HTTPS/1.1
+    String httpDummy;
+    String httpsDummy;
+
+    static final int ITERATION_COUNT = 3;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+    static volatile boolean tasksFailed;
+    static final AtomicLong serverCount = new AtomicLong();
+    static final AtomicLong clientCount = new AtomicLong();
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    private volatile HttpClient sharedClient;
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    System.out.printf(now() + "Task %s failed: %s%n", id, t);
+                    System.err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.printf("%n%sCreated %d servers and %d clients%n",
+                    now(), serverCount.get(), clientCount.get());
+            if (FAILURES.isEmpty()) return;
+            out.println("Failed tests: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+            });
+            if (tasksFailed) {
+                System.out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    private String[] uris() {
+        return new String[] {
+                httpDummy,
+                httpsDummy,
+        };
+    }
+
+    @DataProvider(name = "servers")
+    public Object[][] noThrows() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        //Object[][] result = new Object[uris.length][];
+        int i = 0;
+        for (boolean sameClient : List.of(false, true)) {
+            //if (!sameClient) continue;
+            for (String uri: uris()) {
+                result[i++] = new Object[] {uri, sameClient};
+            }
+        }
+        assert i == uris.length * 2;
+        // assert i == uris.length ;
+        return result;
+    }
+
+    private HttpClient makeNewClient() {
+        clientCount.incrementAndGet();
+        return HttpClient.newBuilder()
+                .executor(executor)
+                .sslContext(sslContext)
+                .build();
+    }
+
+    HttpClient newHttpClient(boolean share) {
+        if (!share) return makeNewClient();
+        HttpClient shared = sharedClient;
+        if (shared != null) return shared;
+        synchronized (this) {
+            shared = sharedClient;
+            if (shared == null) {
+                shared = sharedClient = makeNewClient();
+            }
+            return shared;
+        }
+    }
+
+    final String ENCODED = "/01%252F03/";
+
+    @Test(dataProvider = "servers")
+    public void testServerClose(String uri, boolean sameClient) {
+        HttpClient client = null;
+        out.printf("%n%s testServerClose(%s, %b)%n", now(), uri, sameClient);
+        uri = uri + ENCODED;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            BodyPublisher bodyPublisher = BodyPublishers.ofString(uri);
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .POST(bodyPublisher)
+                    .build();
+            BodyHandler<String> handler = BodyHandlers.ofString();
+            HttpClient c = client;
+            CompletableFuture<HttpResponse<String>> responseCF = c.sendAsync(req, handler);
+            // retry POST if needed   #### Replace with exceptionallyCompose
+            responseCF = responseCF.handle((r,t) ->
+                    t == null ? CompletableFuture.completedFuture(r)
+                            : c.sendAsync(req, handler)).thenCompose(x -> x);
+            HttpResponse<String> response = responseCF.join();
+            String body = response.body();
+            if (!uri.contains(body)) {
+                System.err.println("Test failed: " + response);
+                throw new RuntimeException(uri + " doesn't contain '" + body + "'");
+            } else {
+                System.out.println("Found expected " + body + " in " + uri);
+            }
+        }
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        // DummyServer
+        httpDummyServer = DummyServer.create(sa);
+        httpsDummyServer = DummyServer.create(sa, sslContext);
+        httpDummy = "http://" + httpDummyServer.serverAuthority() + "/http1/dummy/x";
+        httpsDummy = "https://" + httpsDummyServer.serverAuthority() + "/https1/dummy/x";
+
+
+        serverCount.addAndGet(2);
+        httpDummyServer.start();
+        httpsDummyServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        sharedClient = null;
+        httpDummyServer.stopServer();
+        httpsDummyServer.stopServer();
+    }
+
+    static class DummyServer extends Thread {
+        final ServerSocket ss;
+        final boolean secure;
+        ConcurrentLinkedQueue<Socket> connections = new ConcurrentLinkedQueue<>();
+        volatile boolean stopped;
+        DummyServer(ServerSocket ss, boolean secure) {
+            super("DummyServer[" + ss.getLocalPort()+"]");
+            this.secure = secure;
+            this.ss = ss;
+        }
+
+        // This is a bit shaky. It doesn't handle continuation
+        // lines, but our client shouldn't send any.
+        // Read a line from the input stream, swallowing the final
+        // \r\n sequence. Stops at the first \n, doesn't complain
+        // if it wasn't preceded by '\r'.
+        //
+        String readLine(InputStream r) throws IOException {
+            StringBuilder b = new StringBuilder();
+            int c;
+            while ((c = r.read()) != -1) {
+                if (c == '\n') break;
+                b.appendCodePoint(c);
+            }
+            if (b.codePointAt(b.length() -1) == '\r') {
+                b.delete(b.length() -1, b.length());
+            }
+            return b.toString();
+        }
+
+        @Override
+        public void run() {
+            try {
+                while(!stopped) {
+                    Socket clientConnection = ss.accept();
+                    connections.add(clientConnection);
+                    System.out.println(now() + getName() + ": Client accepted");
+                    StringBuilder headers = new StringBuilder();
+                    Socket targetConnection = null;
+                    InputStream  ccis = clientConnection.getInputStream();
+                    OutputStream ccos = clientConnection.getOutputStream();
+                    Writer w = new OutputStreamWriter(
+                            clientConnection.getOutputStream(), "UTF-8");
+                    PrintWriter pw = new PrintWriter(w);
+                    System.out.println(now() + getName() + ": Reading request line");
+                    String requestLine = readLine(ccis);
+                    System.out.println(now() + getName() + ": Request line: " + requestLine);
+
+                    StringTokenizer tokenizer = new StringTokenizer(requestLine);
+                    String method = tokenizer.nextToken();
+                    assert method.equalsIgnoreCase("POST")
+                            || method.equalsIgnoreCase("GET");
+                    String path = tokenizer.nextToken();
+                    URI uri;
+                    try {
+                        String hostport = serverAuthority();
+                        uri = new URI((secure ? "https" : "http") +"://" + hostport + path);
+                    } catch (Throwable x) {
+                        System.err.printf("Bad target address: \"%s\" in \"%s\"%n",
+                                path, requestLine);
+                        clientConnection.close();
+                        continue;
+                    }
+
+                    // Read all headers until we find the empty line that
+                    // signals the end of all headers.
+                    String line = requestLine;
+                    while (!line.equals("")) {
+                        System.out.println(now() + getName() + ": Reading header: "
+                                + (line = readLine(ccis)));
+                        headers.append(line).append("\r\n");
+                    }
+
+                    StringBuilder response = new StringBuilder();
+
+                    int index = headers.toString()
+                            .toLowerCase(Locale.US)
+                            .indexOf("content-length: ");
+                    byte[] b = uri.toString().getBytes(UTF_8);
+                    if (index >= 0) {
+                        index = index + "content-length: ".length();
+                        String cl = headers.toString().substring(index);
+                        StringTokenizer tk = new StringTokenizer(cl);
+                        int len = Integer.parseInt(tk.nextToken());
+                        assert len < b.length * 2;
+                        System.out.println(now() + getName()
+                                + ": received body: "
+                                + new String(ccis.readNBytes(len), UTF_8));
+                    }
+                    System.out.println(now()
+                            + getName() + ": sending back " + uri);
+
+                    response.append("HTTP/1.1 200 OK\r\nContent-Length: ")
+                            .append(b.length)
+                            .append("\r\n\r\n");
+
+                    // Then send the 200 OK response to the client
+                    System.out.println(now() + getName() + ": Sending "
+                            + response);
+                    pw.print(response);
+                    pw.flush();
+                    ccos.write(b);
+                    ccos.flush();
+                    ccos.close();
+                    connections.remove(clientConnection);
+                    clientConnection.close();
+                }
+            } catch (Throwable t) {
+                if (!stopped) {
+                    System.out.println(now() + getName() + ": failed: " + t);
+                    t.printStackTrace();
+                    try {
+                        stopServer();
+                    } catch (Throwable e) {
+
+                    }
+                }
+            } finally {
+                System.out.println(now() + getName() + ": exiting");
+            }
+        }
+
+        void close(Socket s) {
+            try {
+                s.close();
+            } catch(Throwable t) {
+
+            }
+        }
+        public void stopServer() throws IOException {
+            stopped = true;
+            ss.close();
+            connections.forEach(this::close);
+        }
+
+        public String serverAuthority() {
+            return InetAddress.getLoopbackAddress().getHostName() + ":"
+                    + ss.getLocalPort();
+        }
+
+        static DummyServer create(InetSocketAddress sa) throws IOException {
+            ServerSocket ss = ServerSocketFactory.getDefault()
+                    .createServerSocket(sa.getPort(), -1, sa.getAddress());
+            return  new DummyServer(ss, false);
+        }
+
+        static DummyServer create(InetSocketAddress sa, SSLContext sslContext) throws IOException {
+            ServerSocket ss = sslContext.getServerSocketFactory()
+                    .createServerSocket(sa.getPort(), -1, sa.getAddress());
+            return new DummyServer(ss, true);
+        }
+
+
+    }
+
+}
--- a/test/jdk/java/net/httpclient/ShortRequestBody.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ShortRequestBody.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,9 +25,17 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpTimeoutException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -40,14 +48,8 @@
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpTimeoutException;
-
 import static java.lang.System.err;
 import static java.nio.charset.StandardCharsets.US_ASCII;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
@@ -102,21 +104,21 @@
 
     static class StringRequestBody extends AbstractDelegateRequestBody {
         StringRequestBody(String body, int additionalLength) {
-            super(HttpRequest.BodyPublisher.fromString(body),
+            super(HttpRequest.BodyPublishers.ofString(body),
                   body.getBytes(UTF_8).length + additionalLength);
         }
     }
 
     static class ByteArrayRequestBody extends AbstractDelegateRequestBody {
         ByteArrayRequestBody(byte[] body, int additionalLength) {
-            super(HttpRequest.BodyPublisher.fromByteArray(body),
+            super(BodyPublishers.ofByteArray(body),
                   body.length + additionalLength);
         }
     }
 
     static class FileRequestBody extends AbstractDelegateRequestBody {
         FileRequestBody(Path path, int additionalLength) throws IOException {
-            super(HttpRequest.BodyPublisher.fromFile(path),
+            super(BodyPublishers.ofFile(path),
                   Files.size(path) + additionalLength);
         }
     }
@@ -132,7 +134,7 @@
         try (Server server = new Server()) {
             for (Supplier<HttpClient> cs : clientSuppliers) {
                 err.println("\n---- next supplier ----\n");
-                URI uri = new URI("http://127.0.0.1:" + server.getPort() + "/");
+                URI uri = new URI("http://localhost:" + server.getPort() + "/");
 
                 // sanity ( 6 requests to keep client and server offsets easy to workout )
                 success(cs, uri, new StringRequestBody(STRING_BODY, 0));
@@ -164,11 +166,12 @@
         HttpRequest request = HttpRequest.newBuilder(uri)
                                          .POST(publisher)
                                          .build();
-        cf = clientSupplier.get().sendAsync(request, discard(null));
+        cf = clientSupplier.get().sendAsync(request, BodyHandlers.discarding());
 
         HttpResponse<Void> resp = cf.get(30, TimeUnit.SECONDS);
         err.println("Response code: " + resp.statusCode());
-        check(resp.statusCode() == 200, "Expected 200, got ", resp.statusCode());
+        check(resp.statusCode() == 200, null,
+                "Expected 200, got ", resp.statusCode());
     }
 
     static void failureNonBlocking(Supplier<HttpClient> clientSupplier,
@@ -180,7 +183,7 @@
         HttpRequest request = HttpRequest.newBuilder(uri)
                                          .POST(publisher)
                                          .build();
-        cf = clientSupplier.get().sendAsync(request, discard(null));
+        cf = clientSupplier.get().sendAsync(request, BodyHandlers.discarding());
 
         try {
             HttpResponse<Void> r = cf.get(30, TimeUnit.SECONDS);
@@ -190,11 +193,11 @@
         } catch (ExecutionException expected) {
             err.println("Caught expected: " + expected);
             Throwable t = expected.getCause();
-            check(t instanceof IOException,
-                  "Expected cause IOException, but got: ", expected.getCause());
+            check(t instanceof IOException, t,
+                  "Expected cause IOException, but got: ", t);
             String msg = t.getMessage();
             check(msg.contains("Too many") || msg.contains("Too few"),
-                    "Expected Too many|Too few, got: ", t);
+                    t, "Expected Too many|Too few, got: ", t);
         }
     }
 
@@ -207,7 +210,8 @@
                                          .POST(publisher)
                                          .build();
         try {
-            HttpResponse<Void> r = clientSupplier.get().send(request, discard(null));
+            HttpResponse<Void> r = clientSupplier.get()
+                    .send(request, BodyHandlers.discarding());
             throw new RuntimeException("Unexpected response: " + r.statusCode());
         } catch (HttpTimeoutException x) {
             throw new RuntimeException("Unexpected timeout", x);
@@ -215,7 +219,7 @@
             err.println("Caught expected: " + expected);
             String msg = expected.getMessage();
             check(msg.contains("Too many") || msg.contains("Too few"),
-                    "Expected Too many|Too few, got: ", expected);
+                    expected,"Expected Too many|Too few, got: ", expected);
         }
     }
 
@@ -230,7 +234,10 @@
 
         Server() throws IOException {
             super("Test-Server");
-            ss = new ServerSocket(0); this.start();
+            ss = new ServerSocket();
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+            this.start();
         }
 
         int getPort() { return ss.getLocalPort(); }
@@ -318,13 +325,13 @@
         catch (IOException x) { throw new UncheckedIOException(x); }
     }
 
-    static boolean check(boolean cond, Object... failedArgs) {
+    static boolean check(boolean cond, Throwable t, Object... failedArgs) {
         if (cond)
             return true;
         // We are going to fail...
         StringBuilder sb = new StringBuilder();
         for (Object o : failedArgs)
                 sb.append(o);
-        throw new RuntimeException(sb.toString());
+        throw new RuntimeException(sb.toString(), t);
     }
 }
--- a/test/jdk/java/net/httpclient/SmallTimeout.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/SmallTimeout.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -21,26 +21,28 @@
  * questions.
  */
 
-import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpTimeoutException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpTimeoutException;
 import java.time.Duration;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import static java.lang.System.out;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 
 /**
  * @test
  * @bug 8178147
+ * @modules java.net.http/jdk.internal.net.http.common
  * @summary Ensures that small timeouts do not cause hangs due to race conditions
- * @run main/othervm -Djdk.incubator.http.internal.common.DEBUG=true SmallTimeout
+ * @run main/othervm -Djdk.internal.httpclient.debug=true SmallTimeout
  */
 
 // To enable logging use. Not enabled by default as it changes the dynamics
@@ -76,10 +78,14 @@
 
     public static void main(String[] args) throws Exception {
         HttpClient client = HttpClient.newHttpClient();
+        ReferenceTracker.INSTANCE.track(client);
 
-        try (ServerSocket ss = new ServerSocket(0, 20)) {
+        Throwable failed = null;
+        try (ServerSocket ss = new ServerSocket()) {
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             int port = ss.getLocalPort();
-            URI uri = new URI("http://127.0.0.1:" + port + "/");
+            URI uri = new URI("http://localhost:" + port + "/");
 
             HttpRequest[] requests = new HttpRequest[TIMEOUTS.length];
 
@@ -92,7 +98,7 @@
 
                 final HttpRequest req = requests[i];
                 CompletableFuture<HttpResponse<Object>> response = client
-                    .sendAsync(req, discard(null))
+                    .sendAsync(req, BodyHandlers.replacing(null))
                     .whenComplete((HttpResponse<Object> r, Throwable t) -> {
                         Throwable cause = null;
                         if (r != null) {
@@ -142,7 +148,7 @@
                 executor.execute(() -> {
                     Throwable cause = null;
                     try {
-                        client.send(req, discard(null));
+                        client.send(req, BodyHandlers.replacing(null));
                     } catch (HttpTimeoutException e) {
                         out.println("Caught expected timeout: " + e);
                     } catch (Throwable ee) {
@@ -164,6 +170,24 @@
             if (error)
                 throw new RuntimeException("Failed. Check output");
 
+        } catch (Throwable t) {
+            failed = t;
+            throw t;
+        } finally {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException t) {
+                // ignore;
+            }
+            AssertionError trackFailed = ReferenceTracker.INSTANCE.check(500);
+            if (trackFailed != null) {
+                if (failed != null) {
+                    failed.addSuppressed(trackFailed);
+                    if (failed instanceof Exception) throw (Exception) failed;
+                    if (failed instanceof Error) throw (Exception) failed;
+                }
+                throw trackFailed;
+            }
         }
     }
 
--- a/test/jdk/java/net/httpclient/SmokeTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/SmokeTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8087112 8178699
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary/ /
@@ -32,7 +32,10 @@
  * @compile ../../../com/sun/net/httpserver/LogFilter.java
  * @compile ../../../com/sun/net/httpserver/EchoHandler.java
  * @compile ../../../com/sun/net/httpserver/FileServerHandler.java
- * @run main/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=errors,trace SmokeTest
+ * @run main/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.httpclient.HttpClient.log=errors,ssl,trace
+ *      SmokeTest
  */
 
 import com.sun.net.httpserver.Headers;
@@ -43,17 +46,22 @@
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsParameters;
 import com.sun.net.httpserver.HttpsServer;
+
+import java.net.InetAddress;
 import java.net.Proxy;
 import java.net.SocketAddress;
+import java.util.Collections;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.net.InetSocketAddress;
 import java.net.PasswordAuthentication;
 import java.net.ProxySelector;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import java.nio.file.StandardOpenOption;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -81,12 +89,9 @@
 import java.util.List;
 import java.util.Random;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromFile;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromInputStream;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.*;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asFile;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Level;
@@ -250,7 +255,7 @@
 
         HttpRequest request = builder.build();
 
-        HttpResponse<String> response = client.send(request, asString());
+        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
 
         String body = response.body();
         if (!body.equals("This is foo.txt\r\n")) {
@@ -261,7 +266,7 @@
         }
 
         // repeat async
-        HttpResponse<String> response1 = client.sendAsync(request, asString())
+        HttpResponse<String> response1 = client.sendAsync(request, BodyHandlers.ofString())
                                                .join();
 
         String body1 = response1.body();
@@ -277,10 +282,10 @@
         URI uri = new URI(s);
 
         HttpRequest request = HttpRequest.newBuilder(uri)
-                                         .POST(fromString(body))
+                                         .POST(BodyPublishers.ofString(body))
                                          .build();
 
-        HttpResponse<String> response = client.send(request, asString());
+        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
 
         if (response.statusCode() != 200) {
             throw new RuntimeException(
@@ -301,16 +306,13 @@
         Path p = getTempFile(128 * 1024);
 
         HttpRequest request = HttpRequest.newBuilder(uri)
-                                         .POST(fromFile(p))
+                                         .POST(BodyPublishers.ofFile(p))
                                          .build();
 
         Path resp = getTempFile(1); // will be overwritten
 
-        HttpResponse<Path> response =
-                client.send(request,
-                            BodyHandler.asFile(resp,
-                                               StandardOpenOption.TRUNCATE_EXISTING,
-                                               StandardOpenOption.WRITE));
+        HttpResponse<Path> response = client.send(request,
+                BodyHandlers.ofFile(resp, TRUNCATE_EXISTING, WRITE));
 
         if (response.statusCode() != 200) {
             throw new RuntimeException(
@@ -340,7 +342,7 @@
                                          .build();
 
         HttpResponse<Path> response = client.send(request,
-                                                  asFile(Paths.get("redir1.txt")));
+                BodyHandlers.ofFile(Paths.get("redir1.txt")));
 
         if (response.statusCode() != 200) {
             throw new RuntimeException(
@@ -361,7 +363,8 @@
 
         request = HttpRequest.newBuilder(uri).build();
 
-        response = client.sendAsync(request, asFile(Paths.get("redir2.txt"))).join();
+        response = client.sendAsync(request,
+                BodyHandlers.ofFile(Paths.get("redir2.txt"))).join();
 
         if (response.statusCode() != 200) {
             throw new RuntimeException(
@@ -449,7 +452,8 @@
     static void test4(String s) throws Exception {
         System.out.print("test4: " + s);
         URI uri = new URI(s);
-        InetSocketAddress proxyAddr = new InetSocketAddress("127.0.0.1", proxyPort);
+        InetSocketAddress proxyAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                                                            proxyPort);
         String filename = fileroot + uri.getPath();
 
         ExecutorService e = Executors.newCachedThreadPool();
@@ -464,7 +468,7 @@
 
         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
 
-        CompletableFuture<String> fut = cl.sendAsync(request, asString())
+        CompletableFuture<String> fut = cl.sendAsync(request, BodyHandlers.ofString())
                 .thenApply((response) -> response.body());
 
         String body = fut.get(5, TimeUnit.HOURS);
@@ -490,7 +494,7 @@
 
         HttpRequest.Builder builder = HttpRequest.newBuilder(uri)
                                             .expectContinue(true)
-                                            .POST(fromString(requestBody));
+                                            .POST(BodyPublishers.ofString(requestBody));
 
         if (fixedLen) {
             builder.header("XFixed", "yes");
@@ -498,7 +502,7 @@
 
         HttpRequest request = builder.build();
 
-        HttpResponse<String> response = client.send(request, asString());
+        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
 
         String body = response.body();
 
@@ -523,7 +527,7 @@
 
         HttpRequest request = builder.build();
 
-        HttpResponse<String> response = client.send(request, asString());
+        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
 
         if (response.statusCode() != 200) {
             throw new RuntimeException(
@@ -548,7 +552,7 @@
         HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build();
 
         for (int i=0; i<4; i++) {
-            HttpResponse<String> r = client.send(request, asString());
+            HttpResponse<String> r = client.send(request, BodyHandlers.ofString());
             String body = r.body();
             if (!body.equals("OK")) {
                 throw new RuntimeException("Expected OK, got: " + body);
@@ -556,10 +560,13 @@
         }
 
         // Second test: 4 x parallel
-        request = HttpRequest.newBuilder().uri(uri).POST(fromFile(requestBody)).build();
+        request = HttpRequest.newBuilder()
+                .uri(uri)
+                .POST(BodyPublishers.ofFile(requestBody))
+                .build();
         List<CompletableFuture<String>> futures = new LinkedList<>();
         for (int i=0; i<4; i++) {
-            futures.add(client.sendAsync(request, asString())
+            futures.add(client.sendAsync(request, BodyHandlers.ofString())
                               .thenApply((response) -> {
                                   if (response.statusCode() == 200)
                                       return response.body();
@@ -582,7 +589,7 @@
         request = HttpRequest.newBuilder(uri).GET().build();
         BlockingQueue<String> q = new LinkedBlockingQueue<>();
         for (int i=0; i<4; i++) {
-            client.sendAsync(request, asString())
+            client.sendAsync(request, BodyHandlers.ofString())
                   .thenApply((HttpResponse<String> resp) -> {
                       String body = resp.body();
                       putQ(q, body);
@@ -599,7 +606,7 @@
             if (!body.equals("OK")) {
                 throw new RuntimeException(body);
             }
-            client.sendAsync(request, asString())
+            client.sendAsync(request, BodyHandlers.ofString())
                   .thenApply((resp) -> {
                       if (resp.statusCode() == 200)
                           putQ(q, resp.body());
@@ -645,12 +652,12 @@
         URI uri = new URI(target);
 
         HttpRequest request = HttpRequest.newBuilder(uri)
-                                         .POST(fromInputStream(SmokeTest::newStream))
-                                         .build();
+                .POST(BodyPublishers.ofInputStream(SmokeTest::newStream))
+                .build();
 
         Path download = Paths.get("test11.txt");
 
-        HttpResponse<Path> response = client.send(request, asFile(download));
+        HttpResponse<Path> response = client.send(request, BodyHandlers.ofFile(download));
 
         if (response.statusCode() != 200) {
             throw new RuntimeException("Wrong response code");
@@ -683,7 +690,7 @@
 
         HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
         CompletableFuture<HttpResponse<String>> cf =
-                client.sendAsync(request, asString());
+                client.sendAsync(request, BodyHandlers.ofString());
 
         try {
             HttpResponse<String> response = cf.join();
@@ -722,7 +729,7 @@
         logger.addHandler(ch);
 
         String root = System.getProperty ("test.src", ".")+ "/docs";
-        InetSocketAddress addr = new InetSocketAddress (0);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         s1 = HttpServer.create (addr, 0);
         if (s1 instanceof HttpsServer) {
             throw new RuntimeException ("should not be httpsserver");
@@ -751,7 +758,8 @@
         s1.setExecutor(executor);
         s2.setExecutor(executor);
         ctx = new SimpleSSLContext().get();
-        sslparams = ctx.getSupportedSSLParameters();
+        sslparams = ctx.getDefaultSSLParameters();
+        //sslparams.setProtocols(new String[]{"TLSv1.2"});
         s2.setHttpsConfigurator(new Configurator(ctx));
         s1.start();
         s2.start();
@@ -760,8 +768,8 @@
         System.out.println("HTTP server port = " + port);
         httpsport = s2.getAddress().getPort();
         System.out.println("HTTPS server port = " + httpsport);
-        httproot = "http://127.0.0.1:" + port + "/";
-        httpsroot = "https://127.0.0.1:" + httpsport + "/";
+        httproot = "http://localhost:" + port + "/";
+        httpsroot = "https://localhost:" + httpsport + "/";
 
         proxy = new ProxyServer(0, false);
         proxyPort = proxy.getPort();
@@ -872,12 +880,16 @@
         }
 
         public void configure (HttpsParameters params) {
-            params.setSSLParameters (getSSLContext().getSupportedSSLParameters());
+            SSLParameters p = getSSLContext().getDefaultSSLParameters();
+            //p.setProtocols(new String[]{"TLSv1.2"});
+            params.setSSLParameters (p);
         }
     }
 
+    static final Path CWD = Paths.get(".");
+
     static Path getTempFile(int size) throws IOException {
-        File f = File.createTempFile("test", "txt");
+        File f = Files.createTempFile(CWD, "test", "txt").toFile();
         f.deleteOnExit();
         byte[] buf = new byte[2048];
         for (int i = 0; i < buf.length; i++)
@@ -901,12 +913,12 @@
 // Then send 4 requests in parallel x 100 times (same four addresses used all time)
 
 class KeepAliveHandler implements HttpHandler {
-    AtomicInteger counter = new AtomicInteger(0);
-    AtomicInteger nparallel = new AtomicInteger(0);
+    final AtomicInteger counter = new AtomicInteger(0);
+    final AtomicInteger nparallel = new AtomicInteger(0);
 
-    HashSet<Integer> portSet = new HashSet<>();
+    final Set<Integer> portSet = Collections.synchronizedSet(new HashSet<>());
 
-    int[] ports = new int[8];
+    final int[] ports = new int[8];
 
     void sleep(int n) {
         try {
@@ -929,7 +941,9 @@
         dest[3] = ports[from+3];
     }
 
-    static CountDownLatch latch = new CountDownLatch(4);
+    static final CountDownLatch latch = new CountDownLatch(4);
+    static final CountDownLatch latch7 = new CountDownLatch(4);
+    static final CountDownLatch latch8 = new CountDownLatch(1);
 
     @Override
     public void handle (HttpExchange t)
@@ -962,8 +976,11 @@
             setPort(n, remotePort);
             latch.countDown();
             try {latch.await();} catch (InterruptedException e) {}
+            latch7.countDown();
         }
         if (n == 7) {
+            // wait until all n <= 7 have called setPort(...)
+            try {latch7.await();} catch (InterruptedException e) {}
             getPorts(lports, 4);
             // should be all different
             if (lports[0] == lports[1] || lports[2] == lports[3]
@@ -976,15 +993,19 @@
                 portSet.add(lports[i]);
             }
             System.out.printf("Ports: %d, %d, %d, %d\n", lports[0], lports[1], lports[2], lports[3]);
+            latch8.countDown();
         }
         // Third test
         if (n > 7) {
+            // wait until all n == 7 has updated portSet
+            try {latch8.await();} catch (InterruptedException e) {}
             if (np > 4) {
                 System.err.println("XXX np = " + np);
             }
             // just check that port is one of the ones in portSet
             if (!portSet.contains(remotePort)) {
-                System.out.println ("UNEXPECTED REMOTE PORT " + remotePort);
+                System.out.println ("UNEXPECTED REMOTE PORT "
+                        + remotePort + " not in " + portSet);
                 result = "Error " + Integer.toString(n);
                 System.out.println(result);
             }
--- a/test/jdk/java/net/httpclient/SplitResponse.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/SplitResponse.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,19 +22,21 @@
  */
 
 import java.io.IOException;
+import java.net.SocketException;
 import java.net.URI;
 import java.util.concurrent.CompletableFuture;
 import javax.net.ssl.SSLContext;
 import javax.net.ServerSocketFactory;
 import javax.net.ssl.SSLServerSocketFactory;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpClient.Version;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import jdk.testlibrary.SimpleSSLContext;
 import static java.lang.System.out;
 import static java.lang.String.format;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.net.http.HttpResponse.BodyHandlers.ofString;
 
 /**
  * @test
@@ -60,7 +62,9 @@
         if (!serverKeepalive)
             sb.append("Connection: Close\r\n");
 
-        sb.append("Content-length: ").append(body.length()).append("\r\n");
+        sb.append("Content-length: ")
+                .append(body.getBytes(ISO_8859_1).length)
+                .append("\r\n");
         sb.append("\r\n");
         sb.append(body);
         return sb.toString();
@@ -146,7 +150,7 @@
         //                        .newBuilder(uri2).version(version).build();
         //                    while (true) {
         //                        try {
-        //                            client.send(request, HttpResponse.BodyHandler.asString());
+        //                            client.send(request, HttpResponse.BodyHandlers.ofString());
         //                        } catch (IOException ex) {
         //                            System.out.println("Client rejected " + request);
         //                        }
@@ -173,18 +177,20 @@
 
                 if (async) {
                     out.println("send async: " + request);
-                    cf1 = client.sendAsync(request, asString());
+                    cf1 = client.sendAsync(request, ofString());
                     r = cf1.get();
                 } else { // sync
                     out.println("send sync: " + request);
-                    r = client.send(request, asString());
+                    r = client.send(request, ofString());
                 }
 
+                out.println("response " + r);
+                String rxbody = r.body();
+                out.println("response body:[" + rxbody + "]");
+
                 if (r.statusCode() != 200)
-                    throw new RuntimeException("Failed");
+                    throw new RuntimeException("Expected 200, got:" + r.statusCode());
 
-                String rxbody = r.body();
-                out.println("received " + rxbody);
                 if (!rxbody.equals(body))
                     throw new RuntimeException(format("Expected:%s, got:%s", body, rxbody));
 
@@ -203,21 +209,37 @@
     // Sends the response, mostly, one byte at a time with a small delay
     // between bytes, to encourage that each byte is read in a separate read
     Thread sendSplitResponse(String s, MockServer server) {
-        System.out.println("Sending: ");
+        System.out.println("Server: creating new thread to send ... ");
         Thread t = new Thread(() -> {
-            System.out.println("Waiting for server to receive headers");
+            System.out.println("Server: waiting for server to receive headers");
             conn = server.activity();
-            System.out.println("Start sending response");
+            System.out.println("Server: Start sending response");
 
             try {
                 int len = s.length();
-                out.println("sending " + s);
+                out.println("Server: going to send [" + s + "]");
                 for (int i = 0; i < len; i++) {
                     String onechar = s.substring(i, i + 1);
-                    conn.send(onechar);
+                    try {
+                        conn.send(onechar);
+                    } catch(SocketException x) {
+                        if (!useSSL || i != len - 1) throw x;
+                        if (x.getMessage().contains("closed by remote host")) {
+                            String osname = System.getProperty("os.name", "unknown");
+                            // On Solaris we can receive an exception when
+                            // the client closes the connection after receiving
+                            // the last expected char.
+                            if (osname.contains("SunO")) {
+                                System.out.println(osname + " detected");
+                                System.out.println("WARNING: ignoring " + x);
+                                System.err.println(osname + " detected");
+                                System.err.println("WARNING: ignoring " + x);
+                            }
+                        }
+                    }
                     Thread.sleep(10);
                 }
-                out.println("sent " + s);
+                out.println("Server: sent [" + s + "]");
             } catch (IOException | InterruptedException e) {
                 throw new RuntimeException(e);
             }
--- a/test/jdk/java/net/httpclient/SplitResponseSSL.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/SplitResponseSSL.java	Tue Apr 17 08:54:17 2018 -0700
@@ -27,7 +27,10 @@
  * @library /lib/testlibrary
  * @build jdk.testlibrary.SimpleSSLContext
  * @build MockServer SplitResponse
- * @run main/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=all SplitResponseSSL SSL
+ * @run main/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.httpclient.HttpClient.log=all
+ *      SplitResponseSSL SSL
  */
 public class SplitResponseSSL {
     public static void main(String[] args) throws Exception {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/StreamingBody.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Exercise a streaming subscriber ( InputStream ) without holding a
+ *          strong (or any ) reference to the client.
+ * @modules java.base/sun.net.www.http
+ *          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 /test/lib http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm
+ *       -Djdk.httpclient.HttpClient.log=trace,headers,requests
+ *       StreamingBody
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import javax.net.ssl.SSLContext;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static org.testng.Assert.assertEquals;
+
+public class StreamingBody implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;        // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;       // HTTPS/1.1
+    HttpTestServer http2TestServer;       // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;      // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    static final String MESSAGE = "StreamingBody message body";
+    static final int ITERATIONS = 100;
+
+    @DataProvider(name = "positive")
+    public Object[][] positive() {
+        return new Object[][] {
+                { httpURI,    },
+                { httpsURI,   },
+                { http2URI,   },
+                { https2URI,  },
+        };
+    }
+
+    @Test(dataProvider = "positive")
+    void test(String uriString) throws Exception {
+        out.printf("%n---- starting (%s) ----%n", uriString);
+        URI uri = URI.create(uriString);
+        HttpRequest request = HttpRequest.newBuilder(uri).build();
+
+        for (int i=0; i< ITERATIONS; i++) {
+            out.println("iteration: " + i);
+            HttpResponse<InputStream> response = HttpClient.newBuilder()
+                    .sslContext(sslContext)
+                    .proxy(NO_PROXY)
+                    .build()
+                    .sendAsync(request, BodyHandlers.ofInputStream())
+                    .join();
+
+            String body = new String(response.body().readAllBytes(), UTF_8);
+            out.println("Got response: " + response);
+            out.println("Got body Path: " + body);
+
+            assertEquals(response.statusCode(), 200);
+            assertEquals(body, MESSAGE);
+        }
+    }
+
+
+    // -- Infrastructure
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(new MessageHandler(), "/http1/streamingbody/");
+        httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/streamingbody/w";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(new MessageHandler(),"/https1/streamingbody/");
+        httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/streamingbody/x";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(new MessageHandler(), "/http2/streamingbody/");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/streamingbody/y";
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(new MessageHandler(), "/https2/streamingbody/");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/streamingbody/z";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    static class MessageHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            System.out.println("MessageHandler for: " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = MESSAGE.getBytes(UTF_8);
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -30,15 +30,21 @@
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Flow;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpRequest.BodyPublisher;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.ResponseInfo;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.util.function.Function;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
 import static java.nio.file.StandardOpenOption.WRITE;
+import static java.nio.file.StandardOpenOption.READ;
 import static org.testng.Assert.assertThrows;
 
 /*
@@ -56,20 +62,20 @@
 
     @Test
     public void publisherAPIExceptions() {
-        assertThrows(NPE, () -> BodyPublisher.fromByteArray(null));
-        assertThrows(NPE, () -> BodyPublisher.fromByteArray(null, 0, 1));
-        assertThrows(IOB, () -> BodyPublisher.fromByteArray(new byte[100],    0, 101));
-        assertThrows(IOB, () -> BodyPublisher.fromByteArray(new byte[100],    1, 100));
-        assertThrows(IOB, () -> BodyPublisher.fromByteArray(new byte[100],   -1,  10));
-        assertThrows(IOB, () -> BodyPublisher.fromByteArray(new byte[100],   99,   2));
-        assertThrows(IOB, () -> BodyPublisher.fromByteArray(new byte[1],   -100,   1));
-        assertThrows(NPE, () -> BodyPublisher.fromByteArrays(null));
-        assertThrows(NPE, () -> BodyPublisher.fromFile(null));
-        assertThrows(NPE, () -> BodyPublisher.fromInputStream(null));
-        assertThrows(NPE, () -> BodyPublisher.fromString(null));
-        assertThrows(NPE, () -> BodyPublisher.fromString("A", null));
-        assertThrows(NPE, () -> BodyPublisher.fromString(null, UTF_8));
-        assertThrows(NPE, () -> BodyPublisher.fromString(null, null));
+        assertThrows(NPE, () -> BodyPublishers.ofByteArray(null));
+        assertThrows(NPE, () -> BodyPublishers.ofByteArray(null, 0, 1));
+        assertThrows(IOB, () -> BodyPublishers.ofByteArray(new byte[100],    0, 101));
+        assertThrows(IOB, () -> BodyPublishers.ofByteArray(new byte[100],    1, 100));
+        assertThrows(IOB, () -> BodyPublishers.ofByteArray(new byte[100],   -1,  10));
+        assertThrows(IOB, () -> BodyPublishers.ofByteArray(new byte[100],   99,   2));
+        assertThrows(IOB, () -> BodyPublishers.ofByteArray(new byte[1],   -100,   1));
+        assertThrows(NPE, () -> BodyPublishers.ofByteArray(null));
+        assertThrows(NPE, () -> BodyPublishers.ofFile(null));
+        assertThrows(NPE, () -> BodyPublishers.ofInputStream(null));
+        assertThrows(NPE, () -> BodyPublishers.ofString(null));
+        assertThrows(NPE, () -> BodyPublishers.ofString("A", null));
+        assertThrows(NPE, () -> BodyPublishers.ofString(null, UTF_8));
+        assertThrows(NPE, () -> BodyPublishers.ofString(null, null));
     }
 
     @DataProvider(name = "nonExistentFiles")
@@ -87,53 +93,74 @@
 
     @Test(dataProvider = "nonExistentFiles", expectedExceptions = FileNotFoundException.class)
     public void fromFileCheck(Path path) throws Exception {
-        BodyPublisher.fromFile(path);
+        BodyPublishers.ofFile(path);
     }
 
     @Test
-    public void handlerAPIExceptions() {
+    public void handlerAPIExceptions() throws Exception {
         Path path = Paths.get(".").resolve("tt");
-        assertThrows(NPE, () -> BodyHandler.asByteArrayConsumer(null));
-        assertThrows(NPE, () -> BodyHandler.asFile(null));
-        assertThrows(NPE, () -> BodyHandler.asFile(null, CREATE, WRITE));
-        assertThrows(NPE, () -> BodyHandler.asFile(path, (OpenOption[])null));
-        assertThrows(NPE, () -> BodyHandler.asFile(path, new OpenOption[] {null}));
-        assertThrows(NPE, () -> BodyHandler.asFile(path, new OpenOption[] {CREATE, null}));
-        assertThrows(NPE, () -> BodyHandler.asFile(path, new OpenOption[] {null, CREATE}));
-        assertThrows(NPE, () -> BodyHandler.asFile(null, (OpenOption[])null));
-        assertThrows(NPE, () -> BodyHandler.asFileDownload(null, CREATE, WRITE));
-        assertThrows(NPE, () -> BodyHandler.asFileDownload(path, (OpenOption[])null));
-        assertThrows(NPE, () -> BodyHandler.asFileDownload(path, new OpenOption[] {null}));
-        assertThrows(NPE, () -> BodyHandler.asFileDownload(path, new OpenOption[] {CREATE, null}));
-        assertThrows(NPE, () -> BodyHandler.asFileDownload(path, new OpenOption[] {null, CREATE}));
-        assertThrows(NPE, () -> BodyHandler.asFileDownload(null, (OpenOption[])null));
-        assertThrows(NPE, () -> BodyHandler.asString(null));
-        assertThrows(NPE, () -> BodyHandler.buffering(null, 1));
-        assertThrows(IAE, () -> BodyHandler.buffering(new NoOpHandler(), 0));
-        assertThrows(IAE, () -> BodyHandler.buffering(new NoOpHandler(), -1));
-        assertThrows(IAE, () -> BodyHandler.buffering(new NoOpHandler(), Integer.MIN_VALUE));
+        Path file = Files.createFile(Paths.get(".").resolve("aFile"));
+        Path doesNotExist = Paths.get(".").resolve("doneNotExist");
+        if (Files.exists(doesNotExist))
+            throw new AssertionError("Unexpected " + doesNotExist);
+
+        assertThrows(NPE, () -> BodyHandlers.ofByteArrayConsumer(null));
+        assertThrows(NPE, () -> BodyHandlers.ofFile(null));
+        assertThrows(NPE, () -> BodyHandlers.ofFile(null, CREATE, WRITE));
+        assertThrows(NPE, () -> BodyHandlers.ofFile(path, (OpenOption[])null));
+        assertThrows(NPE, () -> BodyHandlers.ofFile(path, new OpenOption[] {null}));
+        assertThrows(NPE, () -> BodyHandlers.ofFile(path, new OpenOption[] {CREATE, null}));
+        assertThrows(NPE, () -> BodyHandlers.ofFile(path, new OpenOption[] {null, CREATE}));
+        assertThrows(NPE, () -> BodyHandlers.ofFile(null, (OpenOption[])null));
+        assertThrows(NPE, () -> BodyHandlers.ofFileDownload(null, CREATE, WRITE));
+        assertThrows(NPE, () -> BodyHandlers.ofFileDownload(path, (OpenOption[])null));
+        assertThrows(NPE, () -> BodyHandlers.ofFileDownload(path, new OpenOption[] {null}));
+        assertThrows(NPE, () -> BodyHandlers.ofFileDownload(path, new OpenOption[] {CREATE, null}));
+        assertThrows(NPE, () -> BodyHandlers.ofFileDownload(path, new OpenOption[] {null, CREATE}));
+        assertThrows(NPE, () -> BodyHandlers.ofFileDownload(null, (OpenOption[])null));
+        assertThrows(IAE, () -> BodyHandlers.ofFileDownload(file, CREATE, WRITE));
+        assertThrows(IAE, () -> BodyHandlers.ofFileDownload(doesNotExist, CREATE, WRITE));
+        assertThrows(NPE, () -> BodyHandlers.ofString(null));
+        assertThrows(NPE, () -> BodyHandlers.buffering(null, 1));
+        assertThrows(IAE, () -> BodyHandlers.buffering(new NoOpHandler(), 0));
+        assertThrows(IAE, () -> BodyHandlers.buffering(new NoOpHandler(), -1));
+        assertThrows(IAE, () -> BodyHandlers.buffering(new NoOpHandler(), Integer.MIN_VALUE));
+
+        // implementation specific exceptions
+        assertThrows(IAE, () -> BodyHandlers.ofFile(path, READ));
+        assertThrows(IAE, () -> BodyHandlers.ofFile(path, DELETE_ON_CLOSE));
+        assertThrows(IAE, () -> BodyHandlers.ofFile(path, READ, DELETE_ON_CLOSE));
+        assertThrows(IAE, () -> BodyHandlers.ofFileDownload(path, DELETE_ON_CLOSE));
     }
 
     @Test
     public void subscriberAPIExceptions() {
         Path path = Paths.get(".").resolve("tt");
-        assertThrows(NPE, () -> BodySubscriber.asByteArrayConsumer(null));
-        assertThrows(NPE, () -> BodySubscriber.asFile(null));
-        assertThrows(NPE, () -> BodySubscriber.asFile(null, CREATE, WRITE));
-        assertThrows(NPE, () -> BodySubscriber.asFile(path, (OpenOption[])null));
-        assertThrows(NPE, () -> BodySubscriber.asFile(path, new OpenOption[] {null}));
-        assertThrows(NPE, () -> BodySubscriber.asFile(path, new OpenOption[] {CREATE, null}));
-        assertThrows(NPE, () -> BodySubscriber.asFile(path, new OpenOption[] {null, CREATE}));
-        assertThrows(NPE, () -> BodySubscriber.asFile(null, (OpenOption[])null));
-        assertThrows(NPE, () -> BodySubscriber.asString(null));
-        assertThrows(NPE, () -> BodySubscriber.buffering(null, 1));
-        assertThrows(IAE, () -> BodySubscriber.buffering(new NoOpSubscriber(), 0));
-        assertThrows(IAE, () -> BodySubscriber.buffering(new NoOpSubscriber(), -1));
-        assertThrows(IAE, () -> BodySubscriber.buffering(new NoOpSubscriber(), Integer.MIN_VALUE));
+        assertThrows(NPE, () -> BodySubscribers.ofByteArrayConsumer(null));
+        assertThrows(NPE, () -> BodySubscribers.ofFile(null));
+        assertThrows(NPE, () -> BodySubscribers.ofFile(null, CREATE, WRITE));
+        assertThrows(NPE, () -> BodySubscribers.ofFile(path, (OpenOption[])null));
+        assertThrows(NPE, () -> BodySubscribers.ofFile(path, new OpenOption[] {null}));
+        assertThrows(NPE, () -> BodySubscribers.ofFile(path, new OpenOption[] {CREATE, null}));
+        assertThrows(NPE, () -> BodySubscribers.ofFile(path, new OpenOption[] {null, CREATE}));
+        assertThrows(NPE, () -> BodySubscribers.ofFile(null, (OpenOption[])null));
+        assertThrows(NPE, () -> BodySubscribers.ofString(null));
+        assertThrows(NPE, () -> BodySubscribers.buffering(null, 1));
+        assertThrows(IAE, () -> BodySubscribers.buffering(new NoOpSubscriber(), 0));
+        assertThrows(IAE, () -> BodySubscribers.buffering(new NoOpSubscriber(), -1));
+        assertThrows(IAE, () -> BodySubscribers.buffering(new NoOpSubscriber(), Integer.MIN_VALUE));
+        assertThrows(NPE, () -> BodySubscribers.mapping(null, Function.identity()));
+        assertThrows(NPE, () -> BodySubscribers.mapping(BodySubscribers.ofByteArray(), null));
+        assertThrows(NPE, () -> BodySubscribers.mapping(null, null));
+
+        // implementation specific exceptions
+        assertThrows(IAE, () -> BodySubscribers.ofFile(path, READ));
+        assertThrows(IAE, () -> BodySubscribers.ofFile(path, DELETE_ON_CLOSE));
+        assertThrows(IAE, () -> BodySubscribers.ofFile(path, READ, DELETE_ON_CLOSE));
     }
 
     static class NoOpHandler implements BodyHandler<Void> {
-        @Override public BodySubscriber<Void> apply(int code, HttpHeaders hrds) { return null; }
+        @Override public BodySubscriber<Void> apply(ResponseInfo rinfo) { return null; }
     }
 
     static class NoOpSubscriber implements BodySubscriber<Void> {
--- a/test/jdk/java/net/httpclient/TEST.properties	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/TEST.properties	Tue Apr 17 08:54:17 2018 -0700
@@ -1,1 +1,1 @@
-modules = jdk.incubator.httpclient
+modules = java.net.http
--- a/test/jdk/java/net/httpclient/TestKit.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/TestKit.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
--- a/test/jdk/java/net/httpclient/TestKitTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/TestKitTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ThrowingPublishers.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,685 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests what happens when request publishers
+ *          throw unexpected exceptions.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters
+ *        ReferenceTracker ThrowingPublishers
+ * @modules java.base/sun.net.www.http
+ *          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.enableAllMethodRetry=true
+ *                     ThrowingPublishers
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublisher;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.SubmissionPublisher;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.String.format;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class ThrowingPublishers implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final int ITERATION_COUNT = 1;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+    static volatile boolean tasksFailed;
+    static final AtomicLong serverCount = new AtomicLong();
+    static final AtomicLong clientCount = new AtomicLong();
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+    private volatile HttpClient sharedClient;
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    System.out.printf(now() + "Task %s failed: %s%n", id, t);
+                    System.err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.printf("%n%sCreated %d servers and %d clients%n",
+                    now(), serverCount.get(), clientCount.get());
+            if (FAILURES.isEmpty()) return;
+            out.println("Failed tests: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+            });
+            if (tasksFailed) {
+                System.out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    private String[] uris() {
+        return new String[] {
+                httpURI_fixed,
+                httpURI_chunk,
+                httpsURI_fixed,
+                httpsURI_chunk,
+                http2URI_fixed,
+                http2URI_chunk,
+                https2URI_fixed,
+                https2URI_chunk,
+        };
+    }
+
+    @DataProvider(name = "noThrows")
+    public Object[][] noThrows() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        //Object[][] result = new Object[uris.length][];
+        int i = 0;
+        for (boolean sameClient : List.of(false, true)) {
+            //if (!sameClient) continue;
+            for (String uri: uris()) {
+                result[i++] = new Object[] {uri + "/noThrows", sameClient};
+            }
+        }
+        assert i == uris.length * 2;
+        // assert i == uris.length ;
+        return result;
+    }
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2 * 2][];
+        //Object[][] result = new Object[(uris.length/2) * 2 * 2][];
+        int i = 0;
+        for (Thrower thrower : List.of(
+                new UncheckedIOExceptionThrower(),
+                new UncheckedCustomExceptionThrower())) {
+            for (boolean sameClient : List.of(false, true)) {
+                for (String uri : uris()) {
+                    // if (uri.contains("http2") || uri.contains("https2")) continue;
+                    // if (!sameClient) continue;
+                    result[i++] = new Object[]{uri, sameClient, thrower};
+                }
+            }
+        }
+        assert i == uris.length * 2 * 2;
+        //assert Stream.of(result).filter(o -> o != null).count() == result.length;
+        return result;
+    }
+
+    private HttpClient makeNewClient() {
+        clientCount.incrementAndGet();
+        return TRACKER.track(HttpClient.newBuilder()
+                .proxy(HttpClient.Builder.NO_PROXY)
+                .executor(executor)
+                .sslContext(sslContext)
+                .build());
+    }
+
+    HttpClient newHttpClient(boolean share) {
+        if (!share) return makeNewClient();
+        HttpClient shared = sharedClient;
+        if (shared != null) return shared;
+        synchronized (this) {
+            shared = sharedClient;
+            if (shared == null) {
+                shared = sharedClient = makeNewClient();
+            }
+            return shared;
+        }
+    }
+
+    final String BODY = "Some string | that ? can | be split ? several | ways.";
+
+    @Test(dataProvider = "noThrows")
+    public void testNoThrows(String uri, boolean sameClient)
+            throws Exception {
+        HttpClient client = null;
+        out.printf("%n%s testNoThrows(%s, %b)%n", now(), uri, sameClient);
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            SubmissionPublisher<ByteBuffer> publisher
+                    = new SubmissionPublisher<>(executor,10);
+            ThrowingBodyPublisher bodyPublisher = new ThrowingBodyPublisher((w) -> {},
+                    BodyPublishers.fromPublisher(publisher));
+            CompletableFuture<Void> subscribedCF = bodyPublisher.subscribedCF();
+            subscribedCF.whenComplete((r,t) -> System.out.println(now() + " subscribe completed " + t))
+                    .thenAcceptAsync((v) -> {
+                                Stream.of(BODY.split("\\|"))
+                                        .forEachOrdered(s -> {
+                                                System.out.println("submitting \"" + s +"\"");
+                                                publisher.submit(ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8)));
+                                        });
+                                System.out.println("publishing done");
+                                publisher.close();
+                            },
+                    executor);
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .POST(bodyPublisher)
+                    .build();
+            BodyHandler<String> handler = BodyHandlers.ofString();
+            CompletableFuture<HttpResponse<String>> response = client.sendAsync(req, handler);
+
+            String body = response.join().body();
+            assertEquals(body, Stream.of(BODY.split("\\|")).collect(Collectors.joining()));
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsString(String uri,
+                                     boolean sameClient,
+                                     Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsString(%s, %b, %s)",
+                             uri, sameClient, thrower);
+        List<byte[]> bytes = Stream.of(BODY.split("|"))
+                .map(s -> s.getBytes(UTF_8))
+                .collect(Collectors.toList());
+        testThrowing(test, uri, sameClient, () -> BodyPublishers.ofByteArrays(bytes),
+                this::shouldNotThrowInCancel, thrower,false);
+    }
+
+    private <T,U> void testThrowing(String name, String uri, boolean sameClient,
+                                    Supplier<BodyPublisher> publishers,
+                                    Finisher finisher, Thrower thrower, boolean async)
+            throws Exception
+    {
+        out.printf("%n%s%s%n", now(), name);
+        try {
+            testThrowing(uri, sameClient, publishers, finisher, thrower, async);
+        } catch (Error | Exception x) {
+            FAILURES.putIfAbsent(name, x);
+            throw x;
+        }
+    }
+
+    private void testThrowing(String uri, boolean sameClient,
+                                    Supplier<BodyPublisher> publishers,
+                                    Finisher finisher, Thrower thrower,
+                                    boolean async)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (Where where : whereValues()) {
+            //if (where == Where.ON_SUBSCRIBE) continue;
+            //if (where == Where.ON_ERROR) continue;
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            ThrowingBodyPublisher bodyPublisher =
+                    new ThrowingBodyPublisher(where.select(thrower), publishers.get());
+            HttpRequest req = HttpRequest.
+                    newBuilder(URI.create(uri))
+                    .header("X-expect-exception", "true")
+                    .POST(bodyPublisher)
+                    .build();
+            BodyHandler<String> handler = BodyHandlers.ofString();
+            System.out.println("try throwing in " + where);
+            HttpResponse<String> response = null;
+            if (async) {
+                try {
+                    response = client.sendAsync(req, handler).join();
+                } catch (Error | Exception x) {
+                    Throwable cause = findCause(where, x, thrower);
+                    if (cause == null) throw causeNotFound(where, x);
+                    System.out.println(now() + "Got expected exception: " + cause);
+                }
+            } else {
+                try {
+                    response = client.send(req, handler);
+                } catch (Error | Exception t) {
+                    if (thrower.test(where, t)) {
+                        System.out.println(now() + "Got expected exception: " + t);
+                    } else throw causeNotFound(where, t);
+                }
+            }
+            if (response != null) {
+                finisher.finish(where, response, thrower);
+            }
+        }
+    }
+
+    enum Where {
+        BEFORE_SUBSCRIBE, BEFORE_REQUEST, BEFORE_NEXT_REQUEST, BEFORE_CANCEL,
+        AFTER_SUBSCRIBE, AFTER_REQUEST, AFTER_NEXT_REQUEST, AFTER_CANCEL;
+        public Consumer<Where> select(Consumer<Where> consumer) {
+            return new Consumer<Where>() {
+                @Override
+                public void accept(Where where) {
+                    if (Where.this == where) {
+                        consumer.accept(where);
+                    }
+                }
+            };
+        }
+    }
+
+    // can be used to reduce the surface of the test when diagnosing
+    // some failure
+    Set<Where> whereValues() {
+        //return EnumSet.of(Where.BEFORE_CANCEL, Where.AFTER_CANCEL);
+        return EnumSet.allOf(Where.class);
+    }
+
+    interface Thrower extends Consumer<Where>, BiPredicate<Where,Throwable> {
+
+    }
+
+    interface Finisher<T,U> {
+        U finish(Where w, HttpResponse<T> resp, Thrower thrower) throws IOException;
+    }
+
+    final <T,U> U shouldNotThrowInCancel(Where w, HttpResponse<T> resp, Thrower thrower) {
+        switch (w) {
+            case BEFORE_CANCEL: return null;
+            case AFTER_CANCEL: return null;
+            default: break;
+        }
+        return shouldHaveThrown(w, resp, thrower);
+    }
+
+
+    final <T,U> U shouldHaveThrown(Where w, HttpResponse<T> resp, Thrower thrower) {
+        String msg = "Expected exception not thrown in " + w
+                + "\n\tReceived: " + resp
+                + "\n\tWith body: " + resp.body();
+        System.out.println(msg);
+        throw new RuntimeException(msg);
+    }
+
+
+    private static Throwable findCause(Where w,
+                                       Throwable x,
+                                       BiPredicate<Where, Throwable> filter) {
+        while (x != null && !filter.test(w,x)) x = x.getCause();
+        return x;
+    }
+
+    static AssertionError causeNotFound(Where w, Throwable t) {
+        return new AssertionError("Expected exception not found in " + w, t);
+    }
+
+    static boolean isConnectionClosedLocally(Throwable t) {
+        if (t instanceof CompletionException) t = t.getCause();
+        if (t instanceof ExecutionException) t = t.getCause();
+        if (t instanceof IOException) {
+            String msg = t.getMessage();
+            return msg == null ? false
+                    : msg.contains("connection closed locally");
+        }
+        return false;
+    }
+
+    static final class UncheckedCustomExceptionThrower implements Thrower {
+        @Override
+        public void accept(Where where) {
+            out.println(now() + "Throwing in " + where);
+            throw new UncheckedCustomException(where.name());
+        }
+
+        @Override
+        public boolean test(Where w, Throwable throwable) {
+            switch (w) {
+                case AFTER_REQUEST:
+                case BEFORE_NEXT_REQUEST:
+                case AFTER_NEXT_REQUEST:
+                    if (isConnectionClosedLocally(throwable)) return true;
+                    break;
+                default:
+                    break;
+            }
+            return UncheckedCustomException.class.isInstance(throwable);
+        }
+
+        @Override
+        public String toString() {
+            return "UncheckedCustomExceptionThrower";
+        }
+    }
+
+    static final class UncheckedIOExceptionThrower implements Thrower {
+        @Override
+        public void accept(Where where) {
+            out.println(now() + "Throwing in " + where);
+            throw new UncheckedIOException(new CustomIOException(where.name()));
+        }
+
+        @Override
+        public boolean test(Where w, Throwable throwable) {
+            switch (w) {
+                case AFTER_REQUEST:
+                case BEFORE_NEXT_REQUEST:
+                case AFTER_NEXT_REQUEST:
+                    if (isConnectionClosedLocally(throwable)) return true;
+                    break;
+                default:
+                    break;
+            }
+            return UncheckedIOException.class.isInstance(throwable)
+                    && CustomIOException.class.isInstance(throwable.getCause());
+        }
+
+        @Override
+        public String toString() {
+            return "UncheckedIOExceptionThrower";
+        }
+    }
+
+    static final class UncheckedCustomException extends RuntimeException {
+        UncheckedCustomException(String message) {
+            super(message);
+        }
+        UncheckedCustomException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    static final class CustomIOException extends IOException {
+        CustomIOException(String message) {
+            super(message);
+        }
+        CustomIOException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+
+    static final class ThrowingBodyPublisher implements BodyPublisher {
+        private final BodyPublisher publisher;
+        private final CompletableFuture<Void> subscribedCF = new CompletableFuture<>();
+        final Consumer<Where> throwing;
+        ThrowingBodyPublisher(Consumer<Where> throwing, BodyPublisher publisher) {
+            this.throwing = throwing;
+            this.publisher = publisher;
+        }
+
+        @Override
+        public long contentLength() {
+            return publisher.contentLength();
+        }
+
+        @Override
+        public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
+            try {
+                throwing.accept(Where.BEFORE_SUBSCRIBE);
+                publisher.subscribe(new SubscriberWrapper(subscriber));
+                subscribedCF.complete(null);
+                throwing.accept(Where.AFTER_SUBSCRIBE);
+            } catch (Throwable t) {
+                subscribedCF.completeExceptionally(t);
+                throw t;
+            }
+        }
+
+        CompletableFuture<Void> subscribedCF() {
+            return subscribedCF;
+        }
+
+        class SubscriptionWrapper implements Flow.Subscription {
+            final Flow.Subscription subscription;
+            final AtomicLong requestCount = new AtomicLong();
+            SubscriptionWrapper(Flow.Subscription subscription) {
+                this.subscription = subscription;
+            }
+            @Override
+            public void request(long n) {
+                long count = requestCount.incrementAndGet();
+                System.out.printf("%s request-%d(%d)%n", now(), count, n);
+                if (count > 1) throwing.accept(Where.BEFORE_NEXT_REQUEST);
+                throwing.accept(Where.BEFORE_REQUEST);
+                subscription.request(n);
+                throwing.accept(Where.AFTER_REQUEST);
+                if (count > 1) throwing.accept(Where.AFTER_NEXT_REQUEST);
+            }
+
+            @Override
+            public void cancel() {
+                throwing.accept(Where.BEFORE_CANCEL);
+                subscription.cancel();
+                throwing.accept(Where.AFTER_CANCEL);
+            }
+        }
+
+        class SubscriberWrapper implements Flow.Subscriber<ByteBuffer> {
+            final Flow.Subscriber<? super ByteBuffer> subscriber;
+            SubscriberWrapper(Flow.Subscriber<? super ByteBuffer> subscriber) {
+                this.subscriber = subscriber;
+            }
+            @Override
+            public void onSubscribe(Flow.Subscription subscription) {
+                subscriber.onSubscribe(new SubscriptionWrapper(subscription));
+            }
+            @Override
+            public void onNext(ByteBuffer item) {
+                subscriber.onNext(item);
+            }
+            @Override
+            public void onComplete() {
+                subscriber.onComplete();
+            }
+
+            @Override
+            public void onError(Throwable throwable) {
+                subscriber.onError(throwable);
+            }
+        }
+    }
+
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h1_chunkHandler = new HTTP_ChunkedHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(h1_fixedLengthHandler, "/http1/fixed");
+        httpTestServer.addHandler(h1_chunkHandler, "/http1/chunk");
+        httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed/x";
+        httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk/x";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
+        httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
+        httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed/x";
+        httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk/x";
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";
+
+        serverCount.addAndGet(4);
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        String sharedClientName =
+                sharedClient == null ? null : sharedClient.toString();
+        sharedClient = null;
+        Thread.sleep(100);
+        AssertionError fail = TRACKER.check(500);
+        try {
+            httpTestServer.stop();
+            httpsTestServer.stop();
+            http2TestServer.stop();
+            https2TestServer.stop();
+        } finally {
+            if (fail != null) {
+                if (sharedClientName != null) {
+                    System.err.println("Shared client name is: " + sharedClientName);
+                }
+                throw fail;
+            }
+        }
+    }
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            byte[] resp;
+            try (InputStream is = t.getRequestBody()) {
+                resp = is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, resp.length);  //fixed content length
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+
+    static class HTTP_ChunkedHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());
+            byte[] resp;
+            try (InputStream is = t.getRequestBody()) {
+                resp = is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, -1); // chunked/variable
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ThrowingPushPromises.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,757 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests what happens when push promise handlers and their
+ *          response body handlers and subscribers throw unexpected exceptions.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters
+  *       ReferenceTracker ThrowingPushPromises
+ * @modules java.base/sun.net.www.http
+ *          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 ThrowingPushPromises
+ */
+
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.System.out;
+import static java.lang.System.err;
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class ThrowingPushPromises implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final int ITERATION_COUNT = 1;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+    static volatile boolean tasksFailed;
+    static final AtomicLong serverCount = new AtomicLong();
+    static final AtomicLong clientCount = new AtomicLong();
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+    private volatile HttpClient sharedClient;
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    out.printf(now() + "Task %s failed: %s%n", id, t);
+                    err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.printf("%n%sCreated %d servers and %d clients%n",
+                    now(), serverCount.get(), clientCount.get());
+            if (FAILURES.isEmpty()) return;
+            out.println("Failed tests: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+                e.getValue().printStackTrace();
+            });
+            if (tasksFailed) {
+                out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    private String[] uris() {
+        return new String[] {
+                http2URI_fixed,
+                http2URI_chunk,
+                https2URI_fixed,
+                https2URI_chunk,
+        };
+    }
+
+    @DataProvider(name = "noThrows")
+    public Object[][] noThrows() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+
+        int i = 0;
+        for (boolean sameClient : List.of(false, true)) {
+            for (String uri: uris()) {
+                result[i++] = new Object[] {uri, sameClient};
+            }
+        }
+        assert i == uris.length * 2;
+        return result;
+    }
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2 * 2][];
+        int i = 0;
+        for (Thrower thrower : List.of(
+                new UncheckedIOExceptionThrower(),
+                new UncheckedCustomExceptionThrower())) {
+            for (boolean sameClient : List.of(false, true)) {
+                for (String uri : uris()) {
+                    result[i++] = new Object[]{uri, sameClient, thrower};
+                }
+            }
+        }
+        assert i == uris.length * 2 * 2;
+        return result;
+    }
+
+    private HttpClient makeNewClient() {
+        clientCount.incrementAndGet();
+        return TRACKER.track(HttpClient.newBuilder()
+                .proxy(HttpClient.Builder.NO_PROXY)
+                .executor(executor)
+                .sslContext(sslContext)
+                .build());
+    }
+
+    HttpClient newHttpClient(boolean share) {
+        if (!share) return makeNewClient();
+        HttpClient shared = sharedClient;
+        if (shared != null) return shared;
+        synchronized (this) {
+            shared = sharedClient;
+            if (shared == null) {
+                shared = sharedClient = makeNewClient();
+            }
+            return shared;
+        }
+    }
+
+    @Test(dataProvider = "noThrows")
+    public void testNoThrows(String uri, boolean sameClient)
+            throws Exception {
+        HttpClient client = null;
+        out.printf("%ntestNoThrows(%s, %b)%n", uri, sameClient);
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<Stream<String>> handler =
+                    new ThrowingBodyHandler((w) -> {},
+                                            BodyHandlers.ofLines());
+            Map<HttpRequest, CompletableFuture<HttpResponse<Stream<String>>>> pushPromises =
+                    new ConcurrentHashMap<>();
+            PushPromiseHandler<Stream<String>> pushHandler = new PushPromiseHandler<>() {
+                @Override
+                public void applyPushPromise(HttpRequest initiatingRequest,
+                                             HttpRequest pushPromiseRequest,
+                                             Function<BodyHandler<Stream<String>>,
+                                                     CompletableFuture<HttpResponse<Stream<String>>>>
+                                                     acceptor) {
+                    pushPromises.putIfAbsent(pushPromiseRequest, acceptor.apply(handler));
+                }
+            };
+            HttpResponse<Stream<String>> response =
+                    client.sendAsync(req, BodyHandlers.ofLines(), pushHandler).get();
+            String body = response.body().collect(Collectors.joining("|"));
+            assertEquals(URI.create(body).getPath(), URI.create(uri).getPath());
+            for (HttpRequest promised : pushPromises.keySet()) {
+                out.printf("%s Received promise: %s%n\tresponse: %s%n",
+                        now(), promised, pushPromises.get(promised).get());
+                String promisedBody = pushPromises.get(promised).get().body()
+                        .collect(Collectors.joining("|"));
+                assertEquals(promisedBody, promised.uri().toASCIIString());
+            }
+            assertEquals(3, pushPromises.size());
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsString(String uri,
+                                     boolean sameClient,
+                                     Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsString(%s, %b, %s)",
+                             uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofString,
+                this::checkAsString, thrower);
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsLines(String uri,
+                                    boolean sameClient,
+                                    Thrower thrower)
+            throws Exception
+    {
+        String test =  format("testThrowingAsLines(%s, %b, %s)",
+                uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofLines,
+                this::checkAsLines, thrower);
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsInputStream(String uri,
+                                          boolean sameClient,
+                                          Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsInputStream(%s, %b, %s)",
+                uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofInputStream,
+                this::checkAsInputStream,  thrower);
+    }
+
+    private <T,U> void testThrowing(String name, String uri, boolean sameClient,
+                                    Supplier<BodyHandler<T>> handlers,
+                                    Finisher finisher, Thrower thrower)
+            throws Exception
+    {
+        out.printf("%n%s%s%n", now(), name);
+        try {
+            testThrowing(uri, sameClient, handlers, finisher, thrower);
+        } catch (Error | Exception x) {
+            FAILURES.putIfAbsent(name, x);
+            throw x;
+        }
+    }
+
+    private <T,U> void testThrowing(String uri, boolean sameClient,
+                                    Supplier<BodyHandler<T>> handlers,
+                                    Finisher finisher, Thrower thrower)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (Where where : Where.values()) {
+            if (where == Where.ON_ERROR) continue;
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.
+                    newBuilder(URI.create(uri))
+                    .build();
+            ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<T>>> promiseMap =
+                    new ConcurrentHashMap<>();
+            Supplier<BodyHandler<T>> throwing = () ->
+                    new ThrowingBodyHandler(where.select(thrower), handlers.get());
+            PushPromiseHandler<T> pushHandler = new ThrowingPromiseHandler<>(
+                    where.select(thrower),
+                    PushPromiseHandler.of((r) -> throwing.get(), promiseMap));
+            out.println("try throwing in " + where);
+            HttpResponse<T> response = null;
+            try {
+                response = client.sendAsync(req, handlers.get(), pushHandler).join();
+            } catch (Error | Exception x) {
+                throw x;
+            }
+            if (response != null) {
+                finisher.finish(where, req.uri(), response, thrower, promiseMap);
+            }
+        }
+    }
+
+    enum Where {
+        BODY_HANDLER, ON_SUBSCRIBE, ON_NEXT, ON_COMPLETE, ON_ERROR, GET_BODY, BODY_CF,
+        BEFORE_ACCEPTING, AFTER_ACCEPTING;
+        public Consumer<Where> select(Consumer<Where> consumer) {
+            return new Consumer<Where>() {
+                @Override
+                public void accept(Where where) {
+                    if (Where.this == where) {
+                        consumer.accept(where);
+                    }
+                }
+            };
+        }
+    }
+
+    interface Thrower extends Consumer<Where>, Predicate<Throwable> {
+
+    }
+
+    interface Finisher<T,U> {
+        U finish(Where w, URI requestURI, HttpResponse<T> resp, Thrower thrower,
+                 Map<HttpRequest, CompletableFuture<HttpResponse<T>>> promises);
+    }
+
+    final <T,U> U shouldHaveThrown(Where w, HttpResponse<T> resp, Thrower thrower) {
+        String msg = "Expected exception not thrown in " + w
+                + "\n\tReceived: " + resp
+                + "\n\tWith body: " + resp.body();
+        System.out.println(msg);
+        throw new RuntimeException(msg);
+    }
+
+    final List<String> checkAsString(Where w, URI reqURI,
+                                    HttpResponse<String> resp,
+                                    Thrower thrower,
+                                    Map<HttpRequest, CompletableFuture<HttpResponse<String>>> promises) {
+        Function<HttpResponse<String>, List<String>> extractor =
+                (r) -> List.of(r.body());
+        return check(w, reqURI, resp, thrower, promises, extractor);
+    }
+
+    final List<String> checkAsLines(Where w, URI reqURI,
+                                    HttpResponse<Stream<String>> resp,
+                                    Thrower thrower,
+                                    Map<HttpRequest, CompletableFuture<HttpResponse<Stream<String>>>> promises) {
+        Function<HttpResponse<Stream<String>>, List<String>> extractor =
+                (r) -> r.body().collect(Collectors.toList());
+        return check(w, reqURI, resp, thrower, promises, extractor);
+    }
+
+    final List<String> checkAsInputStream(Where w, URI reqURI,
+                                          HttpResponse<InputStream> resp,
+                                          Thrower thrower,
+                                          Map<HttpRequest, CompletableFuture<HttpResponse<InputStream>>> promises)
+    {
+        Function<HttpResponse<InputStream>, List<String>> extractor = (r) -> {
+            List<String> result;
+            try (InputStream is = r.body()) {
+                result = new BufferedReader(new InputStreamReader(is))
+                        .lines().collect(Collectors.toList());
+            } catch (Throwable t) {
+                throw new CompletionException(t);
+            }
+            return result;
+        };
+        return check(w, reqURI, resp, thrower, promises, extractor);
+    }
+
+    private final <T> List<String> check(Where w, URI reqURI,
+                                 HttpResponse<T> resp,
+                                 Thrower thrower,
+                                 Map<HttpRequest, CompletableFuture<HttpResponse<T>>> promises,
+                                 Function<HttpResponse<T>, List<String>> extractor)
+    {
+        List<String> result = extractor.apply(resp);
+        for (HttpRequest req : promises.keySet()) {
+            switch (w) {
+                case BEFORE_ACCEPTING:
+                    throw new RuntimeException("No push promise should have been received" +
+                            " for " + reqURI + " in " + w + ": got " + promises.keySet());
+                default:
+                    break;
+            }
+            HttpResponse<T> presp;
+            try {
+                presp = promises.get(req).join();
+            } catch (Error | Exception x) {
+                Throwable cause = findCause(x, thrower);
+                if (cause != null) {
+                    out.println(now() + "Got expected exception in "
+                            + w + ": " + cause);
+                    continue;
+                }
+                throw x;
+            }
+            switch (w) {
+                case BEFORE_ACCEPTING:
+                case AFTER_ACCEPTING:
+                case BODY_HANDLER:
+                case GET_BODY:
+                case BODY_CF:
+                    return shouldHaveThrown(w, presp, thrower);
+                default:
+                    break;
+            }
+            List<String> presult = null;
+            try {
+                presult = extractor.apply(presp);
+            } catch (Error | Exception x) {
+                Throwable cause = findCause(x, thrower);
+                if (cause != null) {
+                    out.println(now() + "Got expected exception for "
+                            + req + " in " + w + ": " + cause);
+                    continue;
+                }
+                throw x;
+            }
+            throw new RuntimeException("Expected exception not thrown for "
+                    + req + " in " + w);
+        }
+        final int expectedCount;
+        switch (w) {
+            case BEFORE_ACCEPTING:
+                expectedCount = 0;
+                break;
+            default:
+                expectedCount = 3;
+        }
+        assertEquals(promises.size(), expectedCount,
+                "bad promise count for " + reqURI + " with " + w);
+        assertEquals(result, List.of(reqURI.toASCIIString()));
+        return result;
+    }
+
+    private static Throwable findCause(Throwable x,
+                                       Predicate<Throwable> filter) {
+        while (x != null && !filter.test(x)) x = x.getCause();
+        return x;
+    }
+
+    static final class UncheckedCustomExceptionThrower implements Thrower {
+        @Override
+        public void accept(Where where) {
+            out.println(now() + "Throwing in " + where);
+            throw new UncheckedCustomException(where.name());
+        }
+
+        @Override
+        public boolean test(Throwable throwable) {
+            return UncheckedCustomException.class.isInstance(throwable);
+        }
+
+        @Override
+        public String toString() {
+            return "UncheckedCustomExceptionThrower";
+        }
+    }
+
+    static final class UncheckedIOExceptionThrower implements Thrower {
+        @Override
+        public void accept(Where where) {
+            out.println(now() + "Throwing in " + where);
+            throw new UncheckedIOException(new CustomIOException(where.name()));
+        }
+
+        @Override
+        public boolean test(Throwable throwable) {
+            return UncheckedIOException.class.isInstance(throwable)
+                    && CustomIOException.class.isInstance(throwable.getCause());
+        }
+
+        @Override
+        public String toString() {
+            return "UncheckedIOExceptionThrower";
+        }
+    }
+
+    static final class UncheckedCustomException extends RuntimeException {
+        UncheckedCustomException(String message) {
+            super(message);
+        }
+        UncheckedCustomException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    static final class CustomIOException extends IOException {
+        CustomIOException(String message) {
+            super(message);
+        }
+        CustomIOException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    static final class ThrowingPromiseHandler<T> implements PushPromiseHandler<T> {
+        final Consumer<Where> throwing;
+        final PushPromiseHandler<T> pushHandler;
+        ThrowingPromiseHandler(Consumer<Where> throwing, PushPromiseHandler<T> pushHandler) {
+            this.throwing = throwing;
+            this.pushHandler = pushHandler;
+        }
+
+        @Override
+        public void applyPushPromise(HttpRequest initiatingRequest,
+                                     HttpRequest pushPromiseRequest,
+                                     Function<BodyHandler<T>,
+                                             CompletableFuture<HttpResponse<T>>> acceptor) {
+            throwing.accept(Where.BEFORE_ACCEPTING);
+            pushHandler.applyPushPromise(initiatingRequest, pushPromiseRequest, acceptor);
+            throwing.accept(Where.AFTER_ACCEPTING);
+        }
+    }
+
+    static final class ThrowingBodyHandler<T> implements BodyHandler<T> {
+        final Consumer<Where> throwing;
+        final BodyHandler<T> bodyHandler;
+        ThrowingBodyHandler(Consumer<Where> throwing, BodyHandler<T> bodyHandler) {
+            this.throwing = throwing;
+            this.bodyHandler = bodyHandler;
+        }
+        @Override
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
+            throwing.accept(Where.BODY_HANDLER);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
+            return new ThrowingBodySubscriber(throwing, subscriber);
+        }
+    }
+
+    static final class ThrowingBodySubscriber<T> implements BodySubscriber<T> {
+        private final BodySubscriber<T> subscriber;
+        volatile boolean onSubscribeCalled;
+        final Consumer<Where> throwing;
+        ThrowingBodySubscriber(Consumer<Where> throwing, BodySubscriber<T> subscriber) {
+            this.throwing = throwing;
+            this.subscriber = subscriber;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            //out.println("onSubscribe ");
+            onSubscribeCalled = true;
+            throwing.accept(Where.ON_SUBSCRIBE);
+            subscriber.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+           // out.println("onNext " + item);
+            assertTrue(onSubscribeCalled);
+            throwing.accept(Where.ON_NEXT);
+            subscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            //out.println("onError");
+            assertTrue(onSubscribeCalled);
+            throwing.accept(Where.ON_ERROR);
+            subscriber.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            //out.println("onComplete");
+            assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
+            throwing.accept(Where.ON_COMPLETE);
+            subscriber.onComplete();
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            throwing.accept(Where.GET_BODY);
+            try {
+                throwing.accept(Where.BODY_CF);
+            } catch (Throwable t) {
+                return CompletableFuture.failedFuture(t);
+            }
+            return subscriber.getBody();
+        }
+    }
+
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";
+
+        serverCount.addAndGet(2);
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        String sharedClientName =
+                sharedClient == null ? null : sharedClient.toString();
+        sharedClient = null;
+        Thread.sleep(100);
+        AssertionError fail = TRACKER.check(500);
+        try {
+            http2TestServer.stop();
+            https2TestServer.stop();
+        } finally {
+            if (fail != null) {
+                if (sharedClientName != null) {
+                    System.err.println("Shared client name is: " + sharedClientName);
+                }
+                throw fail;
+            }
+        }
+    }
+
+    private static void pushPromiseFor(HttpTestExchange t, URI requestURI, String pushPath, boolean fixed)
+            throws IOException
+    {
+        try {
+            URI promise = new URI(requestURI.getScheme(),
+                    requestURI.getAuthority(),
+                    pushPath, null, null);
+            byte[] promiseBytes = promise.toASCIIString().getBytes(UTF_8);
+            out.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
+            err.printf("TestServer: %s Pushing promise: %s%n", now(), promise);
+            HttpTestHeaders headers =  HttpTestHeaders.of(new HttpHeadersImpl());
+            if (fixed) {
+                headers.addHeader("Content-length", String.valueOf(promiseBytes.length));
+            }
+            t.serverPush(promise, headers, promiseBytes);
+        } catch (URISyntaxException x) {
+            throw new IOException(x.getMessage(), x);
+        }
+    }
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            URI requestURI = t.getRequestURI();
+            for (int i = 1; i<2; i++) {
+                String path = requestURI.getPath() + "/before/promise-" + i;
+                pushPromiseFor(t, requestURI, path, true);
+            }
+            byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);
+            t.sendResponseHeaders(200, resp.length);  //fixed content length
+            try (OutputStream os = t.getResponseBody()) {
+                int bytes = resp.length/3;
+                for (int i = 0; i<2; i++) {
+                    String path = requestURI.getPath() + "/after/promise-" + (i + 2);
+                    os.write(resp, i * bytes, bytes);
+                    os.flush();
+                    pushPromiseFor(t, requestURI, path, true);
+                }
+                os.write(resp, 2*bytes, resp.length - 2*bytes);
+            }
+        }
+
+    }
+
+    static class HTTP_ChunkedHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());
+            byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            URI requestURI = t.getRequestURI();
+            for (int i = 1; i<2; i++) {
+                String path = requestURI.getPath() + "/before/promise-" + i;
+                pushPromiseFor(t, requestURI, path, false);
+            }
+            t.sendResponseHeaders(200, -1); // chunked/variable
+            try (OutputStream os = t.getResponseBody()) {
+                int bytes = resp.length/3;
+                for (int i = 0; i<2; i++) {
+                    String path = requestURI.getPath() + "/after/promise-" + (i + 2);
+                    os.write(resp, i * bytes, bytes);
+                    os.flush();
+                    pushPromiseFor(t, requestURI, path, false);
+                }
+                os.write(resp, 2*bytes, resp.length - 2*bytes);
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ThrowingSubscribers.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,734 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests what happens when response body handlers and subscribers
+ *          throw unexpected exceptions.
+ * @library /lib/testlibrary http2/server
+ * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters
+  *       ReferenceTracker ThrowingSubscribers
+ * @modules java.base/sun.net.www.http
+ *          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 ThrowingSubscribers
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+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.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.System.out;
+import static java.lang.String.format;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class ThrowingSubscribers implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;   // HTTPS/1.1
+    HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
+    String httpURI_fixed;
+    String httpURI_chunk;
+    String httpsURI_fixed;
+    String httpsURI_chunk;
+    String http2URI_fixed;
+    String http2URI_chunk;
+    String https2URI_fixed;
+    String https2URI_chunk;
+
+    static final int ITERATION_COUNT = 1;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
+    static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
+    static volatile boolean tasksFailed;
+    static final AtomicLong serverCount = new AtomicLong();
+    static final AtomicLong clientCount = new AtomicLong();
+    static final long start = System.nanoTime();
+    public static String now() {
+        long now = System.nanoTime() - start;
+        long secs = now / 1000_000_000;
+        long mill = (now % 1000_000_000) / 1000_000;
+        long nan = now % 1000_000;
+        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
+    }
+
+    final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
+    private volatile HttpClient sharedClient;
+
+    static class TestExecutor implements Executor {
+        final AtomicLong tasks = new AtomicLong();
+        Executor executor;
+        TestExecutor(Executor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            long id = tasks.incrementAndGet();
+            executor.execute(() -> {
+                try {
+                    command.run();
+                } catch (Throwable t) {
+                    tasksFailed = true;
+                    System.out.printf(now() + "Task %s failed: %s%n", id, t);
+                    System.err.printf(now() + "Task %s failed: %s%n", id, t);
+                    FAILURES.putIfAbsent("Task " + id, t);
+                    throw t;
+                }
+            });
+        }
+    }
+
+    @AfterClass
+    static final void printFailedTests() {
+        out.println("\n=========================");
+        try {
+            out.printf("%n%sCreated %d servers and %d clients%n",
+                    now(), serverCount.get(), clientCount.get());
+            if (FAILURES.isEmpty()) return;
+            out.println("Failed tests: ");
+            FAILURES.entrySet().forEach((e) -> {
+                out.printf("\t%s: %s%n", e.getKey(), e.getValue());
+                e.getValue().printStackTrace(out);
+                e.getValue().printStackTrace();
+            });
+            if (tasksFailed) {
+                System.out.println("WARNING: Some tasks failed");
+            }
+        } finally {
+            out.println("\n=========================\n");
+        }
+    }
+
+    private String[] uris() {
+        return new String[] {
+                httpURI_fixed,
+                httpURI_chunk,
+                httpsURI_fixed,
+                httpsURI_chunk,
+                http2URI_fixed,
+                http2URI_chunk,
+                https2URI_fixed,
+                https2URI_chunk,
+        };
+    }
+
+    @DataProvider(name = "noThrows")
+    public Object[][] noThrows() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2][];
+        int i = 0;
+        for (boolean sameClient : List.of(false, true)) {
+            for (String uri: uris()) {
+                result[i++] = new Object[] {uri, sameClient};
+            }
+        }
+        assert i == uris.length * 2;
+        return result;
+    }
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        String[] uris = uris();
+        Object[][] result = new Object[uris.length * 2 * 2][];
+        int i = 0;
+        for (Thrower thrower : List.of(
+                new UncheckedIOExceptionThrower(),
+                new UncheckedCustomExceptionThrower())) {
+            for (boolean sameClient : List.of(false, true)) {
+                for (String uri : uris()) {
+                    result[i++] = new Object[]{uri, sameClient, thrower};
+                }
+            }
+        }
+        assert i == uris.length * 2 * 2;
+        return result;
+    }
+
+    private HttpClient makeNewClient() {
+        clientCount.incrementAndGet();
+        HttpClient client =  HttpClient.newBuilder()
+                .proxy(HttpClient.Builder.NO_PROXY)
+                .executor(executor)
+                .sslContext(sslContext)
+                .build();
+        return TRACKER.track(client);
+    }
+
+    HttpClient newHttpClient(boolean share) {
+        if (!share) return makeNewClient();
+        HttpClient shared = sharedClient;
+        if (shared != null) return shared;
+        synchronized (this) {
+            shared = sharedClient;
+            if (shared == null) {
+                shared = sharedClient = makeNewClient();
+            }
+            return shared;
+        }
+    }
+
+    enum SubscriberType {
+        INLINE,  // In line subscribers complete their CF on ON_COMPLETE
+                 // e.g. BodySubscribers::ofString
+        OFFLINE; // Off line subscribers complete their CF immediately
+                 // but require the client to pull the data after the
+                 // CF completes (e.g. BodySubscribers::ofInputStream)
+    }
+
+    static EnumSet<Where> excludes(SubscriberType type) {
+        EnumSet<Where> set = EnumSet.noneOf(Where.class);
+
+        if (type == SubscriberType.OFFLINE) {
+            // Throwing on onSubscribe needs some more work
+            // for the case of InputStream, where the body has already
+            // completed by the time the subscriber is subscribed.
+            // The only way we have at that point to relay the exception
+            // is to call onError on the subscriber, but should we if
+            // Subscriber::onSubscribed has thrown an exception and
+            // not completed normally?
+            set.add(Where.ON_SUBSCRIBE);
+        }
+
+        // Don't know how to make the stack reliably cause onError
+        // to be called without closing the connection.
+        // And how do we get the exception if onError throws anyway?
+        set.add(Where.ON_ERROR);
+
+        return set;
+    }
+
+    @Test(dataProvider = "noThrows")
+    public void testNoThrows(String uri, boolean sameClient)
+            throws Exception {
+        HttpClient client = null;
+        out.printf("%ntestNoThrows(%s, %b)%n", uri, sameClient);
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<String> handler =
+                    new ThrowingBodyHandler((w) -> {},
+                                            BodyHandlers.ofString());
+            HttpResponse<String> response = client.send(req, handler);
+            String body = response.body();
+            assertEquals(URI.create(body).getPath(), URI.create(uri).getPath());
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsString(String uri,
+                                     boolean sameClient,
+                                     Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsString(%s, %b, %s)",
+                             uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofString,
+                this::shouldHaveThrown, thrower,false,
+                excludes(SubscriberType.INLINE));
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsLines(String uri,
+                                    boolean sameClient,
+                                    Thrower thrower)
+            throws Exception
+    {
+        String test =  format("testThrowingAsLines(%s, %b, %s)",
+                uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofLines,
+                this::checkAsLines, thrower,false,
+                excludes(SubscriberType.OFFLINE));
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsInputStream(String uri,
+                                          boolean sameClient,
+                                          Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsInputStream(%s, %b, %s)",
+                uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofInputStream,
+                this::checkAsInputStream,  thrower,false,
+                excludes(SubscriberType.OFFLINE));
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsStringAsync(String uri,
+                                          boolean sameClient,
+                                          Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsStringAsync(%s, %b, %s)",
+                uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofString,
+                     this::shouldHaveThrown, thrower, true,
+                excludes(SubscriberType.INLINE));
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsLinesAsync(String uri,
+                                         boolean sameClient,
+                                         Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsLinesAsync(%s, %b, %s)",
+                uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofLines,
+                this::checkAsLines, thrower,true,
+                excludes(SubscriberType.OFFLINE));
+    }
+
+    @Test(dataProvider = "variants")
+    public void testThrowingAsInputStreamAsync(String uri,
+                                               boolean sameClient,
+                                               Thrower thrower)
+            throws Exception
+    {
+        String test = format("testThrowingAsInputStreamAsync(%s, %b, %s)",
+                uri, sameClient, thrower);
+        testThrowing(test, uri, sameClient, BodyHandlers::ofInputStream,
+                this::checkAsInputStream, thrower,true,
+                excludes(SubscriberType.OFFLINE));
+    }
+
+    private <T,U> void testThrowing(String name, String uri, boolean sameClient,
+                                    Supplier<BodyHandler<T>> handlers,
+                                    Finisher finisher, Thrower thrower,
+                                    boolean async, EnumSet<Where> excludes)
+            throws Exception
+    {
+        out.printf("%n%s%s%n", now(), name);
+        try {
+            testThrowing(uri, sameClient, handlers, finisher, thrower, async, excludes);
+        } catch (Error | Exception x) {
+            FAILURES.putIfAbsent(name, x);
+            throw x;
+        }
+    }
+
+    private <T,U> void testThrowing(String uri, boolean sameClient,
+                                    Supplier<BodyHandler<T>> handlers,
+                                    Finisher finisher, Thrower thrower,
+                                    boolean async,
+                                    EnumSet<Where> excludes)
+            throws Exception
+    {
+        HttpClient client = null;
+        for (Where where : EnumSet.complementOf(excludes)) {
+
+            if (!sameClient || client == null)
+                client = newHttpClient(sameClient);
+
+            HttpRequest req = HttpRequest.
+                    newBuilder(URI.create(uri))
+                    .build();
+            BodyHandler<T> handler =
+                    new ThrowingBodyHandler(where.select(thrower), handlers.get());
+            System.out.println("try throwing in " + where);
+            HttpResponse<T> response = null;
+            if (async) {
+                try {
+                    response = client.sendAsync(req, handler).join();
+                } catch (Error | Exception x) {
+                    Throwable cause = findCause(x, thrower);
+                    if (cause == null) throw causeNotFound(where, x);
+                    System.out.println(now() + "Got expected exception: " + cause);
+                }
+            } else {
+                try {
+                    response = client.send(req, handler);
+                } catch (Error | Exception t) {
+                    if (thrower.test(t)) {
+                        System.out.println(now() + "Got expected exception: " + t);
+                    } else throw causeNotFound(where, t);
+                }
+            }
+            if (response != null) {
+                finisher.finish(where, response, thrower);
+            }
+        }
+    }
+
+    enum Where {
+        BODY_HANDLER, ON_SUBSCRIBE, ON_NEXT, ON_COMPLETE, ON_ERROR, GET_BODY, BODY_CF;
+        public Consumer<Where> select(Consumer<Where> consumer) {
+            return new Consumer<Where>() {
+                @Override
+                public void accept(Where where) {
+                    if (Where.this == where) {
+                        consumer.accept(where);
+                    }
+                }
+            };
+        }
+    }
+
+    static AssertionError causeNotFound(Where w, Throwable t) {
+        return new AssertionError("Expected exception not found in " + w, t);
+    }
+
+    interface Thrower extends Consumer<Where>, Predicate<Throwable> {
+
+    }
+
+    interface Finisher<T,U> {
+        U finish(Where w, HttpResponse<T> resp, Thrower thrower) throws IOException;
+    }
+
+    final <T,U> U shouldHaveThrown(Where w, HttpResponse<T> resp, Thrower thrower) {
+        String msg = "Expected exception not thrown in " + w
+                + "\n\tReceived: " + resp
+                + "\n\tWith body: " + resp.body();
+        System.out.println(msg);
+        throw new RuntimeException(msg);
+    }
+
+    final List<String> checkAsLines(Where w, HttpResponse<Stream<String>> resp, Thrower thrower) {
+        switch(w) {
+            case BODY_HANDLER: return shouldHaveThrown(w, resp, thrower);
+            case GET_BODY: return shouldHaveThrown(w, resp, thrower);
+            case BODY_CF: return shouldHaveThrown(w, resp, thrower);
+            default: break;
+        }
+        List<String> result = null;
+        try {
+            result = resp.body().collect(Collectors.toList());
+        } catch (Error | Exception x) {
+            Throwable cause = findCause(x, thrower);
+            if (cause != null) {
+                out.println(now() + "Got expected exception in " + w + ": " + cause);
+                return result;
+            }
+            throw causeNotFound(w, x);
+        }
+        return shouldHaveThrown(w, resp, thrower);
+    }
+
+    final List<String> checkAsInputStream(Where w, HttpResponse<InputStream> resp,
+                                    Thrower thrower)
+            throws IOException
+    {
+        switch(w) {
+            case BODY_HANDLER: return shouldHaveThrown(w, resp, thrower);
+            case GET_BODY: return shouldHaveThrown(w, resp, thrower);
+            case BODY_CF: return shouldHaveThrown(w, resp, thrower);
+            default: break;
+        }
+        List<String> result = null;
+        try (InputStreamReader r1 = new InputStreamReader(resp.body(), UTF_8);
+             BufferedReader r = new BufferedReader(r1)) {
+            try {
+                result = r.lines().collect(Collectors.toList());
+            } catch (Error | Exception x) {
+                Throwable cause = findCause(x, thrower);
+                if (cause != null) {
+                    out.println(now() + "Got expected exception in " + w + ": " + cause);
+                    return result;
+                }
+                throw causeNotFound(w, x);
+            }
+        }
+        return shouldHaveThrown(w, resp, thrower);
+    }
+
+    private static Throwable findCause(Throwable x,
+                                       Predicate<Throwable> filter) {
+        while (x != null && !filter.test(x)) x = x.getCause();
+        return x;
+    }
+
+    static final class UncheckedCustomExceptionThrower implements Thrower {
+        @Override
+        public void accept(Where where) {
+            out.println(now() + "Throwing in " + where);
+            throw new UncheckedCustomException(where.name());
+        }
+
+        @Override
+        public boolean test(Throwable throwable) {
+            return UncheckedCustomException.class.isInstance(throwable);
+        }
+
+        @Override
+        public String toString() {
+            return "UncheckedCustomExceptionThrower";
+        }
+    }
+
+    static final class UncheckedIOExceptionThrower implements Thrower {
+        @Override
+        public void accept(Where where) {
+            out.println(now() + "Throwing in " + where);
+            throw new UncheckedIOException(new CustomIOException(where.name()));
+        }
+
+        @Override
+        public boolean test(Throwable throwable) {
+            return UncheckedIOException.class.isInstance(throwable)
+                    && CustomIOException.class.isInstance(throwable.getCause());
+        }
+
+        @Override
+        public String toString() {
+            return "UncheckedIOExceptionThrower";
+        }
+    }
+
+    static final class UncheckedCustomException extends RuntimeException {
+        UncheckedCustomException(String message) {
+            super(message);
+        }
+        UncheckedCustomException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    static final class CustomIOException extends IOException {
+        CustomIOException(String message) {
+            super(message);
+        }
+        CustomIOException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    static final class ThrowingBodyHandler<T> implements BodyHandler<T> {
+        final Consumer<Where> throwing;
+        final BodyHandler<T> bodyHandler;
+        ThrowingBodyHandler(Consumer<Where> throwing, BodyHandler<T> bodyHandler) {
+            this.throwing = throwing;
+            this.bodyHandler = bodyHandler;
+        }
+        @Override
+        public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
+            throwing.accept(Where.BODY_HANDLER);
+            BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
+            return new ThrowingBodySubscriber(throwing, subscriber);
+        }
+    }
+
+    static final class ThrowingBodySubscriber<T> implements BodySubscriber<T> {
+        private final BodySubscriber<T> subscriber;
+        volatile boolean onSubscribeCalled;
+        final Consumer<Where> throwing;
+        ThrowingBodySubscriber(Consumer<Where> throwing, BodySubscriber<T> subscriber) {
+            this.throwing = throwing;
+            this.subscriber = subscriber;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            //out.println("onSubscribe ");
+            onSubscribeCalled = true;
+            throwing.accept(Where.ON_SUBSCRIBE);
+            subscriber.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+           // out.println("onNext " + item);
+            assertTrue(onSubscribeCalled);
+            throwing.accept(Where.ON_NEXT);
+            subscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            //out.println("onError");
+            assertTrue(onSubscribeCalled);
+            throwing.accept(Where.ON_ERROR);
+            subscriber.onError(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            //out.println("onComplete");
+            assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
+            throwing.accept(Where.ON_COMPLETE);
+            subscriber.onComplete();
+        }
+
+        @Override
+        public CompletionStage<T> getBody() {
+            throwing.accept(Where.GET_BODY);
+            try {
+                throwing.accept(Where.BODY_CF);
+            } catch (Throwable t) {
+                return CompletableFuture.failedFuture(t);
+            }
+            return subscriber.getBody();
+        }
+    }
+
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h1_chunkHandler = new HTTP_ChunkedHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(h1_fixedLengthHandler, "/http1/fixed");
+        httpTestServer.addHandler(h1_chunkHandler, "/http1/chunk");
+        httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed/x";
+        httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk/x";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
+        httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
+        httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed/x";
+        httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk/x";
+
+        // HTTP/2
+        HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
+        HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
+        http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
+        http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";
+        http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
+        https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
+        https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";
+        https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";
+
+        serverCount.addAndGet(4);
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        String sharedClientName =
+                sharedClient == null ? null : sharedClient.toString();
+        sharedClient = null;
+        Thread.sleep(100);
+        AssertionError fail = TRACKER.check(500);
+        try {
+            httpTestServer.stop();
+            httpsTestServer.stop();
+            http2TestServer.stop();
+            https2TestServer.stop();
+        } finally {
+            if (fail != null) {
+                if (sharedClientName != null) {
+                    System.err.println("Shared client name is: " + sharedClientName);
+                }
+                throw fail;
+            }
+        }
+    }
+
+    static class HTTP_FixedLengthHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);
+            t.sendResponseHeaders(200, resp.length);  //fixed content length
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+
+    static class HTTP_ChunkedHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());
+            byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);
+            try (InputStream is = t.getRequestBody()) {
+                is.readAllBytes();
+            }
+            t.sendResponseHeaders(200, -1); // chunked/variable
+            try (OutputStream os = t.getResponseBody()) {
+                os.write(resp);
+            }
+        }
+    }
+
+}
--- a/test/jdk/java/net/httpclient/TimeoutBasic.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/TimeoutBasic.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,12 +22,15 @@
  */
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpTimeoutException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpTimeoutException;
 import jdk.testlibrary.SimpleSSLContext;
 
 import javax.net.ServerSocketFactory;
@@ -40,7 +43,6 @@
 import java.util.function.Function;
 
 import static java.lang.System.out;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 
 /**
  * @test
@@ -96,17 +98,16 @@
     }
 
     static HttpRequest.Builder DELETE(HttpRequest.Builder builder) {
-        HttpRequest.BodyPublisher noBody = HttpRequest.BodyPublisher.noBody();
-        return builder.DELETE(noBody);
+        return builder.DELETE();
     }
 
     static HttpRequest.Builder PUT(HttpRequest.Builder builder) {
-        HttpRequest.BodyPublisher noBody = HttpRequest.BodyPublisher.noBody();
+        HttpRequest.BodyPublisher noBody = HttpRequest.BodyPublishers.noBody();
         return builder.PUT(noBody);
     }
 
     static HttpRequest.Builder POST(HttpRequest.Builder builder) {
-        HttpRequest.BodyPublisher noBody = HttpRequest.BodyPublisher.noBody();
+        HttpRequest.BodyPublisher noBody = HttpRequest.BodyPublishers.noBody();
         return builder.POST(noBody);
     }
 
@@ -140,9 +141,11 @@
         if (version != null) builder.version(version);
         HttpClient client = builder.build();
         out.printf("%ntest(version=%s, reqVersion=%s, scheme=%s)%n", version, reqVersion, scheme);
-        try (ServerSocket ss = ssf.createServerSocket(0, 20)) {
+        try (ServerSocket ss = ssf.createServerSocket()) {
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             int port = ss.getLocalPort();
-            URI uri = new URI(scheme +"://127.0.0.1:" + port + "/");
+            URI uri = new URI(scheme +"://localhost:" + port + "/");
 
             out.println("--- TESTING Async");
             int count = 0;
@@ -152,7 +155,12 @@
                 if (request == null) continue;
                 count++;
                 try {
-                    HttpResponse<?> resp = client.sendAsync(request, discard(null)).join();
+                    HttpResponse<?> resp = client.sendAsync(request, BodyHandlers.discarding()).join();
+                    out.println("Unexpected response for: " + request);
+                    out.println("\t from " + ss.getLocalSocketAddress());
+                    out.println("Response is: " + resp);
+                    out.println("Headers: " + resp.headers().map());
+                    out.println("Body (should be null): " + resp.body());
                     throw new RuntimeException("Unexpected response: " + resp.statusCode());
                 } catch (CompletionException e) {
                     if (!(e.getCause() instanceof HttpTimeoutException)) {
@@ -172,7 +180,7 @@
                 if (request == null) continue;
                 count++;
                 try {
-                    client.send(request, discard(null));
+                    client.send(request, BodyHandlers.discarding());
                 } catch (HttpTimeoutException e) {
                     out.println("Caught expected timeout: " + e);
                 }
--- a/test/jdk/java/net/httpclient/TimeoutOrdering.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/TimeoutOrdering.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,19 +22,21 @@
  */
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpTimeoutException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpTimeoutException;
 import java.time.Duration;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import static java.lang.System.out;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
 
 /**
  * @test
@@ -59,9 +61,11 @@
     public static void main(String[] args) throws Exception {
         HttpClient client = HttpClient.newHttpClient();
 
-        try (ServerSocket ss = new ServerSocket(0, 20)) {
+        try (ServerSocket ss = new ServerSocket()) {
+            ss.setReuseAddress(false);
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             int port = ss.getLocalPort();
-            URI uri = new URI("http://127.0.0.1:" + port + "/");
+            URI uri = new URI("http://localhost:" + port + "/");
 
             HttpRequest[] requests = new HttpRequest[TIMEOUTS.length];
 
@@ -74,7 +78,7 @@
 
                 final HttpRequest req = requests[i];
                 CompletableFuture<HttpResponse<Object>> response = client
-                    .sendAsync(req, discard(null))
+                    .sendAsync(req, BodyHandlers.replacing(null))
                     .whenComplete((HttpResponse<Object> r, Throwable t) -> {
                         if (r != null) {
                             out.println("Unexpected response: " + r);
@@ -115,7 +119,7 @@
                 final HttpRequest req = requests[i];
                 executor.execute(() -> {
                     try {
-                        client.send(req, discard(null));
+                        client.send(req, BodyHandlers.replacing(null));
                     } catch (HttpTimeoutException e) {
                         out.println("Caught expected timeout: " + e);
                         queue.offer(req);
--- a/test/jdk/java/net/httpclient/VersionTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/VersionTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,9 @@
 /*
  * @test
  * @bug 8175814
- * @modules jdk.incubator.httpclient java.logging jdk.httpserver
+ * @modules java.net.http java.logging jdk.httpserver
+ * @library /lib/testlibrary/ /
+ * @build ProxyServer
  * @run main/othervm -Djdk.httpclient.HttpClient.log=errors,requests,headers,trace VersionTest
  */
 
@@ -35,26 +37,26 @@
 import com.sun.net.httpserver.HttpServer;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.URI;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ExecutorService;
-import java.net.InetSocketAddress;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+import static java.net.http.HttpClient.Version.HTTP_2;
 
-/**
- */
 public class VersionTest {
     static HttpServer s1 ;
+    static ProxyServer proxy;
     static ExecutorService executor;
     static int port;
-    static HttpClient client;
+    static InetSocketAddress proxyAddr;
+    static HttpClient client, clientWithProxy;
     static URI uri;
     static volatile boolean error = false;
 
@@ -64,13 +66,20 @@
         client = HttpClient.newBuilder()
                            .executor(executor)
                            .build();
+
+        clientWithProxy = HttpClient.newBuilder()
+                           .executor(executor)
+                           .proxy(ProxySelector.of(proxyAddr))
+                           .build();
+
         // first check that the version is HTTP/2
         if (client.version() != HttpClient.Version.HTTP_2) {
             throw new RuntimeException("Default version not HTTP_2");
         }
         try {
-            test(HTTP_1_1);
-            test(HTTP_2);
+            test(HTTP_1_1, false);
+            test(HTTP_2, false);
+            test(HTTP_2, true);
         } finally {
             s1.stop(0);
             executor.shutdownNow();
@@ -79,12 +88,13 @@
             throw new RuntimeException();
     }
 
-    public static void test(HttpClient.Version version) throws Exception {
+    public static void test(HttpClient.Version version, boolean proxy) throws Exception {
         HttpRequest r = HttpRequest.newBuilder(uri)
                 .version(version)
                 .GET()
                 .build();
-        HttpResponse<Void> resp = client.send(r, discard(null));
+        HttpClient c = proxy ? clientWithProxy : client;
+        HttpResponse<Void> resp = c.send(r, BodyHandlers.discarding());
         System.out.printf("Client: response is %d\n", resp.statusCode());
         if (resp.version() != HTTP_1_1) {
             throw new RuntimeException();
@@ -93,7 +103,7 @@
     }
 
     static void initServer() throws Exception {
-        InetSocketAddress addr = new InetSocketAddress (0);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
         s1 = HttpServer.create (addr, 0);
         HttpHandler h = new Handler();
 
@@ -104,36 +114,40 @@
         s1.start();
 
         port = s1.getAddress().getPort();
-        uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/foo");
+        uri = new URI("http://localhost:" + Integer.toString(port) + "/foo");
         System.out.println("HTTP server port = " + port);
+        proxy = new ProxyServer(0, false);
+        int proxyPort = proxy.getPort();
+        proxyAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), proxyPort);
     }
-}
 
-class Handler implements HttpHandler {
-    int counter = 0;
+    static class Handler implements HttpHandler {
+        int counter;
 
-    void checkHeader(Headers h) {
-        counter++;
-        if (counter == 1 && h.containsKey("Upgrade")) {
-            VersionTest.error = true;
+        void checkHeader(Headers h) {
+            counter++;
+            if (counter == 1 && h.containsKey("Upgrade")) {
+                VersionTest.error = true;
+            }
+            if (counter == 2 && !h.containsKey("Upgrade")) {
+                VersionTest.error = true;
+            }
+            if (counter == 3 && h.containsKey("Upgrade")) {
+                VersionTest.error = true;
+            }
         }
-        if (counter > 1 && !h.containsKey("Upgrade")) {
-            VersionTest.error = true;
+
+        @Override
+        public synchronized void handle(HttpExchange t) throws IOException {
+            String reply = "Hello world";
+            int len = reply.length();
+            Headers h = t.getRequestHeaders();
+            checkHeader(h);
+            System.out.printf("Sending response 200\n");
+            t.sendResponseHeaders(200, len);
+            OutputStream o = t.getResponseBody();
+            o.write(reply.getBytes());
+            t.close();
         }
     }
-
-    @Override
-    public synchronized void handle(HttpExchange t)
-        throws IOException
-    {
-        String reply = "Hello world";
-        int len = reply.length();
-        Headers h = t.getRequestHeaders();
-        checkHeader(h);
-        System.out.printf("Sending response 200\n");
-        t.sendResponseHeaders(200, len);
-        OutputStream o = t.getResponseBody();
-        o.write(reply.getBytes());
-        t.close();
-    }
 }
--- a/test/jdk/java/net/httpclient/ZeroRedirects.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/ZeroRedirects.java	Tue Apr 17 08:54:17 2018 -0700
@@ -24,32 +24,26 @@
 /*
  * @test
  * @bug 8164941
- * @modules jdk.incubator.httpclient java.logging jdk.httpserver
+ * @modules java.net.http java.logging jdk.httpserver
  * @run main/othervm ZeroRedirects
  */
 
-import com.sun.net.httpserver.Headers;
 import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.net.InetAddress;
 import java.net.URI;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ExecutorService;
 import java.net.InetSocketAddress;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 
-/**
- */
 public class ZeroRedirects {
     static HttpServer s1 ;
     static ExecutorService executor;
@@ -73,19 +67,19 @@
     }
 
     public static void test() throws Exception {
-        System.setProperty("java.net.httpclient.redirects.retrylimit", "0");
+        System.setProperty("java.net.http.redirects.retrylimit", "0");
         HttpRequest r = HttpRequest.newBuilder(uri)
                 .GET()
                 .build();
-        HttpResponse<Void> resp = client.send(r, discard(null));
+        HttpResponse<Void> resp = client.send(r, BodyHandlers.discarding());
         System.out.printf("Client: response is %d\n", resp.statusCode());
         if (resp.statusCode() != 200)
             throw new RuntimeException();
     }
 
     static void initServer() throws Exception {
-        InetSocketAddress addr = new InetSocketAddress (0);
-        s1 = HttpServer.create (addr, 0);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        s1 = HttpServer.create(addr, 0);
         HttpHandler h = new Handler();
 
         HttpContext c1 = s1.createContext("/", h);
@@ -95,23 +89,21 @@
         s1.start();
 
         port = s1.getAddress().getPort();
-        uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/foo");
+        uri = new URI("http://localhost:" + port + "/foo");
         System.out.println("HTTP server port = " + port);
     }
+
+    static class Handler implements HttpHandler {
+
+        @Override
+        public synchronized void handle(HttpExchange t) throws IOException {
+            String reply = "Hello world";
+            int len = reply.length();
+            System.out.printf("Sending response 200\n");
+            t.sendResponseHeaders(200, len);
+            OutputStream o = t.getResponseBody();
+            o.write(reply.getBytes());
+            t.close();
+        }
+    }
 }
-
-class Handler implements HttpHandler {
-
-    @Override
-    public synchronized void handle(HttpExchange t)
-        throws IOException
-    {
-        String reply = "Hello world";
-        int len = reply.length();
-        System.out.printf("Sending response 200\n");
-        t.sendResponseHeaders(200, len);
-        OutputStream o = t.getResponseBody();
-        o.write(reply.getBytes());
-        t.close();
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/dependent.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,68 @@
+//
+// 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.
+//
+
+// for JTwork/classes/0/lib/testlibrary/jdk/testlibrary/SimpleSSLContext.class
+grant codeBase "file:${test.classes}/../../../../lib/testlibrary/-" {
+    permission java.util.PropertyPermission "test.src.path", "read";
+    permission java.io.FilePermission "${test.src}/../../../lib/testlibrary/jdk/testlibrary/testkeys", "read";
+};
+
+// for JTwork//classes/0/java/net/httpclient/http2/server/*
+grant codeBase "file:${test.classes}/../../../../java/net/httpclient/http2/server/*" {
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.common";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.frame";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.hpack";
+    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www.http";
+
+    permission java.net.SocketPermission "localhost:*", "listen,accept,resolve";
+    permission java.lang.RuntimePermission "modifyThread";
+};
+
+grant codeBase "file:${test.classes}/*" {
+    permission java.io.FilePermission "${user.dir}${/}asFileDownloadTest.tmp.dir", "read,write";
+    permission java.io.FilePermission "${user.dir}${/}asFileDownloadTest.tmp.dir/-", "read,write";
+
+    permission java.net.URLPermission "http://localhost:*/http1/-",   "GET,POST";
+    permission java.net.URLPermission "https://localhost:*/https1/-", "GET,POST";
+    permission java.net.URLPermission "http://localhost:*/http2/-",   "GET,POST";
+    permission java.net.URLPermission "https://localhost:*/https2/-", "GET,POST";
+
+
+    // needed to grant permission to the HTTP/2 server
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.common";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.frame";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.net.http.hpack";
+    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www.http";
+
+    // for HTTP/1.1 server logging
+    permission java.util.logging.LoggingPermission "control";
+
+    // needed to grant the HTTP servers
+    permission java.net.SocketPermission "localhost:*", "listen,accept,resolve";
+
+    permission java.util.PropertyPermission "*", "read";
+    permission java.lang.RuntimePermission "modifyThread";
+
+    // Permission for DependentActionsTest
+    permission java.lang.RuntimePermission "getStackWalkerWithClassReference";
+};
--- a/test/jdk/java/net/httpclient/docs/files/notsobigfile.txt	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/docs/files/notsobigfile.txt	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/examples/JavadocExamples.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.Authenticator;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Redirect;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Flow;
+import java.util.regex.Pattern;
+
+/*
+ * THE CONTENTS OF THIS FILE HAVE TO BE IN SYNC WITH THE EXAMPLES USED IN THE
+ * JAVADOC.
+ *
+ * @test
+ * @compile JavadocExamples.java
+ */
+public class JavadocExamples {
+    HttpRequest request = null;
+    HttpClient client = null;
+    Pattern p = null;
+
+    void fromHttpClientClasslevelDescription() throws Exception {
+        //Synchronous Example
+        HttpClient client = HttpClient.newBuilder()
+                .version(Version.HTTP_1_1)
+                .followRedirects(Redirect.NORMAL)
+                .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
+                .authenticator(Authenticator.getDefault())
+                .build();
+        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+        System.out.println(response.statusCode());
+        System.out.println(response.body());
+
+        //Asynchronous Example
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("https://foo.com/"))
+                .timeout(Duration.ofMinutes(1))
+                .header("Content-Type", "application/json")
+                .POST(BodyPublishers.ofFile(Paths.get("file.json")))
+                .build();
+        client.sendAsync(request, BodyHandlers.ofString())
+                .thenApply(HttpResponse::body)
+                .thenAccept(System.out::println);
+    }
+
+    void fromHttpRequest() throws Exception {
+        // HttpRequest class-level description
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://foo.com/"))
+                .build();
+        client.sendAsync(request, BodyHandlers.ofString())
+                .thenApply(HttpResponse::body)
+                .thenAccept(System.out::println)
+                .join();
+
+        // HttpRequest.BodyPublishers class-level description
+        // Request body from a String
+        HttpRequest request1 = HttpRequest.newBuilder()
+                .uri(URI.create("https://foo.com/"))
+                .header("Content-Type", "text/plain; charset=UTF-8")
+                .POST(BodyPublishers.ofString("some body text"))
+                .build();
+
+        // Request body from a File
+        HttpRequest request2 = HttpRequest.newBuilder()
+                .uri(URI.create("https://foo.com/"))
+                .header("Content-Type", "application/json")
+                .POST(BodyPublishers.ofFile(Paths.get("file.json")))
+                .build();
+
+        // Request body from a byte array
+        HttpRequest request3 = HttpRequest.newBuilder()
+                .uri(URI.create("https://foo.com/"))
+                .POST(BodyPublishers.ofByteArray(new byte[] { /*...*/ }))
+                .build();
+    }
+
+    void fromHttpResponse() throws Exception {
+        // HttpResponse class-level description
+        HttpResponse<String> response = client
+                .send(request, BodyHandlers.ofString());
+
+        // HttpResponse.BodyHandler class-level description
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://www.foo.com/"))
+                .build();
+        client.sendAsync(request, BodyHandlers.ofFile(Paths.get("/tmp/f")))
+                .thenApply(HttpResponse::body)
+                .thenAccept(System.out::println);
+
+        HttpRequest request1 = HttpRequest.newBuilder()
+                .uri(URI.create("http://www.foo.com/"))
+                .build();
+        BodyHandler<Path> bodyHandler = (info) -> info.statusCode() == 200
+                ? BodySubscribers.ofFile(Paths.get("/tmp/f"))
+                : BodySubscribers.replacing(Paths.get("/NULL"));
+        client.sendAsync(request, bodyHandler)
+                .thenApply(HttpResponse::body)
+                .thenAccept(System.out::println);
+
+
+        // HttpResponse.BodyHandlers class-level description
+        // Receives the response body as a String
+        HttpResponse<String> response1 = client
+                .send(request, BodyHandlers.ofString());
+
+        // Receives the response body as a file
+        HttpResponse<Path> response2 = client
+                .send(request, BodyHandlers.ofFile(Paths.get("example.html")));
+
+        // Receives the response body as an InputStream
+        HttpResponse<InputStream> respons3 = client
+                .send(request, BodyHandlers.ofInputStream());
+
+        // Discards the response body
+        HttpResponse<Void> respons4 = client
+                .send(request, BodyHandlers.discarding());
+
+    }
+
+    /**
+     * @apiNote This method can be used as an adapter between a {@code
+     * BodySubscriber} and a text based {@code Flow.Subscriber} that parses
+     * text line by line.
+     *
+     * <p> For example:
+     * <pre> {@code  // A PrintSubscriber that implements Flow.Subscriber<String>
+     *  // and print lines received by onNext() on System.out
+     *  PrintSubscriber subscriber = new PrintSubscriber(System.out);
+     *  client.sendAsync(request, BodyHandlers.fromLineSubscriber(subscriber))
+     *      .thenApply(HttpResponse::statusCode)
+     *      .thenAccept((status) -> {
+     *          if (status != 200) {
+     *              System.err.printf("ERROR: %d status received%n", status);
+     *          }
+     *      }); }</pre>
+     */
+    void fromLineSubscriber1() {
+         // A PrintSubscriber that implements Flow.Subscriber<String>
+         // and print lines received by onNext() on System.out
+         PrintSubscriber subscriber = new PrintSubscriber(System.out);
+         client.sendAsync(request, BodyHandlers.fromLineSubscriber(subscriber))
+                 .thenApply(HttpResponse::statusCode)
+                 .thenAccept((status) -> {
+                     if (status != 200) {
+                         System.err.printf("ERROR: %d status received%n", status);
+                     }
+                 });
+    }
+
+    /**
+     * @apiNote This method can be used as an adapter between a {@code
+     * BodySubscriber} and a text based {@code Flow.Subscriber} that parses
+     * text line by line.
+     *
+     * <p> For example:
+     * <pre> {@code  // A LineParserSubscriber that implements Flow.Subscriber<String>
+     *  // and accumulates lines that match a particular pattern
+     *  Pattern pattern = ...;
+     *  LineParserSubscriber subscriber = new LineParserSubscriber(pattern);
+     *  HttpResponse<List<String>> response = client.send(request,
+     *      BodyHandlers.fromLineSubscriber(subscriber, s -> s.getMatchingLines(), "\n"));
+     *  if (response.statusCode() != 200) {
+     *      System.err.printf("ERROR: %d status received%n", response.statusCode());
+     *  } }</pre>
+     *
+     */
+    void fromLineSubscriber2() throws IOException, InterruptedException {
+        // A LineParserSubscriber that implements Flow.Subscriber<String>
+        // and accumulates lines that match a particular pattern
+        Pattern pattern = p;
+        LineParserSubscriber subscriber = new LineParserSubscriber(pattern);
+        HttpResponse<List<String>> response = client.send(request,
+                BodyHandlers.fromLineSubscriber(subscriber, s -> s.getMatchingLines(), "\n"));
+        if (response.statusCode() != 200) {
+            System.err.printf("ERROR: %d status received%n", response.statusCode());
+        }
+    }
+
+    static final class PrintSubscriber implements Flow.Subscriber<String> {
+        final PrintStream out;
+        PrintSubscriber(PrintStream out) {
+            this.out = out;
+        }
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            subscription.request(Long.MAX_VALUE);
+        }
+        @Override
+        public void onNext(String item) {
+            out.println(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            throwable.printStackTrace();
+        }
+        @Override
+        public void onComplete() {
+        }
+    }
+
+    static final class LineParserSubscriber implements Flow.Subscriber<String> {
+        final Pattern pattern;
+        final CopyOnWriteArrayList<String> matches = new CopyOnWriteArrayList<>();
+        LineParserSubscriber(Pattern pattern) {
+            this.pattern = pattern;
+        }
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            subscription.request(Long.MAX_VALUE);
+        }
+        @Override
+        public void onNext(String item) {
+            if (pattern.matcher(item).matches()) {
+                matches.add(item);
+            }
+        }
+        @Override
+        public void onError(Throwable throwable) {
+            throwable.printStackTrace();
+        }
+        @Override
+        public void onComplete() {
+        }
+
+        public List<String> getMatchingLines() {
+            return Collections.unmodifiableList(matches);
+        }
+    }
+
+}
--- a/test/jdk/java/net/httpclient/examples/WebSocketExample.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/examples/WebSocketExample.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -26,8 +26,9 @@
 import java.net.URI;
 import java.util.concurrent.CompletableFuture;
 
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.WebSocket;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.util.concurrent.CompletionStage;
 
 /*
  * THE CONTENTS OF THIS FILE HAVE TO BE IN SYNC WITH THE EXAMPLES USED IN THE
@@ -56,4 +57,26 @@
         CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
                 .buildAsync(URI.create("ws://websocket.example.com"), listener);
     }
+
+    public void requestExample() {
+        WebSocket.Listener listener = new WebSocket.Listener() {
+
+            StringBuilder text = new StringBuilder();
+
+            @Override
+            public CompletionStage<?> onText(WebSocket webSocket,
+                                             CharSequence message,
+                                             boolean last) {
+                text.append(message);
+                if (last) {
+                    processCompleteTextMessage(text);
+                    text = new StringBuilder();
+                }
+                webSocket.request(1);
+                return null;
+            }
+        };
+    }
+
+    static void processCompleteTextMessage(CharSequence result) { }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/BadHeadersTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,326 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @modules java.base/sun.net.www.http
+ *          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
+ * @run testng/othervm -Djdk.internal.httpclient.debug=true BadHeadersTest
+ */
+
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.Pair;
+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;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+
+import static java.util.List.of;
+import static jdk.internal.net.http.common.Pair.pair;
+import static org.testng.Assert.assertThrows;
+
+// Code copied from ContinuationFrameTest
+public class BadHeadersTest {
+
+    private static final List<List<Pair<String, String>>> BAD_HEADERS = of(
+            of(pair(":status", "200"),  pair(":hello", "GET")),                      // Unknown pseudo-header
+            of(pair(":status", "200"),  pair("hell o", "value")),                    // Space in the name
+            of(pair(":status", "200"),  pair("hello", "line1\r\n  line2\r\n")),      // Multiline value
+            of(pair(":status", "200"),  pair("hello", "DE" + ((char) 0x7F) + "L")),  // Bad byte in value
+            of(pair("hello", "world!"), pair(":status", "200"))                      // Pseudo header is not the first one
+    );
+
+    SSLContext sslContext;
+    Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
+    Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
+    String http2URI;
+    String https2URI;
+
+    /**
+     * A function that returns a list of 1) a HEADERS frame ( with an empty
+     * payload ), and 2) a CONTINUATION frame with the actual headers.
+     */
+    static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> oneContinuation =
+            (Integer streamid, List<ByteBuffer> encodedHeaders) -> {
+                List<ByteBuffer> empty =  of(ByteBuffer.wrap(new byte[0]));
+                HeadersFrame hf = new HeadersFrame(streamid, 0, empty);
+                ContinuationFrame cf = new ContinuationFrame(streamid,
+                                                             HeaderFrame.END_HEADERS,
+                                                             encodedHeaders);
+                return of(hf, cf);
+            };
+
+    /**
+     * A function that returns a list of a HEADERS frame followed by a number of
+     * CONTINUATION frames. Each frame contains just a single byte of payload.
+     */
+    static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> byteAtATime =
+            (Integer streamid, List<ByteBuffer> encodedHeaders) -> {
+                assert encodedHeaders.get(0).hasRemaining();
+                List<Http2Frame> frames = new ArrayList<>();
+                ByteBuffer hb = ByteBuffer.wrap(new byte[] {encodedHeaders.get(0).get()});
+                HeadersFrame hf = new HeadersFrame(streamid, 0, hb);
+                frames.add(hf);
+                for (ByteBuffer bb : encodedHeaders) {
+                    while (bb.hasRemaining()) {
+                        List<ByteBuffer> data = of(ByteBuffer.wrap(new byte[] {bb.get()}));
+                        ContinuationFrame cf = new ContinuationFrame(streamid, 0, data);
+                        frames.add(cf);
+                    }
+                }
+                frames.get(frames.size() - 1).setFlag(HeaderFrame.END_HEADERS);
+                return frames;
+            };
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][] {
+                { http2URI,  false, oneContinuation },
+                { https2URI, false, oneContinuation },
+                { http2URI,  true,  oneContinuation },
+                { https2URI, true,  oneContinuation },
+
+                { http2URI,  false, byteAtATime },
+                { https2URI, false, byteAtATime },
+                { http2URI,  true,  byteAtATime },
+                { https2URI, true,  byteAtATime },
+        };
+    }
+
+
+    @Test(dataProvider = "variants")
+    void test(String uri,
+              boolean sameClient,
+              BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
+            throws Exception
+    {
+        CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
+
+        HttpClient client = null;
+        for (int i=0; i< BAD_HEADERS.size(); i++) {
+            if (!sameClient || client == null)
+                client = HttpClient.newBuilder().sslContext(sslContext).build();
+
+            HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
+                    .POST(BodyPublishers.ofString("Hello there!"))
+                    .build();
+            final HttpClient cc = client;
+            if (i % 2 == 0) {
+                assertThrows(IOException.class, () -> cc.send(request, BodyHandlers.ofString()));
+            } else {
+                Throwable t = null;
+                try {
+                    cc.sendAsync(request, BodyHandlers.ofString()).join();
+                } catch (Throwable t0) {
+                    t = t0;
+                }
+                if (t == null) {
+                    throw new AssertionError("An exception was expected");
+                }
+                if (t instanceof CompletionException) {
+                    Throwable c = t.getCause();
+                    if (!(c instanceof IOException)) {
+                        throw new AssertionError("Unexpected exception", c);
+                    }
+                } else if (!(t instanceof IOException)) {
+                    throw new AssertionError("Unexpected exception", t);
+                }
+            }
+        }
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        http2TestServer = new Http2TestServer("localhost", false, 0) {
+            @Override
+            protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer,
+                                                                 Socket socket,
+                                                                 Http2TestExchangeSupplier exchangeSupplier)
+                    throws IOException {
+                return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier) {
+                    @Override
+                    protected HttpHeadersImpl createNewResponseHeaders() {
+                        return new OrderedHttpHeaders();
+                    }
+                };
+            }
+        };
+        http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
+        int port = http2TestServer.getAddress().getPort();
+        http2URI = "http://localhost:" + port + "/http2/echo";
+
+        https2TestServer = new Http2TestServer("localhost", true, 0){
+            @Override
+            protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer,
+                                                                 Socket socket,
+                                                                 Http2TestExchangeSupplier exchangeSupplier)
+                    throws IOException {
+                return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier) {
+                    @Override
+                    protected HttpHeadersImpl createNewResponseHeaders() {
+                        return new OrderedHttpHeaders();
+                    }
+                };
+            }
+        };
+        https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
+        port = https2TestServer.getAddress().getPort();
+        https2URI = "https://localhost:" + port + "/https2/echo";
+
+        // Override the default exchange supplier with a custom one to enable
+        // particular test scenarios
+        http2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new);
+        https2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new);
+
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    static class Http2EchoHandler implements Http2Handler {
+
+        private final AtomicInteger requestNo = new AtomicInteger();
+
+        @Override
+        public void handle(Http2TestExchange t) throws IOException {
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                byte[] bytes = is.readAllBytes();
+                int i = requestNo.incrementAndGet();
+                List<Pair<String, String>> p = BAD_HEADERS.get(i % BAD_HEADERS.size());
+                p.forEach(h -> t.getResponseHeaders().addHeader(h.first, h.second));
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+
+    // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to
+    // allow headers to be sent with a number of CONTINUATION frames.
+    static class CFTHttp2TestExchange extends Http2TestExchangeImpl {
+        static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier;
+
+        static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) {
+            headerFrameSupplier = hfs;
+        }
+
+        CFTHttp2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders,
+                             HttpHeadersImpl rspheaders, URI uri, InputStream is,
+                             SSLSession sslSession, BodyOutputStream os,
+                             Http2TestServerConnection conn, boolean pushAllowed) {
+            super(streamid, method, reqheaders, rspheaders, uri, is, sslSession,
+                  os, conn, pushAllowed);
+
+        }
+
+        @Override
+        public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
+            List<ByteBuffer> encodeHeaders = conn.encodeHeaders(rspheaders);
+            List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders);
+            assert headerFrames.size() > 0;  // there must always be at least 1
+
+            if (responseLength < 0) {
+                headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM);
+                os.closeInternal();
+            }
+
+            for (Http2Frame f : headerFrames)
+                conn.outputQ.put(f);
+
+            os.goodToGo();
+            System.err.println("Sent response headers " + rCode);
+        }
+    }
+
+    /*
+     * Use carefully. This class might not be suitable outside this test's
+     * context. Pay attention working with multi Map view returned from map().
+     * The reason is that header names must be lower-cased prior to any
+     * operation that depends on whether or not the map contains a specific
+     * element.
+     */
+    private static class OrderedHttpHeaders extends HttpHeadersImpl {
+
+        private final Map<String, List<String>> map = new LinkedHashMap<>();
+
+        @Override
+        public void addHeader(String name, String value) {
+            super.addHeader(name.toLowerCase(Locale.ROOT), value);
+        }
+
+        @Override
+        public void setHeader(String name, String value) {
+            super.setHeader(name.toLowerCase(Locale.ROOT), value);
+        }
+
+        @Override
+        protected Map<String, List<String>> headersMap() {
+            return map;
+        }
+
+        @Override
+        protected HttpHeadersImpl newDeepCopy() {
+            return new OrderedHttpHeaders();
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/http2/BasicTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/BasicTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,30 +27,29 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
  */
 
 import java.io.IOException;
 import java.net.*;
-import jdk.incubator.http.*;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
 import javax.net.ssl.*;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.nio.file.*;
 import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromFile;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asFile;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
-
 import org.testng.annotations.Test;
+import static java.net.http.HttpClient.Version.HTTP_2;
 
 @Test
 public class BasicTest {
@@ -77,9 +76,9 @@
             httpsServer.addHandler(new Http2EchoHandler(), "/");
 
             httpsPort = httpsServer.getAddress().getPort();
-            httpURIString = "http://127.0.0.1:" + httpPort + "/foo/";
-            pingURIString = "http://127.0.0.1:" + httpPort + "/ping/";
-            httpsURIString = "https://127.0.0.1:" + httpsPort + "/bar/";
+            httpURIString = "http://localhost:" + httpPort + "/foo/";
+            pingURIString = "http://localhost:" + httpPort + "/ping/";
+            httpsURIString = "https://localhost:" + httpsPort + "/bar/";
 
             httpServer.start();
             httpsServer.start();
@@ -203,12 +202,12 @@
         HttpClient client = getClient();
         Path src = TestUtil.getAFile(FILESIZE * 4);
         HttpRequest req = HttpRequest.newBuilder(uri)
-                                     .POST(fromFile(src))
+                                     .POST(BodyPublishers.ofFile(src))
                                      .build();
 
         Path dest = Paths.get("streamtest.txt");
         dest.toFile().delete();
-        CompletableFuture<Path> response = client.sendAsync(req, asFile(dest))
+        CompletableFuture<Path> response = client.sendAsync(req, BodyHandlers.ofFile(dest))
                 .thenApply(resp -> {
                     if (resp.statusCode() != 200)
                         throw new RuntimeException();
@@ -230,10 +229,10 @@
                 t.sendResponseHeaders(500, -1);
             }
         }), "/");
-        URI u = new URI("https://127.0.0.1:"+httpsPort+"/foo");
+        URI u = new URI("https://localhost:"+httpsPort+"/foo");
         HttpClient client = getClient();
         HttpRequest req = HttpRequest.newBuilder(u).build();
-        HttpResponse<String> resp = client.send(req, asString());
+        HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());
         int stat = resp.statusCode();
         if (stat != 200) {
             throw new RuntimeException("paramsTest failed "
@@ -250,9 +249,9 @@
 
         HttpClient client = getClient();
         HttpRequest req = HttpRequest.newBuilder(uri)
-                                     .POST(fromString(SIMPLE_STRING))
+                                     .POST(BodyPublishers.ofString(SIMPLE_STRING))
                                      .build();
-        HttpResponse<String> response = client.send(req, asString());
+        HttpResponse<String> response = client.send(req, BodyHandlers.ofString());
         checkStatus(200, response.statusCode());
         String responseBody = response.body();
         HttpHeaders h = response.headers();
@@ -270,10 +269,10 @@
         CompletableFuture[] responses = new CompletableFuture[LOOPS];
         final Path source = TestUtil.getAFile(FILESIZE);
         HttpRequest request = HttpRequest.newBuilder(uri)
-                                         .POST(fromFile(source))
+                                         .POST(BodyPublishers.ofFile(source))
                                          .build();
         for (int i = 0; i < LOOPS; i++) {
-            responses[i] = client.sendAsync(request, asFile(tempFile()))
+            responses[i] = client.sendAsync(request, BodyHandlers.ofFile(tempFile()))
                 //.thenApply(resp -> compareFiles(resp.body(), source));
                 .thenApply(resp -> {
                     System.out.printf("Resp status %d body size %d\n",
--- a/test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -25,9 +25,9 @@
  * @test
  * @summary Test for CONTINUATION frame handling
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
@@ -44,23 +44,23 @@
 import java.util.function.BiFunction;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSession;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.frame.ContinuationFrame;
-import jdk.incubator.http.internal.frame.HeaderFrame;
-import jdk.incubator.http.internal.frame.HeadersFrame;
-import jdk.incubator.http.internal.frame.Http2Frame;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+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;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static java.lang.System.out;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.net.http.HttpClient.Version.HTTP_2;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
@@ -139,13 +139,13 @@
                 client = HttpClient.newBuilder().sslContext(sslContext).build();
 
             HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
-                                             .POST(fromString("Hello there!"))
+                                             .POST(BodyPublishers.ofString("Hello there!"))
                                              .build();
             HttpResponse<String> resp;
             if (i % 2 == 0) {
-                resp = client.send(request, asString());
+                resp = client.send(request, BodyHandlers.ofString());
             } else {
-                resp = client.sendAsync(request, asString()).join();
+                resp = client.sendAsync(request, BodyHandlers.ofString()).join();
             }
 
             out.println("Got response: " + resp);
@@ -163,15 +163,15 @@
         if (sslContext == null)
             throw new AssertionError("Unexpected null sslContext");
 
-        http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+        http2TestServer = new Http2TestServer("localhost", false, 0);
         http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
         int port = http2TestServer.getAddress().getPort();
-        http2URI = "http://127.0.0.1:" + port + "/http2/echo";
+        http2URI = "http://localhost:" + port + "/http2/echo";
 
-        https2TestServer = new Http2TestServer("127.0.0.1", true, 0);
+        https2TestServer = new Http2TestServer("localhost", true, 0);
         https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo");
         port = https2TestServer.getAddress().getPort();
-        https2URI = "https://127.0.0.1:" + port + "/https2/echo";
+        https2URI = "https://localhost:" + port + "/https2/echo";
 
         // Override the default exchange supplier with a custom one to enable
         // particular test scenarios
@@ -194,9 +194,9 @@
             try (InputStream is = t.getRequestBody();
                  OutputStream os = t.getResponseBody()) {
                 byte[] bytes = is.readAllBytes();
-                t.getResponseHeaders().addHeader("just some", "noise");
-                t.getResponseHeaders().addHeader("to add ", "payload in ");
-                t.getResponseHeaders().addHeader("the header", "frames");
+                t.getResponseHeaders().addHeader("justSome", "Noise");
+                t.getResponseHeaders().addHeader("toAdd", "payload in");
+                t.getResponseHeaders().addHeader("theHeader", "Frames");
                 t.sendResponseHeaders(200, bytes.length);
                 os.write(bytes);
             }
--- a/test/jdk/java/net/httpclient/http2/ErrorTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/ErrorTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,9 +27,9 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
@@ -37,17 +37,17 @@
 
 import java.io.IOException;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLParameters;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ExecutorService;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
+import static java.net.http.HttpClient.Version.HTTP_2;
 
 import org.testng.annotations.Test;
 
@@ -85,18 +85,18 @@
                                               serverContext);
             httpsServer.addHandler(new Http2EchoHandler(), "/");
             int httpsPort = httpsServer.getAddress().getPort();
-            String httpsURIString = "https://127.0.0.1:" + httpsPort + "/bar/";
+            String httpsURIString = "https://localhost:" + httpsPort + "/bar/";
 
             httpsServer.start();
             URI uri = URI.create(httpsURIString);
             System.err.println("Request to " + uri);
 
             HttpRequest req = HttpRequest.newBuilder(uri)
-                                    .POST(fromString(SIMPLE_STRING))
+                                    .POST(BodyPublishers.ofString(SIMPLE_STRING))
                                     .build();
             HttpResponse response;
             try {
-                response = client.send(req, discard(null));
+                response = client.send(req, BodyHandlers.discarding());
                 throw new RuntimeException("Unexpected response: " + response);
             } catch (IOException e) {
                 System.err.println("Caught Expected IOException: " + e);
--- a/test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/FixedThreadPoolTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,24 +27,21 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
  */
 
 import java.net.*;
-import jdk.incubator.http.*;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
+import java.net.http.*;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandlers;
 import javax.net.ssl.*;
 import java.nio.file.*;
 import java.util.concurrent.*;
 import jdk.testlibrary.SimpleSSLContext;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromFile;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asFile;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
-
+import static java.net.http.HttpClient.Version.HTTP_2;
 import org.testng.annotations.Test;
 
 @Test
@@ -70,8 +67,8 @@
             httpsServer.addHandler(new Http2EchoHandler(), "/");
 
             httpsPort = httpsServer.getAddress().getPort();
-            httpURIString = "http://127.0.0.1:" + httpPort + "/foo/";
-            httpsURIString = "https://127.0.0.1:" + httpsPort + "/bar/";
+            httpURIString = "http://localhost:" + httpPort + "/foo/";
+            httpsURIString = "https://localhost:" + httpsPort + "/bar/";
 
             httpServer.start();
             httpsServer.start();
@@ -164,12 +161,12 @@
         HttpClient client = getClient();
         Path src = TestUtil.getAFile(FILESIZE * 4);
         HttpRequest req = HttpRequest.newBuilder(uri)
-                                     .POST(fromFile(src))
+                                     .POST(BodyPublishers.ofFile(src))
                                      .build();
 
         Path dest = Paths.get("streamtest.txt");
         dest.toFile().delete();
-        CompletableFuture<Path> response = client.sendAsync(req, asFile(dest))
+        CompletableFuture<Path> response = client.sendAsync(req, BodyHandlers.ofFile(dest))
                 .thenApply(resp -> {
                     if (resp.statusCode() != 200)
                         throw new RuntimeException();
@@ -195,10 +192,10 @@
         }), "/");
         server.start();
         int port = server.getAddress().getPort();
-        URI u = new URI("https://127.0.0.1:"+port+"/foo");
+        URI u = new URI("https://localhost:"+port+"/foo");
         HttpClient client = getClient();
         HttpRequest req = HttpRequest.newBuilder(u).build();
-        HttpResponse<String> resp = client.sendAsync(req, asString()).get();
+        HttpResponse<String> resp = client.sendAsync(req, BodyHandlers.ofString()).get();
         int stat = resp.statusCode();
         if (stat != 200) {
             throw new RuntimeException("paramsTest failed "
@@ -214,9 +211,9 @@
 
         HttpClient client = getClient();
         HttpRequest req = HttpRequest.newBuilder(uri)
-                                     .POST(fromString(SIMPLE_STRING))
+                                     .POST(BodyPublishers.ofString(SIMPLE_STRING))
                                      .build();
-        HttpResponse<String> response = client.sendAsync(req, asString()).get();
+        HttpResponse<String> response = client.sendAsync(req, BodyHandlers.ofString()).get();
         HttpHeaders h = response.headers();
 
         checkStatus(200, response.statusCode());
@@ -232,10 +229,10 @@
         CompletableFuture[] responses = new CompletableFuture[LOOPS];
         final Path source = TestUtil.getAFile(FILESIZE);
         HttpRequest request = HttpRequest.newBuilder(uri)
-                                         .POST(fromFile(source))
+                                         .POST(BodyPublishers.ofFile(source))
                                          .build();
         for (int i = 0; i < LOOPS; i++) {
-            responses[i] = client.sendAsync(request, asFile(tempFile()))
+            responses[i] = client.sendAsync(request, BodyHandlers.ofFile(tempFile()))
                 //.thenApply(resp -> compareFiles(resp.body(), source));
                 .thenApply(resp -> {
                     System.out.printf("Resp status %d body size %d\n",
--- a/test/jdk/java/net/httpclient/http2/HpackBinaryTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/HpackBinaryTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -24,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/SpecHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/TestHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/HpackCircularBufferDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/SpecHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/TestHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/HpackDecoderDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/SpecHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/TestHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/HpackEncoderDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/SpecHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/TestHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/HpackHeaderTableDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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,12 +24,12 @@
 /*
  * @test
  * @bug 8153353
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  *          jdk.localedata
  * @key randomness
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/SpecHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/TestHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/HpackHuffmanDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/SpecHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/TestHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/HpackTestHelper.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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,11 +24,11 @@
 /*
  * @test
  * @bug 8153353
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @modules java.net.http/jdk.internal.net.http.hpack
  * @key randomness
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/SpecHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/TestHelper.java
- * @compile/module=jdk.incubator.httpclient jdk/incubator/http/internal/hpack/BuffersTestingKit.java
- * @run testng/othervm jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/ImplicitPushCancel.java	Tue Apr 17 08:54:17 2018 -0700
@@ -26,10 +26,13 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
- * @run testng/othervm -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=errors,requests,responses,trace ImplicitPushCancel
+ *          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
  */
 
 import java.io.ByteArrayInputStream;
@@ -39,13 +42,16 @@
 import java.net.URI;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.MultiMapResult;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
@@ -79,7 +85,7 @@
         server.start();
         int port = server.getAddress().getPort();
         System.err.println("Server listening on port " + port);
-        uri = new URI("http://127.0.0.1:" + port + "/foo/a/b/c");
+        uri = new URI("http://localhost:" + port + "/foo/a/b/c");
     }
 
     @AfterTest
@@ -101,21 +107,26 @@
     public void test() throws Exception {
         HttpClient client = HttpClient.newHttpClient();
 
-        client.sendAsync(HttpRequest.newBuilder(uri).build(), BodyHandler.asString())
+        client.sendAsync(HttpRequest.newBuilder(uri).build(), BodyHandlers.ofString())
                 .thenApply(ImplicitPushCancel::assert200ResponseCode)
                 .thenApply(HttpResponse::body)
                 .thenAccept(body -> body.equals(MAIN_RESPONSE_BODY))
                 .join();
 
-        MultiMapResult<String> map = client.sendAsync(
+        ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<String>>> promises
+                = new ConcurrentHashMap<>();
+        PushPromiseHandler<String> pph = PushPromiseHandler
+                .of((r) -> BodyHandlers.ofString(), promises);
+        HttpResponse<String> main = client.sendAsync(
                 HttpRequest.newBuilder(uri).build(),
-                HttpResponse.MultiSubscriber.asMap(
-                       (req) -> Optional.of(HttpResponse.BodyHandler.asString()))
-                ).join();
+                BodyHandlers.ofString(),
+                pph)
+                .join();
 
-        map.entrySet().stream().forEach(e -> System.out.println(e.getKey() + ":" + e.getValue().join().body()));
+        promises.entrySet().stream().forEach(e -> System.out.println(e.getKey() + ":" + e.getValue().join().body()));
 
-        map.entrySet().stream().forEach(entry -> {
+        promises.putIfAbsent(main.request(), CompletableFuture.completedFuture(main));
+        promises.entrySet().stream().forEach(entry -> {
             HttpRequest request = entry.getKey();
             HttpResponse<String> response = entry.getValue().join();
             assertEquals(response.statusCode(), 200);
--- a/test/jdk/java/net/httpclient/http2/ProxyTest2.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/ProxyTest2.java	Tue Apr 17 08:54:17 2018 -0700
@@ -49,9 +49,9 @@
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSession;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import jdk.testlibrary.SimpleSSLContext;
 import java.util.concurrent.*;
 
@@ -60,12 +60,12 @@
  * @bug 8181422
  * @summary  Verifies that you can access an HTTP/2 server over HTTPS by
  *           tunnelling through an HTTP/1.1 proxy.
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  * @library /lib/testlibrary server
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.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
@@ -146,7 +146,7 @@
 
             System.out.println("Sending request with HttpClient");
             HttpResponse<String> response
-                = client.send(request, HttpResponse.BodyHandler.asString());
+                = client.send(request, HttpResponse.BodyHandlers.ofString());
             System.out.println("Got response");
             String resp = response.body();
             System.out.println("Received: " + resp);
@@ -165,19 +165,25 @@
         final ServerSocket ss;
         final boolean DEBUG = false;
         final Http2TestServer serverImpl;
+        final CopyOnWriteArrayList<CompletableFuture<Void>> connectionCFs
+                = new CopyOnWriteArrayList<>();
+        private volatile boolean stopped;
         TunnelingProxy(Http2TestServer serverImpl) throws IOException {
             this.serverImpl = serverImpl;
             ss = new ServerSocket();
             accept = new Thread(this::accept);
+            accept.setDaemon(true);
         }
 
         void start() throws IOException {
+            ss.setReuseAddress(false);
             ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             accept.start();
         }
 
         // Pipe the input stream to the output stream.
-        private synchronized Thread pipe(InputStream is, OutputStream os, char tag) {
+        private synchronized Thread pipe(InputStream is, OutputStream os,
+                                         char tag, CompletableFuture<Void> end) {
             return new Thread("TunnelPipe("+tag+")") {
                 @Override
                 public void run() {
@@ -197,13 +203,15 @@
                         }
                     } catch (IOException ex) {
                         if (DEBUG) ex.printStackTrace(System.out);
+                    } finally {
+                        end.complete(null);
                     }
                 }
             };
         }
 
         public InetSocketAddress getAddress() {
-            return new InetSocketAddress(ss.getInetAddress(), ss.getLocalPort());
+            return new InetSocketAddress( InetAddress.getLoopbackAddress(), ss.getLocalPort());
         }
 
         // This is a bit shaky. It doesn't handle continuation
@@ -228,18 +236,14 @@
         public void accept() {
             Socket clientConnection = null;
             try {
-                while (true) {
+                while (!stopped) {
                     System.out.println("Tunnel: Waiting for client");
-                    Socket previous = clientConnection;
+                    Socket toClose;
                     try {
-                        clientConnection = ss.accept();
+                        toClose = clientConnection = ss.accept();
                     } catch (IOException io) {
                         if (DEBUG) io.printStackTrace(System.out);
                         break;
-                    } finally {
-                        // we have only 1 client at a time, so it is safe
-                        // to close the previous connection here
-                        if (previous != null) previous.close();
                     }
                     System.out.println("Tunnel: Client accepted");
                     Socket targetConnection = null;
@@ -259,40 +263,59 @@
                         // signals the end of all headers.
                         while(!requestLine.equals("")) {
                             System.out.println("Tunnel: Reading header: "
-                                               + (requestLine = readLine(ccis)));
+                                    + (requestLine = readLine(ccis)));
                         }
 
                         // Open target connection
                         targetConnection = new Socket(
-                                serverImpl.getAddress().getAddress(),
+                                InetAddress.getLoopbackAddress(),
                                 serverImpl.getAddress().getPort());
 
                         // Then send the 200 OK response to the client
                         System.out.println("Tunnel: Sending "
-                                           + "HTTP/1.1 200 OK\r\n\r\n");
+                                + "HTTP/1.1 200 OK\r\n\r\n");
                         pw.print("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
                         pw.flush();
                     } else {
-                        // This should not happen.
-                        throw new IOException("Tunnel: Unexpected status line: "
-                                           + requestLine);
+                        // This should not happen. If it does then just print an
+                        // error - both on out and err, and close the accepted
+                        // socket
+                        System.out.println("WARNING: Tunnel: Unexpected status line: "
+                                + requestLine + " received by "
+                                + ss.getLocalSocketAddress()
+                                + " from "
+                                + toClose.getRemoteSocketAddress()
+                                + " - closing accepted socket");
+                        // Print on err
+                        System.err.println("WARNING: Tunnel: Unexpected status line: "
+                                + requestLine + " received by "
+                                + ss.getLocalSocketAddress()
+                                + " from "
+                                + toClose.getRemoteSocketAddress());
+                        // close accepted socket.
+                        toClose.close();
+                        System.err.println("Tunnel: accepted socket closed.");
+                        continue;
                     }
 
                     // Pipe the input stream of the client connection to the
                     // output stream of the target connection and conversely.
                     // Now the client and target will just talk to each other.
                     System.out.println("Tunnel: Starting tunnel pipes");
-                    Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+');
-                    Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-');
+                    CompletableFuture<Void> end, end1, end2;
+                    Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+',
+                            end1 = new CompletableFuture<>());
+                    Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-',
+                            end2 = new CompletableFuture<>());
+                    end = CompletableFuture.allOf(end1, end2);
+                    end.whenComplete(
+                            (r,t) -> {
+                                try { toClose.close(); } catch (IOException x) { }
+                                finally {connectionCFs.remove(end);}
+                            });
+                    connectionCFs.add(end);
                     t1.start();
                     t2.start();
-
-                    // We have only 1 client... wait until it has finished before
-                    // accepting a new connection request.
-                    // System.out.println("Tunnel: Waiting for pipes to close");
-                    t1.join();
-                    t2.join();
-                    System.out.println("Tunnel: Done - waiting for next client");
                 }
             } catch (Throwable ex) {
                 try {
@@ -301,10 +324,14 @@
                     ex.addSuppressed(ex1);
                 }
                 ex.printStackTrace(System.err);
+            } finally {
+                System.out.println("Tunnel: exiting (stopped=" + stopped + ")");
+                connectionCFs.forEach(cf -> cf.complete(null));
             }
         }
 
-        void stop() throws IOException {
+        public void stop() throws IOException {
+            stopped = true;
             ss.close();
         }
 
--- a/test/jdk/java/net/httpclient/http2/RedirectTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/RedirectTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,23 +27,28 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
- * @run testng/othervm -Djdk.httpclient.HttpClient.log=frames,ssl,requests,responses,errors RedirectTest
+ *          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
+ *      -Djdk.internal.httpclient.debug=true
+ *      RedirectTest
  */
 
-import java.net.*;
-import jdk.incubator.http.*;
-import java.util.Optional;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import java.util.concurrent.*;
 import java.util.function.*;
 import java.util.Arrays;
 import java.util.Iterator;
 import org.testng.annotations.Test;
-import static jdk.incubator.http.HttpClient.Version.HTTP_2;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.net.http.HttpClient.Version.HTTP_2;
 
 public class RedirectTest {
     static int httpPort;
@@ -96,11 +101,11 @@
             // urls are accessed in sequence below. The first two are on
             // different servers. Third on same server as second. So, the
             // client should use the same http connection.
-            httpURIString = "http://127.0.0.1:" + httpPort + "/foo/";
+            httpURIString = "http://localhost:" + httpPort + "/foo/";
             httpURI = URI.create(httpURIString);
-            altURIString1 = "http://127.0.0.1:" + httpPort + "/redir";
+            altURIString1 = "http://localhost:" + httpPort + "/redir";
             altURI1 = URI.create(altURIString1);
-            altURIString2 = "http://127.0.0.1:" + httpPort + "/redir_again";
+            altURIString2 = "http://localhost:" + httpPort + "/redir_again";
             altURI2 = URI.create(altURIString2);
 
             Redirector r = new Redirector(sup(altURIString1, altURIString2));
@@ -182,9 +187,9 @@
 
         HttpClient client = getClient();
         HttpRequest req = HttpRequest.newBuilder(uri)
-                                     .POST(fromString(SIMPLE_STRING))
+                                     .POST(BodyPublishers.ofString(SIMPLE_STRING))
                                      .build();
-        CompletableFuture<HttpResponse<String>> cf = client.sendAsync(req, asString());
+        CompletableFuture<HttpResponse<String>> cf = client.sendAsync(req, BodyHandlers.ofString());
         HttpResponse<String> response = cf.join();
 
         checkStatus(200, response.statusCode());
--- a/test/jdk/java/net/httpclient/http2/ServerPush.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/ServerPush.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,92 +27,280 @@
  * @library /lib/testlibrary server
  * @build jdk.testlibrary.SimpleSSLContext
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
- * @run testng/othervm -Djdk.internal.httpclient.hpack.debug=true -Djdk.internal.httpclient.debug=true -Djdk.httpclient.HttpClient.log=errors,requests,responses ServerPush
+ *          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
  */
 
 import java.io.*;
 import java.net.*;
+import java.nio.ByteBuffer;
 import java.nio.file.*;
-import java.nio.file.attribute.*;
-import jdk.incubator.http.*;
-import jdk.incubator.http.HttpResponse.MultiSubscriber;
-import jdk.incubator.http.HttpResponse.BodyHandler;
+import java.net.http.*;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.net.http.HttpResponse.PushPromiseHandler;
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.function.Consumer;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.*;
+
 
 public class ServerPush {
 
-    static ExecutorService e = Executors.newCachedThreadPool();
-
     static final int LOOPS = 13;
     static final int FILE_SIZE = 512 * 1024 + 343;
 
     static Path tempFile;
 
+    Http2TestServer server;
+    URI uri;
+
+    @BeforeTest
+    public void setup() throws Exception {
+        tempFile = TestUtil.getAFile(FILE_SIZE);
+        server = new Http2TestServer(false, 0);
+        server.addHandler(new PushHandler(tempFile, LOOPS), "/");
+        System.out.println("Using temp file:" + tempFile);
+
+        System.err.println("Server listening on port " + server.getAddress().getPort());
+        server.start();
+        int port = server.getAddress().getPort();
+        uri = new URI("http://localhost:" + port + "/foo/a/b/c");
+    }
+
+    @AfterTest
+    public void teardown() {
+        server.stop();
+    }
+
+    // Test 1 - custom written push promise handler, everything as a String
     @Test
-    public static void test() throws Exception {
-        Http2TestServer server = null;
-        final Path dir = Files.createTempDirectory("serverPush");
-        try {
-            server = new Http2TestServer(false, 0);
-            server.addHandler(new PushHandler(FILE_SIZE, LOOPS), "/");
-            tempFile = TestUtil.getAFile(FILE_SIZE);
+    public void testTypeString() throws Exception {
+        String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
+        ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<String>>>
+                resultMap = new ConcurrentHashMap<>();
+
+        PushPromiseHandler<String> pph = (initial, pushRequest, acceptor) -> {
+            BodyHandler<String> s = BodyHandlers.ofString(UTF_8);
+            CompletableFuture<HttpResponse<String>> cf = acceptor.apply(s);
+            resultMap.put(pushRequest, cf);
+        };
+
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+        CompletableFuture<HttpResponse<String>> cf =
+                client.sendAsync(request, BodyHandlers.ofString(UTF_8), pph);
+        cf.join();
+        resultMap.put(request, cf);
+        System.err.println("results.size: " + resultMap.size());
+        for (HttpRequest r : resultMap.keySet()) {
+            HttpResponse<String> response = resultMap.get(r).join();
+            assertEquals(response.statusCode(), 200);
+            assertEquals(response.body(), tempFileAsString);
+        }
+        assertEquals(resultMap.size(), LOOPS + 1);
+    }
 
-            System.err.println("Server listening on port " + server.getAddress().getPort());
-            server.start();
-            int port = server.getAddress().getPort();
+    // Test 2 - of(...) populating the given Map, everything as a String
+    @Test
+    public void testTypeStringOfMap() throws Exception {
+        String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
+        ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<String>>>
+                resultMap = new ConcurrentHashMap<>();
+
+        PushPromiseHandler<String> pph =
+                PushPromiseHandler.of(pushPromise -> BodyHandlers.ofString(UTF_8),
+                                      resultMap);
 
-            // use multi-level path
-            URI uri = new URI("http://127.0.0.1:" + port + "/foo/a/b/c");
-            HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+        CompletableFuture<HttpResponse<String>> cf =
+                client.sendAsync(request, BodyHandlers.ofString(UTF_8), pph);
+        cf.join();
+        resultMap.put(request, cf);
+        System.err.println("results.size: " + resultMap.size());
+        for (HttpRequest r : resultMap.keySet()) {
+            HttpResponse<String> response = resultMap.get(r).join();
+            assertEquals(response.statusCode(), 200);
+            assertEquals(response.body(), tempFileAsString);
+        }
+        assertEquals(resultMap.size(), LOOPS + 1);
+    }
+
+    // --- Path ---
 
-            CompletableFuture<MultiMapResult<Path>> cf =
-                HttpClient.newBuilder().version(HttpClient.Version.HTTP_2)
-                    .executor(e).build().sendAsync(
-                        request, MultiSubscriber.asMap((req) -> {
-                            URI u = req.uri();
-                            Path path = Paths.get(dir.toString(), u.getPath());
-                            try {
-                                Files.createDirectories(path.getParent());
-                            } catch (IOException ee) {
-                                throw new UncheckedIOException(ee);
-                            }
-                            return Optional.of(BodyHandler.asFile(path));
-                        }
-                    ));
-            MultiMapResult<Path> results = cf.get();
+    static final Path dir = Paths.get(".", "serverPush");
+    static BodyHandler<Path> requestToPath(HttpRequest req) {
+        URI u = req.uri();
+        Path path = Paths.get(dir.toString(), u.getPath());
+        try {
+            Files.createDirectories(path.getParent());
+        } catch (IOException ee) {
+            throw new UncheckedIOException(ee);
+        }
+        return BodyHandlers.ofFile(path);
+    }
+
+    // Test 3 - custom written push promise handler, everything as a Path
+    @Test
+    public void testTypePath() throws Exception {
+        String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
+        ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Path>>> resultsMap
+                = new ConcurrentHashMap<>();
 
-            //HttpResponse resp = request.response();
-            System.err.println(results.size());
-            Set<HttpRequest> requests = results.keySet();
+        PushPromiseHandler<Path> pushPromiseHandler = (initial, pushRequest, acceptor) -> {
+            BodyHandler<Path> pp = requestToPath(pushRequest);
+            CompletableFuture<HttpResponse<Path>> cf = acceptor.apply(pp);
+            resultsMap.put(pushRequest, cf);
+        };
+
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+        CompletableFuture<HttpResponse<Path>> cf =
+                client.sendAsync(request, requestToPath(request), pushPromiseHandler);
+        cf.join();
+        resultsMap.put(request, cf);
+
+        for (HttpRequest r : resultsMap.keySet()) {
+            HttpResponse<Path> response = resultsMap.get(r).join();
+            assertEquals(response.statusCode(), 200);
+            String fileAsString = new String(Files.readAllBytes(response.body()), UTF_8);
+            assertEquals(fileAsString, tempFileAsString);
+        }
+        assertEquals(resultsMap.size(),  LOOPS + 1);
+    }
 
-            for (HttpRequest r : requests) {
-                URI u = r.uri();
-                Path result = results.get(r).get().body();
-                System.err.printf("%s -> %s\n", u.toString(), result.toString());
-                TestUtil.compareFiles(result, tempFile);
+    // Test 4 - of(...) populating the given Map, everything as a Path
+    @Test
+    public void testTypePathOfMap() throws Exception {
+        String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
+        ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Path>>> resultsMap
+                = new ConcurrentHashMap<>();
+
+        PushPromiseHandler<Path> pushPromiseHandler =
+                PushPromiseHandler.of(pushRequest -> requestToPath(pushRequest),
+                        resultsMap);
+
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+        CompletableFuture<HttpResponse<Path>> cf =
+                client.sendAsync(request, requestToPath(request), pushPromiseHandler);
+        cf.join();
+        resultsMap.put(request, cf);
+
+        for (HttpRequest r : resultsMap.keySet()) {
+            HttpResponse<Path> response = resultsMap.get(r).join();
+            assertEquals(response.statusCode(), 200);
+            String fileAsString = new String(Files.readAllBytes(response.body()), UTF_8);
+            assertEquals(fileAsString, tempFileAsString);
+        }
+        assertEquals(resultsMap.size(),  LOOPS + 1);
+    }
+
+    // ---  Consumer<byte[]> ---
+
+    static class ByteArrayConsumer implements Consumer<Optional<byte[]>> {
+        volatile List<byte[]> listByteArrays = new ArrayList<>();
+        volatile byte[] accumulatedBytes;
+
+        public byte[] getAccumulatedBytes() { return accumulatedBytes; }
+
+        @Override
+        public void accept(Optional<byte[]> optionalBytes) {
+            assert accumulatedBytes == null;
+            if (!optionalBytes.isPresent()) {
+                int size = listByteArrays.stream().mapToInt(ba -> ba.length).sum();
+                ByteBuffer bb = ByteBuffer.allocate(size);
+                listByteArrays.stream().forEach(ba -> bb.put(ba));
+                accumulatedBytes = bb.array();
+            } else {
+                listByteArrays.add(optionalBytes.get());
             }
-            if (requests.size() != LOOPS + 1)
-                throw new RuntimeException("some results missing");
-            System.out.println("TEST OK: sleeping for 5 sec");
-            Thread.sleep (5 * 1000);
-        } finally {
-            e.shutdownNow();
-            server.stop();
-            Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
-                public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
-                    dir.toFile().delete();
-                    return FileVisitResult.CONTINUE;
-                }
-                public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
-                    path.toFile().delete();
-                    return FileVisitResult.CONTINUE;
-                }
-            });
         }
     }
+
+    // Test 5 - custom written handler, everything as a consumer of optional byte[]
+    @Test
+    public void testTypeByteArrayConsumer() throws Exception {
+        String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
+        ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Void>>> resultsMap
+                = new ConcurrentHashMap<>();
+        Map<HttpRequest,ByteArrayConsumer> byteArrayConsumerMap
+                = new ConcurrentHashMap<>();
+
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+        ByteArrayConsumer bac = new ByteArrayConsumer();
+        byteArrayConsumerMap.put(request, bac);
+
+        PushPromiseHandler<Void> pushPromiseHandler = (initial, pushRequest, acceptor) -> {
+            CompletableFuture<HttpResponse<Void>> cf = acceptor.apply(
+                    (info) -> {
+                        ByteArrayConsumer bc = new ByteArrayConsumer();
+                        byteArrayConsumerMap.put(pushRequest, bc);
+                        return BodySubscribers.ofByteArrayConsumer(bc); } );
+            resultsMap.put(pushRequest, cf);
+        };
+
+        CompletableFuture<HttpResponse<Void>> cf =
+                client.sendAsync(request, BodyHandlers.ofByteArrayConsumer(bac), pushPromiseHandler);
+        cf.join();
+        resultsMap.put(request, cf);
+
+        for (HttpRequest r : resultsMap.keySet()) {
+            HttpResponse<Void> response = resultsMap.get(r).join();
+            assertEquals(response.statusCode(), 200);
+            byte[] ba = byteArrayConsumerMap.get(r).getAccumulatedBytes();
+            String result = new String(ba, UTF_8);
+            assertEquals(result, tempFileAsString);
+        }
+        assertEquals(resultsMap.size(), LOOPS + 1);
+    }
+
+    // Test 6 - of(...) populating the given Map, everything as a consumer of optional byte[]
+    @Test
+    public void testTypeByteArrayConsumerOfMap() throws Exception {
+        String tempFileAsString = new String(Files.readAllBytes(tempFile), UTF_8);
+        ConcurrentMap<HttpRequest, CompletableFuture<HttpResponse<Void>>> resultsMap
+                = new ConcurrentHashMap<>();
+        Map<HttpRequest,ByteArrayConsumer> byteArrayConsumerMap
+                = new ConcurrentHashMap<>();
+
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+        ByteArrayConsumer bac = new ByteArrayConsumer();
+        byteArrayConsumerMap.put(request, bac);
+
+        PushPromiseHandler<Void> pushPromiseHandler =
+                PushPromiseHandler.of(
+                        pushRequest -> {
+                            ByteArrayConsumer bc = new ByteArrayConsumer();
+                            byteArrayConsumerMap.put(pushRequest, bc);
+                            return BodyHandlers.ofByteArrayConsumer(bc);
+                        },
+                        resultsMap);
+
+        CompletableFuture<HttpResponse<Void>> cf =
+                client.sendAsync(request, BodyHandlers.ofByteArrayConsumer(bac), pushPromiseHandler);
+        cf.join();
+        resultsMap.put(request, cf);
+
+        for (HttpRequest r : resultsMap.keySet()) {
+            HttpResponse<Void> response = resultsMap.get(r).join();
+            assertEquals(response.statusCode(), 200);
+            byte[] ba = byteArrayConsumerMap.get(r).getAccumulatedBytes();
+            String result = new String(ba, UTF_8);
+            assertEquals(result, tempFileAsString);
+        }
+        assertEquals(resultsMap.size(), LOOPS + 1);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/ServerPushWithDiffTypes.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,258 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @library /lib/testlibrary server
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @modules java.base/sun.net.www.http
+ *          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
+ *       ServerPushWithDiffTypes
+ */
+
+import java.io.*;
+import java.net.*;
+import java.nio.ByteBuffer;
+import java.nio.file.*;
+import java.net.http.*;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.util.*;
+import java.util.concurrent.*;
+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;
+
+public class ServerPushWithDiffTypes {
+
+    static Map<String,String> PUSH_PROMISES = Map.of(
+            "/x/y/z/1", "the first push promise body",
+            "/x/y/z/2", "the second push promise body",
+            "/x/y/z/3", "the third push promise body",
+            "/x/y/z/4", "the fourth push promise body",
+            "/x/y/z/5", "the fifth push promise body",
+            "/x/y/z/6", "the sixth push promise body",
+            "/x/y/z/7", "the seventh push promise body",
+            "/x/y/z/8", "the eighth push promise body",
+            "/x/y/z/9", "the ninth push promise body"
+    );
+
+    @Test
+    public static void test() throws Exception {
+        Http2TestServer server = null;
+        try {
+            server = new Http2TestServer(false, 0);
+            Http2Handler handler =
+                    new ServerPushHandler("the main response body",
+                                          PUSH_PROMISES);
+            server.addHandler(handler, "/");
+            server.start();
+            int port = server.getAddress().getPort();
+            System.err.println("Server listening on port " + port);
+
+            HttpClient client = HttpClient.newHttpClient();
+            // use multi-level path
+            URI uri = new URI("http://localhost:" + port + "/foo/a/b/c");
+            HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
+
+            ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<BodyAndType<?>>>>
+                    results = new ConcurrentHashMap<>();
+            PushPromiseHandler<BodyAndType<?>> bh = PushPromiseHandler.of(
+                    (pushRequest) -> new BodyAndTypeHandler(pushRequest), results);
+
+            CompletableFuture<HttpResponse<BodyAndType<?>>> cf =
+                    client.sendAsync(request, new BodyAndTypeHandler(request), bh);
+            results.put(request, cf);
+            cf.join();
+
+            assertEquals(results.size(), PUSH_PROMISES.size() + 1);
+
+            for (HttpRequest r : results.keySet()) {
+                URI u = r.uri();
+                BodyAndType<?> body = results.get(r).get().body();
+                String result;
+                // convert all body types to String for easier comparison
+                if (body.type() == String.class) {
+                    result = (String)body.getBody();
+                } else if (body.type() == byte[].class) {
+                    byte[] bytes = (byte[])body.getBody();
+                    result = new String(bytes, UTF_8);
+                } else if (Path.class.isAssignableFrom(body.type())) {
+                    Path path = (Path)body.getBody();
+                    result = new String(Files.readAllBytes(path), UTF_8);
+                } else {
+                    throw new AssertionError("Unknown:" + body.type());
+                }
+
+                System.err.printf("%s -> %s\n", u.toString(), result.toString());
+                String expected = PUSH_PROMISES.get(r.uri().getPath());
+                if (expected == null)
+                    expected = "the main response body";
+                assertEquals(result, expected);
+            }
+        } finally {
+            server.stop();
+        }
+    }
+
+    interface BodyAndType<T> {
+        Class<T> type();
+        T getBody();
+    }
+
+    static final Path WORK_DIR = Paths.get(".");
+
+    static class BodyAndTypeHandler implements BodyHandler<BodyAndType<?>> {
+        int count;
+        final HttpRequest request;
+
+        BodyAndTypeHandler(HttpRequest request) {
+            this.request = request;
+        }
+
+        @Override
+        public HttpResponse.BodySubscriber<BodyAndType<?>> apply(HttpResponse.ResponseInfo info) {
+            int whichType = count++ % 3;  // real world may base this on the request metadata
+            switch (whichType) {
+                case 0: // String
+                    return new BodyAndTypeSubscriber(BodySubscribers.ofString(UTF_8));
+                case 1: // byte[]
+                    return new BodyAndTypeSubscriber(BodySubscribers.ofByteArray());
+                case 2: // Path
+                    URI u = request.uri();
+                    Path path = Paths.get(WORK_DIR.toString(), u.getPath());
+                    try {
+                        Files.createDirectories(path.getParent());
+                    } catch (IOException ee) {
+                        throw new UncheckedIOException(ee);
+                    }
+                    return new BodyAndTypeSubscriber(BodySubscribers.ofFile(path));
+                default:
+                    throw new AssertionError("Unexpected " + whichType);
+            }
+        }
+    }
+
+    static class BodyAndTypeSubscriber<T>
+        implements HttpResponse.BodySubscriber<BodyAndType<T>>
+    {
+        private static class BodyAndTypeImpl<T> implements BodyAndType<T> {
+            private final Class<T> type;
+            private final T body;
+            public BodyAndTypeImpl(Class<T> type, T body) { this.type = type; this.body = body; }
+            @Override public Class<T> type() { return type; }
+            @Override public T getBody() { return body; }
+        }
+
+        private final BodySubscriber<?> bodySubscriber;
+        private final CompletableFuture<BodyAndType<T>> cf;
+
+        BodyAndTypeSubscriber(BodySubscriber bodySubscriber) {
+            this.bodySubscriber = bodySubscriber;
+            cf = new CompletableFuture<>();
+            bodySubscriber.getBody().whenComplete(
+                    (r,t) -> cf.complete(new BodyAndTypeImpl(r.getClass(), r)));
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            bodySubscriber.onSubscribe(subscription);
+        }
+
+        @Override
+        public void onNext(List<ByteBuffer> item) {
+            bodySubscriber.onNext(item);
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            bodySubscriber.onError(throwable);
+            cf.completeExceptionally(throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            bodySubscriber.onComplete();
+        }
+
+        @Override
+        public CompletionStage<BodyAndType<T>> getBody() {
+            return cf;
+        }
+    }
+
+    // --- server push handler ---
+    static class ServerPushHandler implements Http2Handler {
+
+        private final String mainResponseBody;
+        private final Map<String,String> promises;
+
+        public ServerPushHandler(String mainResponseBody,
+                                 Map<String,String> promises)
+            throws Exception
+        {
+            Objects.requireNonNull(promises);
+            this.mainResponseBody = mainResponseBody;
+            this.promises = promises;
+        }
+
+        public void handle(Http2TestExchange exchange) throws IOException {
+            System.err.println("Server: handle " + exchange);
+            try (InputStream is = exchange.getRequestBody()) {
+                is.readAllBytes();
+            }
+
+            if (exchange.serverPushAllowed()) {
+                pushPromises(exchange);
+            }
+
+            // response data for the main response
+            try (OutputStream os = exchange.getResponseBody()) {
+                byte[] bytes = mainResponseBody.getBytes(UTF_8);
+                exchange.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+
+        private void pushPromises(Http2TestExchange exchange) throws IOException {
+            URI requestURI = exchange.getRequestURI();
+            for (Map.Entry<String,String> promise : promises.entrySet()) {
+                URI uri = requestURI.resolve(promise.getKey());
+                InputStream is = new ByteArrayInputStream(promise.getValue().getBytes(UTF_8));
+                HttpHeadersImpl headers = new HttpHeadersImpl();
+                // TODO: add some check on headers, maybe
+                headers.addHeader("X-Promise", promise.getKey());
+                exchange.serverPush(uri, headers, is);
+            }
+            System.err.println("Server: All pushes sent");
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/http2/TLSConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/TLSConnection.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -27,13 +27,12 @@
 import java.io.OutputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandlers;
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSession;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
 
 /*
  * @test
@@ -41,16 +40,21 @@
  * @library server
  * @summary Checks that SSL parameters can be set for HTTP/2 connection
  * @modules java.base/sun.net.www.http
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
- * @run main/othervm TLSConnection
+ *          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
+ *       TLSConnection
  */
 public class TLSConnection {
 
     private static final String KEYSTORE = System.getProperty("test.src")
             + File.separator + "keystore.p12";
-   private static final String PASSWORD = "password";
+    private static final String PASSWORD = "password";
+
+    private static final SSLParameters USE_DEFAULT_SSL_PARAMETERS = new SSLParameters();
 
     public static void main(String[] args) throws Exception {
 
@@ -65,51 +69,52 @@
 
         Handler handler = new Handler();
 
-        try (Http2TestServer server = new Http2TestServer("127.0.0.1", true, 0)) {
+        try (Http2TestServer server = new Http2TestServer("localhost", true, 0)) {
             server.addHandler(handler, "/");
             server.start();
 
             int port = server.getAddress().getPort();
-            String uriString = "https://127.0.0.1:" + Integer.toString(port);
+            String uriString = "https://localhost:" + Integer.toString(port);
 
             // run test cases
             boolean success = true;
 
             SSLParameters parameters = null;
             success &= expectFailure(
-                    "Test #1: SSL parameters is null, expect NPE",
+                    "---\nTest #1: SSL parameters is null, expect NPE",
                     () -> connect(uriString, parameters),
                     NullPointerException.class);
 
             success &= expectSuccess(
-                    "Test #2: default SSL parameters, "
+                    "---\nTest #2: default SSL parameters, "
                             + "expect successful connection",
-                    () -> connect(uriString, new SSLParameters()));
+                    () -> connect(uriString, USE_DEFAULT_SSL_PARAMETERS));
             success &= checkProtocol(handler.getSSLSession(), "TLSv1.2");
 
             // set SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA cipher suite
             // which has less priority in default cipher suite list
             success &= expectSuccess(
-                    "Test #3: SSL parameters with "
+                    "---\nTest #3: SSL parameters with "
                             + "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA cipher suite, "
                             + "expect successful connection",
                     () -> connect(uriString, new SSLParameters(
-                            new String[] { "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA" })));
+                            new String[] { "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA" },
+                            new String[] { "TLSv1.2" })));
             success &= checkProtocol(handler.getSSLSession(), "TLSv1.2");
             success &= checkCipherSuite(handler.getSSLSession(),
                     "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA");
 
             // set TLS_RSA_WITH_AES_128_CBC_SHA cipher suite
             // which has less priority in default cipher suite list
-            // also set TLSv11 protocol
+            // also set TLSv1.2 protocol
             success &= expectSuccess(
-                    "Test #4: SSL parameters with "
+                    "---\nTest #4: SSL parameters with "
                             + "TLS_RSA_WITH_AES_128_CBC_SHA cipher suite,"
                             + " expect successful connection",
                     () -> connect(uriString, new SSLParameters(
                             new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA" },
-                            new String[] { "TLSv1.1" })));
-            success &= checkProtocol(handler.getSSLSession(), "TLSv1.1");
+                            new String[] { "TLSv1.2" })));
+            success &= checkProtocol(handler.getSSLSession(), "TLSv1.2");
             success &= checkCipherSuite(handler.getSSLSession(),
                     "TLS_RSA_WITH_AES_128_CBC_SHA");
 
@@ -158,16 +163,18 @@
     }
 
     private static void connect(String uriString, SSLParameters sslParameters)
-        throws URISyntaxException, IOException, InterruptedException
+            throws URISyntaxException, IOException, InterruptedException
     {
-        HttpClient client = HttpClient.newBuilder()
-                                      .sslParameters(sslParameters)
-                                      .version(HttpClient.Version.HTTP_2)
-                                      .build();
+        HttpClient.Builder builder = HttpClient.newBuilder()
+                .version(HttpClient.Version.HTTP_2);
+        if (sslParameters != USE_DEFAULT_SSL_PARAMETERS)
+            builder.sslParameters(sslParameters);
+        HttpClient client = builder.build();
+
         HttpRequest request = HttpRequest.newBuilder(new URI(uriString))
-                                         .POST(fromString("body"))
-                                         .build();
-        String body = client.send(request, asString()).body();
+                .POST(BodyPublishers.ofString("body"))
+                .build();
+        String body = client.send(request, BodyHandlers.ofString()).body();
 
         System.out.println("Response: " + body);
     }
@@ -222,7 +229,7 @@
     }
 
     private static boolean expectFailure(String message, Test test,
-            Class<? extends Throwable> expectedException) {
+                                         Class<? extends Throwable> expectedException) {
 
         System.out.println(message);
         try {
@@ -243,5 +250,4 @@
             return true;
         }
     }
-
 }
--- a/test/jdk/java/net/httpclient/http2/Timeout.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/Timeout.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -23,19 +23,21 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpTimeoutException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpTimeoutException;
 import java.time.Duration;
 import java.util.concurrent.CompletionException;
 import javax.net.ssl.SSLServerSocket;
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocket;
-import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
 
 /*
  * @test
@@ -69,7 +71,9 @@
                 (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
 
         try (SSLServerSocket ssocket =
-                (SSLServerSocket) factory.createServerSocket(RANDOM_PORT)) {
+                (SSLServerSocket) factory.createServerSocket()) {
+            ssocket.setReuseAddress(false);
+            ssocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), RANDOM_PORT));
 
             // start server
             Thread server = new Thread(() -> {
@@ -118,9 +122,9 @@
                                           .build();
             HttpRequest request = HttpRequest.newBuilder(new URI(server))
                                              .timeout(Duration.ofMillis(TIMEOUT))
-                                             .POST(fromString("body"))
+                                             .POST(BodyPublishers.ofString("body"))
                                              .build();
-            HttpResponse<String> response = client.send(request, asString());
+            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
             System.out.println("Received unexpected reply: " + response.statusCode());
             throw new RuntimeException("unexpected successful connection");
         } catch (HttpTimeoutException e) {
@@ -135,9 +139,9 @@
                     .build();
             HttpRequest request = HttpRequest.newBuilder(new URI(server))
                     .timeout(Duration.ofMillis(TIMEOUT))
-                    .POST(fromString("body"))
+                    .POST(BodyPublishers.ofString("body"))
                     .build();
-            HttpResponse<String> response = client.sendAsync(request, asString()).join();
+            HttpResponse<String> response = client.sendAsync(request, BodyHandlers.ofString()).join();
             System.out.println("Received unexpected reply: " + response.statusCode());
             throw new RuntimeException("unexpected successful connection");
         } catch (CompletionException e) {
--- /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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,369 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 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 random = 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 + random.nextInt(8);
+            final int expected = random.nextInt(Integer.MAX_VALUE) + 1;
+            w.reset().configure(expected, N, random.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 + random.nextInt(8);
+            final int payload = random.nextInt(255);
+            final int expected = random.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];
+                random.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];
+            random.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];
+            random.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];
+//                random.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(reader.get(), expected);
+    }
+
+    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(buf, ByteBuffer.wrap(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/BuffersTestingKit.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,210 @@
+/*
+ * 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.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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,141 @@
+/*
+ * 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.hpack;
+
+import org.testng.annotations.Test;
+import jdk.internal.net.http.hpack.SimpleHeaderTable.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.assertVoidThrows;
+import static jdk.internal.net.http.hpack.TestHelper.newRandom;
+
+public final class CircularBufferTest {
+
+    private final Random random = newRandom();
+
+    @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);
+    }
+
+    @Test
+    public void newCapacityLessThanCurrentSize1() {
+        CircularBuffer<Integer> buffer = new CircularBuffer<>(0);
+        buffer.resize(5);
+        buffer.add(1);
+        buffer.add(1);
+        buffer.add(1);
+        assertVoidThrows(IllegalStateException.class, () -> buffer.resize(2));
+        assertVoidThrows(IllegalStateException.class, () -> buffer.resize(1));
+    }
+
+    @Test
+    public void newCapacityLessThanCurrentSize2() {
+        CircularBuffer<Integer> buffer = new CircularBuffer<>(5);
+        buffer.add(1);
+        buffer.add(1);
+        buffer.add(1);
+        assertVoidThrows(IllegalStateException.class, () -> buffer.resize(2));
+        assertVoidThrows(IllegalStateException.class, () -> buffer.resize(1));
+    }
+
+    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 = random.nextInt(capacity); // [0, capacity)
+        for (int i = 0; i < gets; i++) {
+            referenceQueue.poll();
+            buffer.remove();
+        }
+        int puts = random.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 = random.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 = random.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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,685 @@
+/*
+ * 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.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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,693 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 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.SimpleHeaderTable.HeaderField;
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.testng.Assert.assertEquals;
+
+public class HeaderTableTest extends SimpleHeaderTableTest {
+
+    @Override
+    protected HeaderTable createHeaderTable(int maxSize) {
+        return new HeaderTable(maxSize, HPACK.getLogger());
+    }
+
+    @Test
+    public void staticData() {
+        HeaderTable table = createHeaderTable(0);
+        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 lowerIndexPriority() {
+        HeaderTable table = createHeaderTable(256);
+        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(i, oldLength + 1);
+    }
+
+    @Test
+    public void indexesAreNotLost2() {
+        HeaderTable table = createHeaderTable(256);
+        int oldLength = table.length();
+        table.put("bender", "rodriguez");
+        assertEquals(table.indexOf("bender", "rodriguez"), oldLength + 1);
+        table.put("bender", "rodriguez");
+        assertEquals(table.indexOf("bender", "rodriguez"), oldLength + 1);
+        table.evictEntry();
+        assertEquals(table.indexOf("bender", "rodriguez"), oldLength + 1);
+        table.evictEntry();
+        assertEquals(table.indexOf("bender", "rodriguez"), 0);
+    }
+
+    @Test
+    public void lowerIndexPriority2() {
+        HeaderTable table = createHeaderTable(256);
+        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(i, idx);
+    }
+
+    @Test
+    public void indexOf() {
+        // Let's put a series of header fields
+        int NUM_HEADERS = 32;
+        HeaderTable table =
+                createHeaderTable((32 + 4) * NUM_HEADERS);
+        //                          ^   ^
+        //             entry overhead   symbols per entry (max 2x2 digits)
+        for (int i = 1; i <= NUM_HEADERS; i++) {
+            String s = String.valueOf(i);
+            table.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 = table.indexOf(s, s);
+            int expectedIndex = STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1;
+            assertEquals(actualIndex, expectedIndex);
+        }
+        // as well as for just a name lookup
+        for (int j = 1; j <= NUM_HEADERS; j++) {
+            String s = String.valueOf(j);
+            int actualIndex = table.indexOf(s, "blah");
+            int expectedIndex = -(STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1);
+            assertEquals(actualIndex, expectedIndex);
+        }
+        // lookup for non-existent name returns 0
+        assertEquals(table.indexOf("chupacabra", "1"), 0);
+    }
+}
--- /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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,629 @@
+/*
+ * 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.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/SimpleHeaderTableTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,332 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 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.SimpleHeaderTable.HeaderField;
+import org.testng.annotations.Test;
+
+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 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;
+import static java.lang.String.format;
+import static org.testng.Assert.assertEquals;
+
+public class SimpleHeaderTableTest {
+
+    //
+    // 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
+
+    static final int STATIC_TABLE_LENGTH = createStaticEntries().size();
+    final Random rnd = newRandom();
+
+    /** Creates a header table under test. Override in subclass. */
+    protected SimpleHeaderTable createHeaderTable(int maxSize) {
+        return new SimpleHeaderTable(maxSize, HPACK.getLogger());
+    }
+
+    @Test
+    public void staticData0() {
+        SimpleHeaderTable table = createHeaderTable(0);
+        Map<Integer, HeaderField> staticHeaderFields = createStaticEntries();
+        staticHeaderFields.forEach((index, expectedHeaderField) -> {
+            SimpleHeaderTable.HeaderField actualHeaderField = table.get(index);
+            assertEquals(actualHeaderField, expectedHeaderField);
+        });
+    }
+
+    @Test
+    public void constructorSetsMaxSize() {
+        int size = rnd.nextInt(64);
+        SimpleHeaderTable table = createHeaderTable(size);
+        assertEquals(table.size(), 0);
+        assertEquals(table.maxSize(), size);
+    }
+
+    @Test
+    public void negativeMaximumSize() {
+        int maxSize = -(rnd.nextInt(100) + 1); // [-100, -1]
+        SimpleHeaderTable table = createHeaderTable(0);
+        IllegalArgumentException e =
+                assertVoidThrows(IllegalArgumentException.class,
+                                 () -> table.setMaxSize(maxSize));
+        assertExceptionMessageContains(e, "maxSize");
+    }
+
+    @Test
+    public void zeroMaximumSize() {
+        SimpleHeaderTable table = createHeaderTable(0);
+        table.setMaxSize(0);
+        assertEquals(table.maxSize(), 0);
+    }
+
+    @Test
+    public void negativeIndex() {
+        int idx = -(rnd.nextInt(256) + 1); // [-256, -1]
+        SimpleHeaderTable table = createHeaderTable(0);
+        IndexOutOfBoundsException e =
+                assertVoidThrows(IndexOutOfBoundsException.class,
+                                 () -> table.get(idx));
+        assertExceptionMessageContains(e, "index");
+    }
+
+    @Test
+    public void zeroIndex() {
+        SimpleHeaderTable table = createHeaderTable(0);
+        IndexOutOfBoundsException e =
+                assertThrows(IndexOutOfBoundsException.class,
+                             () -> table.get(0));
+        assertExceptionMessageContains(e, "index");
+    }
+
+    @Test
+    public void length() {
+        SimpleHeaderTable table = createHeaderTable(0);
+        assertEquals(table.length(), STATIC_TABLE_LENGTH);
+    }
+
+    @Test
+    public void indexOutsideStaticRange() {
+        SimpleHeaderTable table = createHeaderTable(0);
+        int idx = table.length() + (rnd.nextInt(256) + 1);
+        IndexOutOfBoundsException e =
+                assertThrows(IndexOutOfBoundsException.class,
+                             () -> table.get(idx));
+        assertExceptionMessageContains(e, "index");
+    }
+
+    @Test
+    public void entryPutAfterStaticArea() {
+        SimpleHeaderTable table = createHeaderTable(256);
+        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);
+        SimpleHeaderTable.HeaderField f = table.get(idx);
+        assertEquals(f.name, name);
+        assertEquals(f.value, value);
+    }
+
+    @Test
+    public void staticTableHasZeroSize() {
+        SimpleHeaderTable table = createHeaderTable(0);
+        assertEquals(table.size(), 0);
+    }
+
+    // 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;
+        SimpleHeaderTable table =
+                createHeaderTable((32 + 4) * NUM_HEADERS);
+        //                          ^   ^
+        //             entry overhead   symbols per entry (max 2x2 digits)
+        for (int i = 1; i <= NUM_HEADERS; i++) {
+            String s = String.valueOf(i);
+            table.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++) {
+            SimpleHeaderTable.HeaderField f = table.get(STATIC_TABLE_LENGTH + j);
+            int actualName = Integer.parseInt(f.name);
+            int expectedName = NUM_HEADERS - j + 1;
+            assertEquals(actualName, expectedName);
+        }
+        // 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++) {
+            SimpleHeaderTable.HeaderField f = table.evictEntry();
+            assertEquals(f.name, String.valueOf(k));
+        }
+    }
+
+    @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(s, "3,1"); // assumption of the test, otherwise the test is useless
+            testToString0();
+        } finally {
+            Locale.setDefault(locale);
+        }
+    }
+
+    private void testToString0() {
+        SimpleHeaderTable table = createHeaderTable(0);
+        {
+            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(table.toString(), expected);
+        }
+
+        {
+            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(s, expected);
+        }
+
+        {
+            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(s, expected);
+        }
+    }
+
+    @Test
+    public void stateString() {
+        SimpleHeaderTable table = createHeaderTable(256);
+        table.put("custom-key", "custom-header");
+        // @formatter:off
+        assertEquals(table.getStateString(),
+                     "[  1] (s =  55) custom-key: custom-header\n" +
+                     "      Table size:  55");
+        // @formatter:on
+    }
+
+    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/SpecHelper.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/BinaryPrimitivesTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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 jdk.incubator.http.internal.hpack.BuffersTestingKit.*;
-import static jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/BuffersTestingKit.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/CircularBufferTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http.internal.hpack;
-
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-import jdk.incubator.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 jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/DecoderTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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 jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/EncoderTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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 jdk.incubator.http.internal.hpack.BuffersTestingKit.concat;
-import static jdk.incubator.http.internal.hpack.BuffersTestingKit.forEachSplit;
-import static jdk.incubator.http.internal.hpack.SpecHelper.toHexdump;
-import static jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/HeaderTableTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http.internal.hpack;
-
-import org.testng.annotations.Test;
-import jdk.incubator.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 jdk.incubator.http.internal.hpack.TestHelper.assertExceptionMessageContains;
-import static jdk.incubator.http.internal.hpack.TestHelper.assertThrows;
-import static jdk.incubator.http.internal.hpack.TestHelper.assertVoidThrows;
-import static jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/HuffmanTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/SpecHelper.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/hpack/TestHelper.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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();
-    }
-}
Binary file test/jdk/java/net/httpclient/http2/keystore.p12 has changed
--- a/test/jdk/java/net/httpclient/http2/server/BodyInputStream.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/BodyInputStream.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -25,10 +25,10 @@
 import java.nio.ByteBuffer;
 import java.util.List;
 
-import jdk.incubator.http.internal.common.Utils;
-import jdk.incubator.http.internal.frame.DataFrame;
-import jdk.incubator.http.internal.frame.Http2Frame;
-import jdk.incubator.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
@@ -62,6 +62,7 @@
         Http2Frame frame;
         do {
             frame = q.take();
+            if (frame == null) return null; // closed/eof before receiving data.
             // ignoring others for now Wupdates handled elsewhere
             if (frame.type() != DataFrame.TYPE) {
                 System.out.println("Ignoring " + frame.toString() + " CHECK THIS");
@@ -122,7 +123,7 @@
         if (c == -1) {
             return -1;
         }
-        return one[0];
+        return one[0] & 0xFF;
     }
 
     @Override
--- a/test/jdk/java/net/httpclient/http2/server/BodyOutputStream.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/BodyOutputStream.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 import java.io.*;
 import java.nio.ByteBuffer;
 
-import jdk.incubator.http.internal.frame.DataFrame;
+import jdk.internal.net.http.frame.DataFrame;
 
 /**
  * OutputStream. Incoming window updates handled by the main connection
@@ -86,8 +86,14 @@
             throw new IllegalStateException("sendResponseHeaders must be called first");
         }
         try {
-            waitForWindow(len);
-            send(buf, offset, len, 0);
+            int max = conn.getMaxFrameSize();
+            while (len > 0) {
+                int n = len > max ? max : len;
+                waitForWindow(n);
+                send(buf, offset, n, 0);
+                offset += n;
+                len -= n;
+            }
         } catch (InterruptedException ex) {
             throw new IOException(ex);
         }
--- a/test/jdk/java/net/httpclient/http2/server/EchoHandler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/EchoHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,9 +22,15 @@
  */
 
 import java.io.*;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public class EchoHandler implements Http2Handler {
+    static final Path CWD = Paths.get(".");
+
     public EchoHandler() {}
 
     @Override
@@ -38,7 +44,7 @@
             map1.addHeader("X-Hello", "world");
             map1.addHeader("X-Bye", "universe");
             String fixedrequest = map.firstValue("XFixed").orElse(null);
-            File outfile = File.createTempFile("foo", "bar");
+            File outfile = Files.createTempFile(CWD, "foo", "bar").toFile();
             //System.err.println ("QQQ = " + outfile.toString());
             FileOutputStream fos = new FileOutputStream(outfile);
             int count = (int) is.transferTo(fos);
--- a/test/jdk/java/net/httpclient/http2/server/ExceptionallyCloseable.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/ExceptionallyCloseable.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
--- a/test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2EchoHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,23 +22,30 @@
  */
 
 import java.io.*;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public class Http2EchoHandler implements Http2Handler {
+    static final Path CWD = Paths.get(".");
+
     public Http2EchoHandler() {}
 
     @Override
     public void handle(Http2TestExchange t)
             throws IOException {
         try {
-            System.err.printf("EchoHandler received request to %s from %s\n", t.getRequestURI(), t.getRemoteAddress());
+            System.err.printf("EchoHandler received request to %s from %s\n",
+                              t.getRequestURI(), t.getRemoteAddress());
             InputStream is = t.getRequestBody();
             HttpHeadersImpl map = t.getRequestHeaders();
             HttpHeadersImpl map1 = t.getResponseHeaders();
             map1.addHeader("X-Hello", "world");
             map1.addHeader("X-Bye", "universe");
             String fixedrequest = map.firstValue("XFixed").orElse(null);
-            File outfile = File.createTempFile("foo", "bar");
+            File outfile = Files.createTempFile(CWD, "foo", "bar").toFile();
             //System.err.println ("QQQ = " + outfile.toString());
             FileOutputStream fos = new FileOutputStream(outfile);
             int count = (int) is.transferTo(fos);
--- a/test/jdk/java/net/httpclient/http2/server/Http2Handler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2Handler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
--- a/test/jdk/java/net/httpclient/http2/server/Http2RedirectHandler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2RedirectHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -25,7 +25,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.function.Supplier;
-import jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java	Tue Apr 17 08:54:17 2018 -0700
@@ -29,7 +29,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 import javax.net.ssl.SSLSession;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public interface Http2TestExchange {
 
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -29,9 +29,9 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 import javax.net.ssl.SSLSession;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.frame.HeaderFrame;
-import jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java	Tue Apr 17 08:54:17 2018 -0700
@@ -24,7 +24,7 @@
 import javax.net.ssl.SSLSession;
 import java.io.InputStream;
 import java.net.URI;
-import jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -35,7 +35,7 @@
 import javax.net.ssl.SSLServerSocket;
 import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SNIServerName;
-import jdk.incubator.http.internal.frame.ErrorFrame;
+import jdk.internal.net.http.frame.ErrorFrame;
 
 /**
  * Waits for incoming TCP connections from a client and establishes
@@ -78,6 +78,11 @@
         return (InetSocketAddress)server.getLocalSocketAddress();
     }
 
+    public String serverAuthority() {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + getAddress().getPort();
+    }
+
     public Http2TestServer(boolean secure,
                            SSLContext context) throws Exception {
         this(null, secure, 0, null, context);
@@ -167,7 +172,10 @@
     }
 
     final ServerSocket initPlaintext(int port) throws Exception {
-        return new ServerSocket(port);
+        ServerSocket ss = new ServerSocket();
+        ss.setReuseAddress(false);
+        ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+        return ss;
     }
 
     public synchronized void stop() {
@@ -191,9 +199,12 @@
         } else {
             fac = SSLServerSocketFactory.getDefault();
         }
-        SSLServerSocket se = (SSLServerSocket) fac.createServerSocket(port);
+        SSLServerSocket se = (SSLServerSocket) fac.createServerSocket();
+        se.setReuseAddress(false);
+        se.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
         SSLParameters sslp = se.getSSLParameters();
         sslp.setApplicationProtocols(new String[]{"h2"});
+        sslp.setEndpointIdentificationAlgorithm("HTTPS");
         se.setSSLParameters(sslp);
         se.setEnabledCipherSuites(se.getSupportedCipherSuites());
         se.setEnabledProtocols(se.getSupportedProtocols());
@@ -222,23 +233,33 @@
             try {
                 while (!stopping) {
                     Socket socket = server.accept();
-                    InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress();
-                    Http2TestServerConnection c =
-                            new Http2TestServerConnection(this, socket, exchangeSupplier);
-                    putConnection(addr, c);
+                    Http2TestServerConnection c = null;
+                    InetSocketAddress addr = null;
                     try {
+                        addr = (InetSocketAddress) socket.getRemoteSocketAddress();
+                        c = createConnection(this, socket, exchangeSupplier);
+                        putConnection(addr, c);
                         c.run();
                     } catch (Throwable e) {
                         // we should not reach here, but if we do
                         // the connection might not have been closed
                         // and if so then the client might wait
                         // forever.
-                        removeConnection(addr, c);
-                        c.close(ErrorFrame.PROTOCOL_ERROR);
+                        if (c != null) {
+                            removeConnection(addr, c);
+                            c.close(ErrorFrame.PROTOCOL_ERROR);
+                        } else {
+                            socket.close();
+                        }
                         System.err.println("TestServer: start exception: " + e);
                         //throw e;
                     }
                 }
+            } catch (SecurityException se) {
+                System.err.println("TestServer: terminating, caught " + se);
+                se.printStackTrace();
+                stopping = true;
+                try { server.close(); } catch (IOException ioe) { /* ignore */}
             } catch (Throwable e) {
                 if (!stopping) {
                     System.err.println("TestServer: terminating, caught " + e);
@@ -248,6 +269,13 @@
         });
     }
 
+    protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer,
+                                                         Socket socket,
+                                                         Http2TestExchangeSupplier exchangeSupplier)
+            throws IOException {
+        return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier);
+    }
+
     @Override
     public void close() throws Exception {
         stop();
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java	Tue Apr 17 08:54:17 2018 -0700
@@ -40,14 +40,14 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.function.Consumer;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.frame.*;
-import jdk.incubator.http.internal.hpack.Decoder;
-import jdk.incubator.http.internal.hpack.DecodingCallback;
-import jdk.incubator.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 jdk.incubator.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
@@ -215,7 +215,7 @@
         if (name == null) {
             // no name set. No need to check
             return;
-        } else if (name.equals("127.0.0.1")) {
+        } else if (name.equals("localhost")) {
             name = "localhost";
         }
         final String fname = name;
@@ -224,7 +224,7 @@
         SNIMatcher matcher = new SNIMatcher(StandardConstants.SNI_HOST_NAME) {
             public boolean matches (SNIServerName n) {
                 String host = ((SNIHostName)n).getAsciiName();
-                if (host.equals("127.0.0.1"))
+                if (host.equals("localhost"))
                     host = "localhost";
                 boolean cmp = host.equalsIgnoreCase(fname);
                 if (cmp)
@@ -269,9 +269,9 @@
         }
     }
 
-    String doUpgrade() throws IOException {
-        String upgrade = readHttp1Request();
-        String h2c = getHeader(upgrade, "Upgrade");
+    Http1InitialRequest doUpgrade() throws IOException {
+        Http1InitialRequest upgrade = readHttp1Request();
+        String h2c = getHeader(upgrade.headers, "Upgrade");
         if (h2c == null || !h2c.equals("h2c")) {
             System.err.println("Server:HEADERS: " + upgrade);
             throw new IOException("Bad upgrade 1 " + h2c);
@@ -283,7 +283,7 @@
         sendSettingsFrame();
         readPreface();
 
-        String clientSettingsString = getHeader(upgrade, "HTTP2-Settings");
+        String clientSettingsString = getHeader(upgrade.headers, "HTTP2-Settings");
         clientSettings = getSettingsFromString(clientSettingsString);
 
         return upgrade;
@@ -312,8 +312,12 @@
         return (SettingsFrame)frame;
     }
 
+    public int getMaxFrameSize() {
+        return clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE);
+    }
+
     void run() throws Exception {
-        String upgrade = null;
+        Http1InitialRequest upgrade = null;
         if (!secure) {
             upgrade = doUpgrade();
         } else {
@@ -327,8 +331,9 @@
             nextstream = 1;
         }
 
-        System.out.println("ServerSettings: " + serverSettings);
-        System.out.println("ClientSettings: " + clientSettings);
+        // Uncomment if needed, but very noisy
+        //System.out.println("ServerSettings: " + serverSettings);
+        //System.out.println("ClientSettings: " + clientSettings);
 
         hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE));
         hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
@@ -416,7 +421,7 @@
     }
 
     HttpHeadersImpl decodeHeaders(List<HeaderFrame> frames) throws IOException {
-        HttpHeadersImpl headers = new HttpHeadersImpl();
+        HttpHeadersImpl headers = createNewResponseHeaders();
 
         DecodingCallback cb = (name, value) -> {
             headers.addHeader(name.toString(), value.toString());
@@ -462,20 +467,20 @@
 
     // First stream (1) comes from a plaintext HTTP/1.1 request
     @SuppressWarnings({"rawtypes","unchecked"})
-    void createPrimordialStream(String request) throws IOException {
-        HttpHeadersImpl headers = new HttpHeadersImpl();
-        String requestLine = getRequestLine(request);
+    void createPrimordialStream(Http1InitialRequest request) throws IOException {
+        HttpHeadersImpl headers = createNewResponseHeaders();
+        String requestLine = getRequestLine(request.headers);
         String[] tokens = requestLine.split(" ");
         if (!tokens[2].equals("HTTP/1.1")) {
             throw new IOException("bad request line");
         }
-        URI uri = null;
+        URI uri;
         try {
             uri = new URI(tokens[1]);
         } catch (URISyntaxException e) {
             throw new IOException(e);
         }
-        String host = getHeader(request, "Host");
+        String host = getHeader(request.headers, "Host");
         if (host == null) {
             throw new IOException("missing Host");
         }
@@ -483,11 +488,15 @@
         headers.setHeader(":method", tokens[0]);
         headers.setHeader(":scheme", "http"); // always in this case
         headers.setHeader(":authority", host);
-        headers.setHeader(":path", uri.getPath());
+        String path = uri.getRawPath();
+        if (uri.getRawQuery() != null)
+            path = path + "?" + uri.getRawQuery();
+        headers.setHeader(":path", path);
+
         Queue q = new Queue(sentinel);
-        String body = getRequestBody(request);
-        addHeaders(getHeaders(request), headers);
-        headers.setHeader("Content-length", Integer.toString(body.length()));
+        byte[] body = getRequestBody(request);
+        addHeaders(getHeaders(request.headers), headers);
+        headers.setHeader("Content-length", Integer.toString(body.length));
 
         addRequestBodyToQueue(body, q);
         streams.put(1, q);
@@ -526,6 +535,18 @@
         }
         boolean endStreamReceived = endStream;
         HttpHeadersImpl headers = decodeHeaders(frames);
+
+        // Strict to assert Client correctness. Not all servers are as strict,
+        // but some are known to be.
+        Optional<?> disallowedHeader = headers.firstValue("Upgrade");
+        if (disallowedHeader.isPresent()) {
+            throw new IOException("Unexpected Upgrade in headers:" + headers);
+        }
+        disallowedHeader = headers.firstValue("HTTP2-Settings");
+        if (disallowedHeader.isPresent())
+            throw new IOException("Unexpected HTTP2-Settings in headers:" + headers);
+
+
         Queue q = new Queue(sentinel);
         streams.put(streamid, q);
         exec.submit(() -> {
@@ -551,7 +572,7 @@
         String authority = headers.firstValue(":authority").orElse("");
         //System.out.println("authority = " + authority);
         System.err.printf("TestServer: %s %s\n", method, path);
-        HttpHeadersImpl rspheaders = new HttpHeadersImpl();
+        HttpHeadersImpl rspheaders = createNewResponseHeaders();
         int winsize = clientSettings.getParameter(
                 SettingsFrame.INITIAL_WINDOW_SIZE);
         //System.err.println ("Stream window size = " + winsize);
@@ -577,16 +598,32 @@
 
             // give to user
             Http2Handler handler = server.getHandlerFor(uri.getPath());
-            handler.handle(exchange);
+            try {
+                handler.handle(exchange);
+            } catch (IOException closed) {
+                if (bos.closed) {
+                    Queue q = streams.get(streamid);
+                    if (q != null && (q.isClosed() || q.isClosing())) {
+                        System.err.println("TestServer: Stream " + streamid + " closed: " + closed);
+                        return;
+                    }
+                }
+                throw closed;
+            }
 
             // everything happens in the exchange from here. Hopefully will
             // return though.
         } catch (Throwable e) {
             System.err.println("TestServer: handleRequest exception: " + e);
             e.printStackTrace();
+            close(-1);
         }
     }
 
+    protected HttpHeadersImpl createNewResponseHeaders() {
+        return new HttpHeadersImpl();
+    }
+
     private SSLSession getSSLSession() {
         if (! (socket instanceof SSLSocket))
             return null;
@@ -775,7 +812,7 @@
     // returns a minimal response with status 200
     // that is the response to the push promise just sent
     private ResponseHeaders getPushResponse(int streamid) {
-        HttpHeadersImpl h = new HttpHeadersImpl();
+        HttpHeadersImpl h = createNewResponseHeaders();
         h.addHeader(":status", "200");
         ResponseHeaders oh = new ResponseHeaders(h);
         oh.streamid(streamid);
@@ -895,7 +932,16 @@
     final static String CRLF = "\r\n";
     final static String CRLFCRLF = "\r\n\r\n";
 
-    String readHttp1Request() throws IOException {
+    static class Http1InitialRequest {
+        final String headers;
+        final byte[] body;
+        Http1InitialRequest(String headers, byte[] body) {
+            this.headers = headers;
+            this.body = body.clone();
+        }
+    }
+
+    Http1InitialRequest readHttp1Request() throws IOException {
         String headers = readUntil(CRLF + CRLF);
         int clen = getContentLength(headers);
         String te = getHeader(headers, "Transfer-encoding");
@@ -909,8 +955,7 @@
                 //  HTTP/1.1 chunked data, read it
                 buf = readChunkedInputStream(is);
             }
-            String body = new String(buf, StandardCharsets.US_ASCII);
-            return headers + body;
+            return new Http1InitialRequest(headers, buf);
         } catch (IOException e) {
             System.err.println("TestServer: headers read: [ " + headers + " ]");
             throw e;
@@ -953,19 +998,13 @@
     // wrapper around a BlockingQueue that throws an exception when it's closed
     // Each stream has one of these
 
-    String getRequestBody(String request) {
-        int bodystart = request.indexOf(CRLF+CRLF);
-        String body;
-        if (bodystart == -1)
-            body = "";
-        else
-            body = request.substring(bodystart+4);
-        return body;
+    byte[] getRequestBody(Http1InitialRequest request) {
+        return request.body;
     }
 
     @SuppressWarnings({"rawtypes","unchecked"})
-    void addRequestBodyToQueue(String body, Queue q) throws IOException {
-        ByteBuffer buf = ByteBuffer.wrap(body.getBytes(StandardCharsets.US_ASCII));
+    void addRequestBodyToQueue(byte[] body, Queue q) throws IOException {
+        ByteBuffer buf = ByteBuffer.wrap(body);
         DataFrame df = new DataFrame(1, DataFrame.END_STREAM, buf);
         // only used for primordial stream
         q.put(df);
--- a/test/jdk/java/net/httpclient/http2/server/NoBodyHandler.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/NoBodyHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
--- a/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -23,8 +23,8 @@
 
 import java.io.*;
 import java.net.*;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/PushHandler.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,18 +24,18 @@
 import java.io.*;
 import java.net.*;
 import java.nio.file.*;
-import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.internal.net.http.common.HttpHeadersImpl;
 
 public class PushHandler implements Http2Handler {
 
     final Path tempFile;
     final int loops;
-    final int file_size;
+    final long file_size;
 
-    public PushHandler(int file_size, int loops) throws Exception {
-        tempFile = TestUtil.getAFile(file_size);
+    public PushHandler(Path file, int loops) throws Exception {
+        tempFile = file;
         this.loops = loops;
-        this.file_size = file_size;
+        this.file_size = Files.size(file);
     }
 
     int invocation = 0;
@@ -46,9 +46,10 @@
             invocation++;
 
             if (ee.serverPushAllowed()) {
+                URI requestURI = ee.getRequestURI();
                 for (int i=0; i<loops; i++) {
                     InputStream is = new FileInputStream(tempFile.toFile());
-                    URI u = new URI ("http://www.foo.com/x/y/z/" + Integer.toString(i));
+                    URI u = requestURI.resolve("/x/y/z/" + Integer.toString(i));
                     HttpHeadersImpl h = new HttpHeadersImpl();
                     h.addHeader("X-foo", "bar");
                     ee.serverPush(u, h, is);
--- a/test/jdk/java/net/httpclient/http2/server/Queue.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Queue.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -48,6 +48,14 @@
         return q.size();
     }
 
+    public synchronized boolean isClosed() {
+        return closed;
+    }
+
+    public synchronized boolean isClosing() {
+        return closing;
+    }
+
     public synchronized void put(T obj) throws IOException {
         Objects.requireNonNull(obj);
         if (closed || closing) {
--- a/test/jdk/java/net/httpclient/http2/server/TestUtil.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/TestUtil.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 public class TestUtil {
 
+    static final Path CWD = Paths.get(".");
     final static String fileContent = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // repeated
 
     public static Path getAFile(int size) throws IOException {
@@ -44,8 +45,7 @@
 
     public static Path tempFile() {
         try {
-            Path p = Files.createTempFile("foo", "test");
-            p.toFile().deleteOnExit();
+            Path p = Files.createTempFile(CWD, "TestUtil_tmp_", "_HTTPClient");
             return p;
         } catch (IOException e) {
             throw new UncheckedIOException(e);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/offline/DelegatingHttpClient.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import java.io.IOException;
+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 java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+/**
+ * An HttpClient that delegates all its operations to the given client.
+ */
+public class DelegatingHttpClient extends HttpClient {
+
+    private final HttpClient client;
+
+    public DelegatingHttpClient(HttpClient client) {
+        this.client = client;
+    }
+
+    @Override
+    public Optional<CookieHandler> cookieHandler() {
+        return client.cookieHandler();
+    }
+
+    @Override
+    public Redirect followRedirects() {
+        return client.followRedirects();
+    }
+
+    @Override
+    public Optional<ProxySelector> proxy() {
+        return client.proxy();
+    }
+
+    @Override
+    public SSLContext sslContext() {
+        return client.sslContext();
+    }
+
+    @Override
+    public SSLParameters sslParameters() {
+        return client.sslParameters();
+    }
+
+    @Override
+    public Optional<Authenticator> authenticator() {
+        return client.authenticator();
+    }
+
+    @Override
+    public Version version() {
+        return client.version();
+    }
+
+    @Override
+    public Optional<Executor> executor() {
+        return client.executor();
+    }
+
+    @Override
+    public <T> HttpResponse<T> send(HttpRequest request,
+                                    HttpResponse.BodyHandler<T> responseBodyHandler)
+            throws IOException, InterruptedException {
+        return client.send(request, responseBodyHandler);
+    }
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest request,
+              HttpResponse.BodyHandler<T> responseBodyHandler) {
+        return client.sendAsync(request, responseBodyHandler);
+    }
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest request,
+              HttpResponse.BodyHandler<T> responseBodyHandler,
+              HttpResponse.PushPromiseHandler<T> pushPromiseHandler) {
+        return client.sendAsync(request, responseBodyHandler, pushPromiseHandler);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/offline/FixedHttpHeaders.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.net.http.HttpHeaders;
+
+/**
+ * An HttpHeaders consisting of the given name value pairs.
+ */
+public class FixedHttpHeaders extends HttpHeaders {
+
+    private final Map<String, List<String>> map =
+            new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+    @Override
+    public Map<String, List<String>> map() {
+        return map;
+    }
+
+    /**
+     * Creates an HttpHeaders of the given name value pairs.
+     */
+    public static HttpHeaders of(String... params) {
+        Objects.requireNonNull(params);
+        if ((params.length & 0x1) != 0)
+            throw new IllegalArgumentException("odd number of params");
+        FixedHttpHeaders headers = new FixedHttpHeaders();
+        for (int i = 0; i < params.length; i += 2) {
+            String name = params[i];
+            String value = params[i + 1];
+            headers.map.computeIfAbsent(name, k -> new ArrayList<>(1))
+                       .add(value);
+        }
+        return headers;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/offline/FixedHttpResponse.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ */
+
+import javax.net.ssl.SSLSession;
+import java.net.URI;
+import java.util.Optional;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+/**
+ * An HttpResponse consisting of the given state.
+ */
+public class FixedHttpResponse<T> implements HttpResponse<T> {
+
+    private final int statusCode;
+    private final HttpRequest request;
+    private final HttpHeaders headers;
+    private final T body;
+    private final SSLSession sslSession;
+    private final URI uri;
+    private final HttpClient.Version version;
+
+    public FixedHttpResponse(int statusCode,
+                             HttpRequest request,
+                             HttpHeaders headers,
+                             T body,
+                             SSLSession sslSession,
+                             URI uri,
+                             HttpClient.Version version) {
+        this.statusCode = statusCode;
+        this.request = request;
+        this.headers = headers;
+        this.body = body;
+        this.sslSession = sslSession;
+        this.uri = uri;
+        this.version = version;
+    }
+
+    @Override
+    public int statusCode() {
+        return statusCode;
+    }
+
+    @Override
+    public HttpRequest request() {
+        return request;
+    }
+
+    @Override
+    public Optional<HttpResponse<T>> previousResponse() {
+        return Optional.empty();
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        return headers;
+    }
+
+    @Override
+    public T body() {
+        return body;
+    }
+
+    @Override
+    public Optional<SSLSession> sslSession() {
+        return Optional.ofNullable(sslSession);
+    }
+
+    @Override
+    public URI uri() {
+        return uri;
+    }
+
+    @Override
+    public HttpClient.Version version() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        return sb.append(super.toString()).append(" [ ")
+                .append("status code: ").append(statusCode)
+                .append(", request: ").append(request)
+                .append(", headers: ").append(headers)
+                .append(" ]")
+                .toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/offline/FixedResponseHttpClient.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Flow;
+import java.util.concurrent.SubmissionPublisher;
+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.BodyHandler;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.PushPromiseHandler;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.ByteBuffer.wrap;
+
+/**
+ * An HttpClient that returns a given fixed response.
+ * Suitable for testing where network connections are to be avoided.
+ */
+public class FixedResponseHttpClient extends DelegatingHttpClient {
+    private final ByteBuffer responseBodyBytes;
+    private final int responseStatusCode;
+    private final HttpHeaders responseHeaders;
+    private final HttpClient.Version responseVersion;
+    private final HttpResponse.ResponseInfo responseInfo;
+
+    private FixedResponseHttpClient(HttpClient.Builder builder,
+                                    int responseStatusCode,
+                                    HttpHeaders responseHeaders,
+                                    ByteBuffer responseBodyBytes) {
+        super(builder.build());
+        this.responseStatusCode = responseStatusCode;
+        this.responseHeaders = responseHeaders;
+        this.responseBodyBytes = responseBodyBytes;
+        this.responseVersion = HttpClient.Version.HTTP_1_1; // should be added to constructor
+        this.responseInfo = new FixedResponseInfo();
+    }
+
+    /**
+     * Creates a new HttpClient that returns a fixed response,
+     * constructed from the given values, for every request sent.
+     */
+    public static HttpClient createClientFrom(HttpClient.Builder builder,
+                                              int responseStatusCode,
+                                              HttpHeaders responseHeaders,
+                                              String responseBody) {
+        return new FixedResponseHttpClient(builder,
+                                           responseStatusCode,
+                                           responseHeaders,
+                                           wrap(responseBody.getBytes(UTF_8)));
+    }
+
+    /**
+     * Creates a new HttpClient that returns a fixed response,
+     * constructed from the given values, for every request sent.
+     */
+    public static HttpClient createClientFrom(HttpClient.Builder builder,
+                                              int responseStatusCode,
+                                              HttpHeaders responseHeaders,
+                                              Path path) {
+        try {
+            return new FixedResponseHttpClient(builder,
+                                               responseStatusCode,
+                                               responseHeaders,
+                                               wrap(Files.readAllBytes(path)));
+        } catch (IOException ioe) {
+            throw new UncheckedIOException(ioe);
+        }
+    }
+
+    /**
+     * Creates a new HttpClient that returns a fixed response,
+     * constructed from the given values, for every request sent.
+     */
+    public static HttpClient createClientFrom(HttpClient.Builder builder,
+                                              int responseStatusCode,
+                                              HttpHeaders responseHeaders,
+                                              byte[] responseBody) {
+        return new FixedResponseHttpClient(builder,
+                responseStatusCode,
+                responseHeaders,
+                wrap(responseBody));
+    }
+
+    private static final ByteBuffer ECHO_SENTINAL = ByteBuffer.wrap(new byte[] {});
+
+    /**
+     * Creates a new HttpClient that returns a fixed response,
+     * constructed from the given values, for every request sent.
+     */
+    public static HttpClient createEchoClient(HttpClient.Builder builder,
+                                              int responseStatusCode,
+                                              HttpHeaders responseHeaders) {
+        return new FixedResponseHttpClient(builder,
+                                           responseStatusCode,
+                                           responseHeaders,
+                                           ECHO_SENTINAL);
+    }
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest request, BodyHandler<T> responseBodyHandler) {
+        return sendAsync(request, responseBodyHandler, null);
+    }
+
+    @Override
+    public <T> HttpResponse<T> send(HttpRequest request,
+                                    BodyHandler<T> responseBodyHandler)
+            throws IOException, InterruptedException {
+        return sendAsync(request, responseBodyHandler).join();  // unwrap exceptions if needed
+    }
+
+    class FixedResponseInfo implements HttpResponse.ResponseInfo {
+        private final int statusCode;
+        private final HttpHeaders headers;
+        private final HttpClient.Version version;
+
+        FixedResponseInfo() {
+            this.statusCode = responseStatusCode;
+            this.headers = responseHeaders;
+            this.version = HttpClient.Version.HTTP_1_1;
+        }
+
+        /**
+         * Provides the response status code
+         * @return the response status code
+         */
+        public int statusCode() {
+            return statusCode;
+        }
+
+        /**
+         * Provides the response headers
+         * @return the response headers
+         */
+        public HttpHeaders headers() {
+            return headers;
+        }
+
+        /**
+         * provides the response protocol version
+         * @return the response protocol version
+         */
+        public HttpClient.Version version() {
+            return version;
+        }
+    }
+
+    @Override
+    public <T> CompletableFuture<HttpResponse<T>>
+    sendAsync(HttpRequest request,
+              BodyHandler<T> responseBodyHandler,
+              PushPromiseHandler<T> pushPromiseHandler) {
+        List<ByteBuffer> responseBody = List.of(responseBodyBytes.duplicate());
+
+        // Push promises can be mocked too, if needed
+
+        Optional<HttpRequest.BodyPublisher> obp = request.bodyPublisher();
+        if (obp.isPresent()) {
+            ConsumingSubscriber subscriber = new ConsumingSubscriber();
+            obp.get().subscribe(subscriber);
+            if (responseBodyBytes == ECHO_SENTINAL) {
+                responseBody = subscriber.buffers;
+            }
+        }
+
+        BodySubscriber<T> bodySubscriber =
+                responseBodyHandler.apply(responseInfo);
+        SubmissionPublisher<List<ByteBuffer>> publisher = new SubmissionPublisher<>();
+        publisher.subscribe(bodySubscriber);
+        publisher.submit(responseBody);
+        publisher.close();
+
+        CompletableFuture<HttpResponse<T>> cf = new CompletableFuture<>();
+        bodySubscriber.getBody().whenComplete((body, throwable) -> {
+                    if (body != null)
+                        cf.complete(new FixedHttpResponse<>(
+                                responseStatusCode,
+                                request,
+                                responseHeaders,
+                                body,
+                                null,
+                                request.uri(),
+                                request.version().orElse(Version.HTTP_2)));
+                    else
+                        cf.completeExceptionally(throwable);
+                }
+        );
+
+        return cf;
+    }
+
+    /**
+     * A Subscriber that demands and consumes all the Publishers data,
+     * after which makes it directly available.
+     */
+    private static class ConsumingSubscriber implements Flow.Subscriber<ByteBuffer> {
+        final List<ByteBuffer> buffers = Collections.synchronizedList(new ArrayList<>());
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            subscription.request(Long.MAX_VALUE);
+        }
+
+        @Override public void onNext(ByteBuffer item) {
+            buffers.add(item.duplicate());
+        }
+
+        @Override public void onError(Throwable throwable) { assert false : "Unexpected"; }
+
+        @Override public void onComplete() { /* do nothing */ }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/offline/OfflineTesting.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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.
+ */
+
+/*
+ * @test
+ * @summary Demonstrates how to achieve testing without network connections
+ * @build FixedHttpHeaders DelegatingHttpClient FixedHttpResponse FixedResponseHttpClient
+ * @run testng/othervm OfflineTesting
+ */
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import org.testng.annotations.Test;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class OfflineTesting {
+
+    private static HttpClient getClient() {
+        // be sure to return the appropriate client when testing
+        //return HttpClient.newHttpClient();
+        return FixedResponseHttpClient.createClientFrom(
+                HttpClient.newBuilder(),
+                200,
+                FixedHttpHeaders.of("Server",  "nginx",
+                                    "Content-Type", "text/html"),
+                "A response message");
+    }
+
+    @Test
+    public void testResponseAsString() {
+        HttpClient client = getClient();
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://openjdk.java.net/"))
+                .build();
+
+        client.sendAsync(request, BodyHandlers.ofString())
+                .thenAccept(response -> {
+                    System.out.println("response: " + response);
+                    assertEquals(response.statusCode(), 200);
+                    assertTrue(response.headers().firstValue("Server").isPresent());
+                    assertEquals(response.body(), "A response message"); } )
+                .join();
+    }
+
+    @Test
+    public void testResponseAsByteArray() {
+        HttpClient client = getClient();
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://openjdk.java.net/"))
+                .build();
+
+        client.sendAsync(request, BodyHandlers.ofByteArray())
+                .thenAccept(response -> {
+                    System.out.println("response: " + response);
+                    assertEquals(response.statusCode(), 200);
+                    assertTrue(response.headers().firstValue("Content-Type").isPresent());
+                    assertEquals(response.body(), "A response message".getBytes(UTF_8)); } )
+                .join();
+    }
+
+    @Test
+    public void testFileNotFound() {
+        //HttpClient client = HttpClient.newHttpClient();
+        HttpClient client = FixedResponseHttpClient.createClientFrom(
+                HttpClient.newBuilder(),
+                404,
+                FixedHttpHeaders.of("Connection",  "keep-alive",
+                                    "Content-Length", "162",
+                                    "Content-Type", "text/html",
+                                    "Date", "Mon, 15 Jan 2018 15:01:16 GMT",
+                                    "Server", "nginx"),
+                "<html>\n" +
+                "<head><title>404 Not Found</title></head>\n" +
+                "<body bgcolor=\"white\">\n" +
+                "<center><h1>404 Not Found</h1></center>\n" +
+                "<hr><center>nginx</center>\n" +
+                "</body>\n" +
+                "</html>");
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://openjdk.java.net/notFound"))
+                .build();
+
+        client.sendAsync(request, BodyHandlers.ofString())
+                .thenAccept(response -> {
+                    assertEquals(response.statusCode(), 404);
+                    response.headers().firstValue("Content-Type")
+                            .ifPresentOrElse(type -> assertEquals(type, "text/html"),
+                                             () -> fail("Content-Type not present"));
+                    assertTrue(response.body().contains("404 Not Found")); } )
+                .join();
+    }
+
+    @Test
+    public void testEcho() {
+        HttpClient client = FixedResponseHttpClient.createEchoClient(
+                HttpClient.newBuilder(),
+                200,
+                FixedHttpHeaders.of("Connection",  "keep-alive"));
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://openjdk.java.net/echo"))
+                .POST(BodyPublishers.ofString("Hello World"))
+                .build();
+
+        client.sendAsync(request, BodyHandlers.ofString())
+                .thenAccept(response -> {
+                    System.out.println("response: " + response);
+                    assertEquals(response.statusCode(), 200);
+                    assertEquals(response.body(), "Hello World"); } )
+                .join();
+    }
+
+    @Test
+    public void testEchoBlocking() throws IOException, InterruptedException {
+        HttpClient client = FixedResponseHttpClient.createEchoClient(
+                HttpClient.newBuilder(),
+                200,
+                FixedHttpHeaders.of("Connection",  "keep-alive"));
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://openjdk.java.net/echo"))
+                .POST(BodyPublishers.ofString("Hello chegar!!"))
+                .build();
+
+        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+        System.out.println("response: " + response);
+        assertEquals(response.statusCode(), 200);
+        assertEquals(response.body(), "Hello chegar!!");
+    }
+}
--- a/test/jdk/java/net/httpclient/security/0.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/0.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -40,28 +40,7 @@
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
 
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
 
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/1.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/1.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,35 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:${port.number}/files/foo.txt", "GET";
+    permission java.net.URLPermission "http://localhost:${port.number}/files/foo.txt", "GET";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
-
--- a/test/jdk/java/net/httpclient/security/10.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/10.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -33,34 +33,11 @@
     permission java.lang.RuntimePermission "createClassLoader";
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:${port.number}/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "http://localhost:${port.number}/files/foo.txt", "GET:*";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/11.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/11.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -33,36 +33,13 @@
     permission java.lang.RuntimePermission "createClassLoader";
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:${port.number}/files/foo.txt", "GET:*";
-    permission java.net.URLPermission "socket://127.0.0.1:${port.number1}", "CONNECT";
+    permission java.net.URLPermission "http://localhost:${port.number}/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "socket://localhost:${port.number1}", "CONNECT";
 };
 
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/12.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/12.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -33,36 +33,13 @@
     permission java.lang.RuntimePermission "createClassLoader";
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:${port.number}/files/foo.txt", "GET:*";
-    permission java.net.URLPermission "socket://127.0.0.1:${port.number1}", "CONNECT";
+    permission java.net.URLPermission "http://localhost:${port.number}/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "socket://localhost:${port.number1}", "CONNECT";
 };
 
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/14.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/14.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET";
+    permission java.net.URLPermission "http://localhost:*/files/foo.txt", "GET";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/15.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/15.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -33,7 +33,7 @@
     permission java.lang.RuntimePermission "createClassLoader";
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "http://localhost:*/files/foo.txt", "GET:*";
 
     // Test checks for this explicitly
     permission java.lang.RuntimePermission "foobar";
@@ -43,28 +43,5 @@
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/2.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/2.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/files/*", "GET";
+    permission java.net.URLPermission "http://localhost:*/files/*", "GET";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/3.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/3.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/redirect/foo.txt", "GET";
+    permission java.net.URLPermission "http://localhost:*/redirect/foo.txt", "GET";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/4.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/4.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,35 +34,12 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/redirect/foo.txt", "GET";
-    permission java.net.URLPermission "http://127.0.0.1:*/redirect/bar.txt", "GET";
+    permission java.net.URLPermission "http://localhost:*/redirect/foo.txt", "GET";
+    permission java.net.URLPermission "http://localhost:*/redirect/bar.txt", "GET";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/5.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/5.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/redirect/bar.txt", "GET";
+    permission java.net.URLPermission "http://localhost:*/redirect/bar.txt", "GET";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/6.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/6.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "POST";
+    permission java.net.URLPermission "http://localhost:*/files/foo.txt", "POST";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/7.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/7.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:X-Bar";
+    permission java.net.URLPermission "http://localhost:*/files/foo.txt", "GET:X-Bar";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/8.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/8.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:X-Foo1,X-Foo,X-Bar";
+    permission java.net.URLPermission "http://localhost:*/files/foo.txt", "GET:X-Foo1,X-Foo,X-Bar";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/9.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/9.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -34,34 +34,11 @@
 
 
     // permissions specific to this test
-    permission java.net.URLPermission "http://127.0.0.1:*/files/foo.txt", "GET:*";
+    permission java.net.URLPermission "http://localhost:*/files/foo.txt", "GET:*";
 };
 
 // For proxy only. Not being tested
 grant codebase "file:${test.classes}/proxydir/-" {
     permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect";
-    permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
+    permission java.net.SocketPermission "localhost:1024-", "connect,resolve";
 };
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write";  // delete???
-
-    permission java.util.PropertyPermission "jdk.httpclient.*","read";
-
-    permission java.net.NetPermission "getProxySelector";
-};
-
--- a/test/jdk/java/net/httpclient/security/Driver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/Driver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,7 @@
  * @test
  * @bug 8087112
  * @library /lib/testlibrary/
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          java.logging
  *          jdk.httpserver
  * @build jdk.testlibrary.SimpleSSLContext jdk.testlibrary.Utils
@@ -49,6 +49,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -59,7 +62,7 @@
  */
 public class Driver {
     // change the default value to "true" to get the subprocess traces.
-    final static boolean DEBUG = Boolean.parseBoolean(System.getProperty("test.debug", "false"));
+    final static boolean DEBUG = Boolean.parseBoolean(System.getProperty("test.debug", "true"));
 
     public static void main(String[] args) throws Throwable {
         System.out.println("Starting Driver");
@@ -70,16 +73,18 @@
         System.out.println("DONE");
     }
 
+    static final Path CWD = Paths.get(".");
+
     static class Logger extends Thread {
         private final OutputStream ps;
         private final InputStream stdout;
 
-        Logger(String cmdLine, Process p, String dir) throws IOException {
+        Logger(String cmdLine, Process p) throws IOException {
             super();
             setDaemon(true);
             cmdLine = "Command line = [" + cmdLine + "]\n";
             stdout = p.getInputStream();
-            File f = File.createTempFile("debug", ".txt", new File(dir));
+            File f = Files.createTempFile(CWD, "debug", ".txt").toFile();
             ps = new FileOutputStream(f);
             ps.write(cmdLine.getBytes());
             ps.flush();
@@ -127,7 +132,6 @@
             cmd.add("-Dtest.src=" + testSrc);
             cmd.add("-Dtest.classes=" + testClasses);
             cmd.add("-Djava.security.manager");
-            cmd.add("--add-modules=jdk.incubator.httpclient");
             cmd.add("-Djava.security.policy=" + testSrc + sep + policy);
             cmd.add("-Dport.number=" + Integer.toString(Utils.getFreePort()));
             cmd.add("-Dport.number1=" + Integer.toString(Utils.getFreePort()));
@@ -144,7 +148,7 @@
             String cmdLine = cmd.stream().collect(Collectors.joining(" "));
             long start = System.currentTimeMillis();
             Process child = processBuilder.start();
-            Logger log = new Logger(cmdLine, child, testClasses);
+            Logger log = new Logger(cmdLine, child);
             log.start();
             retval = child.waitFor();
             long elapsed = System.currentTimeMillis() - start;
--- a/test/jdk/java/net/httpclient/security/Security.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/Security.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8087112
- * @modules jdk.incubator.httpclient
+ * @modules java.net.http
  *          java.logging
  *          jdk.httpserver
  * @library /lib/testlibrary/
@@ -50,7 +50,6 @@
 // Tests 1, 10, 11 and 12 executed from Driver
 
 import com.sun.net.httpserver.Headers;
-import com.sun.net.httpserver.HttpContext;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
@@ -61,15 +60,16 @@
 import java.io.OutputStream;
 import java.lang.reflect.Constructor;
 import java.net.BindException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.ProxySelector;
 import java.net.URI;
 import java.net.URLClassLoader;
 import java.net.URL;
-import jdk.incubator.http.HttpHeaders;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -89,40 +89,62 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.lang.reflect.InvocationTargetException;
-import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static java.net.http.HttpResponse.BodyHandlers.ofString;
 
 /**
  * Security checks test
  */
 public class Security {
 
-    static HttpServer s1 = null;
-    static ExecutorService executor=null;
+    static HttpServer s1;
+    static ExecutorService executor;
     static int port, proxyPort;
     static HttpClient client;
     static String httproot, fileuri, fileroot, redirectroot;
     static List<HttpClient> clients = new LinkedList<>();
     static URI uri;
 
-    interface Test {
-        void execute() throws IOException, InterruptedException;
-    }
+    interface ThrowingRunnable { void run() throws Throwable; }
 
     static class TestAndResult {
-        Test test;
-        boolean result;
+        private final ThrowingRunnable runnable;
+        private final boolean expectSecurityException;
+
+        TestAndResult(boolean expectSecurityException, ThrowingRunnable runnable) {
+            this.expectSecurityException = expectSecurityException;
+            this.runnable = runnable;
+        }
+
+        static TestAndResult of(boolean expectSecurityException,
+                                ThrowingRunnable runnable) {
+            return new TestAndResult(expectSecurityException, runnable);
+        }
 
-        TestAndResult (Test t, boolean result) {
-            this.test = t;
-            this.result = result;
+        void runWithPolicy(String policy) {
+            System.out.println("Using policy file: " + policy);
+            try {
+                runnable.run();
+                if (expectSecurityException) {
+                    String msg = "FAILED: expected security exception not thrown";
+                    System.out.println(msg);
+                    throw new RuntimeException(msg);
+                }
+                System.out.println (policy + " succeeded as expected");
+            } catch (BindException e) {
+                System.exit(10);
+            } catch (SecurityException e) {
+                if (!expectSecurityException) {
+                    System.out.println("UNEXPECTED security Exception: " + e);
+                    throw new RuntimeException("UNEXPECTED security Exception", e);
+                }
+                System.out.println(policy + " threw SecurityException as expected: " + e);
+            } catch (Throwable t) {
+                throw new AssertionError(t);
+            }
         }
     }
 
-    static TestAndResult test(boolean result, Test t) {
-        return new TestAndResult(t, result);
-    }
-
-    static TestAndResult[] tests;
+    static TestAndResult[] tests = createTests();
     static String testclasses;
     static File subdir;
 
@@ -143,7 +165,7 @@
         movefile("ProxyServer$Connection.class");
         movefile("ProxyServer$1.class");
 
-        URL url = subdir.toURL();
+        URL url = subdir.toURI().toURL();
         System.out.println("URL for class loader = " + url);
         URLClassLoader urlc = new URLClassLoader(new URL[] {url});
         proxyClass = Class.forName("ProxyServer", true, urlc);
@@ -164,7 +186,7 @@
         }
     }
 
-    static Object getProxy(int port, boolean b) throws Throwable {
+    static Object createProxy(int port, boolean b) throws Throwable {
         try {
             return proxyConstructor.newInstance(port, b);
         } catch (InvocationTargetException e) {
@@ -175,95 +197,106 @@
     static Class<?> proxyClass;
     static Constructor<?> proxyConstructor;
 
-    static void setupTests() {
-        tests = new TestAndResult[]{
+    static TestAndResult[] createTests() {
+        return new TestAndResult[] {
             // (0) policy does not have permission for file. Should fail
-            test(false, () -> { // Policy 0
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(true, () -> { // Policy 0
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (1) policy has permission for file URL
-            test(true, () -> { //Policy 1
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(false, () -> { //Policy 1
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (2) policy has permission for all file URLs under /files
-            test(true, () -> { // Policy 2
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(false, () -> { // Policy 2
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (3) policy has permission for first URL but not redirected URL
-            test(false, () -> { // Policy 3
-                URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt");
+            TestAndResult.of(true, () -> { // Policy 3
+                URI u = URI.create("http://localhost:" + port + "/redirect/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (4) policy has permission for both first URL and redirected URL
-            test(true, () -> { // Policy 4
-                URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt");
+            TestAndResult.of(false, () -> { // Policy 4
+                URI u = URI.create("http://localhost:" + port + "/redirect/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (5) policy has permission for redirected but not first URL
-            test(false, () -> { // Policy 5
-                URI u = URI.create("http://127.0.0.1:" + port + "/redirect/foo.txt");
+            TestAndResult.of(true, () -> { // Policy 5
+                URI u = URI.create("http://localhost:" + port + "/redirect/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (6) policy has permission for file URL, but not method
-            test(false, () -> { //Policy 6
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(true, () -> { //Policy 6
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (7) policy has permission for file URL, method, but not header
-            test(false, () -> { //Policy 7
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(true, () -> { //Policy 7
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u)
                                                  .header("X-Foo", "bar")
                                                  .GET()
                                                  .build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (8) policy has permission for file URL, method and header
-            test(true, () -> { //Policy 8
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(false, () -> { //Policy 8
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u)
                                                  .header("X-Foo", "bar")
                                                  .GET()
                                                  .build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (9) policy has permission for file URL, method and header
-            test(true, () -> { //Policy 9
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(false, () -> { //Policy 9
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u)
                                                  .headers("X-Foo", "bar", "X-Bar", "foo")
                                                  .GET()
                                                  .build();
-                HttpResponse<?> response = client.send(request, asString());
+                HttpResponse<?> response = client.send(request, ofString());
+                System.out.println("Received response:" + response);
             }),
             // (10) policy has permission for destination URL but not for proxy
-            test(false, () -> { //Policy 10
+            TestAndResult.of(true, () -> { //Policy 10
                 directProxyTest(proxyPort, true);
             }),
             // (11) policy has permission for both destination URL and proxy
-            test(true, () -> { //Policy 11
+            TestAndResult.of(false, () -> { //Policy 11
                 directProxyTest(proxyPort, true);
             }),
             // (12) policy has permission for both destination URL and proxy
-            test(false, () -> { //Policy 11
+            TestAndResult.of(true, () -> { //Policy 12 ( 11 & 12 are the same )
                 directProxyTest(proxyPort, false);
             }),
             // (13) async version of test 0
-            test(false, () -> { // Policy 0
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(true, () -> { // Policy 0
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
                 try {
-                    HttpResponse<?> response = client.sendAsync(request, asString()).get();
+                    HttpResponse<?> response = client.sendAsync(request, ofString()).get();
+                    System.out.println("Received response:" + response);
                 } catch (ExecutionException e) {
                     if (e.getCause() instanceof SecurityException) {
                         throw (SecurityException)e.getCause();
@@ -273,11 +306,12 @@
                 }
             }),
             // (14) async version of test 1
-            test(true, () -> { //Policy 1
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(false, () -> { //Policy 1
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
                 try {
-                    HttpResponse<?> response = client.sendAsync(request, asString()).get();
+                    HttpResponse<?> response = client.sendAsync(request, ofString()).get();
+                    System.out.println("Received response:" + response);
                 } catch (ExecutionException e) {
                     if (e.getCause() instanceof SecurityException) {
                         throw (SecurityException)e.getCause();
@@ -288,16 +322,16 @@
             }),
             // (15) check that user provided unprivileged code running on a worker
             //      thread does not gain ungranted privileges.
-            test(false, () -> { //Policy 12
-                URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+            TestAndResult.of(true, () -> { //Policy 12
+                URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
                 HttpRequest request = HttpRequest.newBuilder(u).GET().build();
-                HttpResponse.BodyHandler<String> sth = asString();
+                HttpResponse.BodyHandler<String> sth = ofString();
 
                 CompletableFuture<HttpResponse<String>> cf =
                     client.sendAsync(request, new HttpResponse.BodyHandler<String>() {
                         @Override
-                        public HttpResponse.BodySubscriber<String> apply(int status, HttpHeaders responseHeaders)  {
-                            final HttpResponse.BodySubscriber<String> stproc = sth.apply(status, responseHeaders);
+                        public HttpResponse.BodySubscriber<String> apply(HttpResponse.ResponseInfo rinfo) {
+                            final HttpResponse.BodySubscriber<String> stproc = sth.apply(rinfo);
                             return new HttpResponse.BodySubscriber<String>() {
                                 @Override
                                 public CompletionStage<String> getBody() {
@@ -331,7 +365,8 @@
                     }
                 );
                 try {
-                    cf.join();
+                    HttpResponse<String> response = cf.join();
+                    System.out.println("Received response:" + response);
                 } catch (CompletionException e) {
                     Throwable t = e.getCause();
                     if (t instanceof SecurityException)
@@ -349,52 +384,43 @@
     private static void directProxyTest(int proxyPort, boolean samePort)
         throws IOException, InterruptedException
     {
-        Object proxy = null;
-        try {
-            proxy = getProxy(proxyPort, true);
-        } catch (BindException e) {
-            System.out.println("Bind failed");
-            throw e;
-        } catch (Throwable ee) {
-            throw new RuntimeException(ee);
+        System.out.println("proxyPort:" + proxyPort + ", samePort:" + samePort);
+
+        int p = proxyPort;
+        if (samePort) {
+            Object proxy;
+            try {
+                proxy = createProxy(p, true);
+            } catch (BindException e) {
+                System.out.println("Bind failed");
+                throw e;
+            } catch (Throwable ee) {
+                throw new RuntimeException(ee);
+            }
+        } else {
+            while (p == proxyPort || p == port) {
+                // avoid ports that may be granted permission
+                p++;
+                if (p > 65535) {
+                    p = 32000; // overflow
+                }
+            }
         }
-        System.out.println("Proxy port = " + proxyPort);
-        if (!samePort)
-            proxyPort++;
+        System.out.println("Proxy port, p:" + p);
 
-        InetSocketAddress addr = new InetSocketAddress("127.0.0.1", proxyPort);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                                                       p);
         HttpClient cl = HttpClient.newBuilder()
                                     .proxy(ProxySelector.of(addr))
                                     .build();
         clients.add(cl);
 
-        URI u = URI.create("http://127.0.0.1:" + port + "/files/foo.txt");
+        URI u = URI.create("http://localhost:" + port + "/files/foo.txt");
         HttpRequest request = HttpRequest.newBuilder(u)
                                          .headers("X-Foo", "bar", "X-Bar", "foo")
                                          .build();
-        HttpResponse<?> response = cl.send(request, asString());
-    }
-
-    static void runtest(Test r, String policy, boolean succeeds) {
-        System.out.println("Using policy file: " + policy);
-        try {
-            r.execute();
-            if (!succeeds) {
-                System.out.println("FAILED: expected security exception");
-                throw new RuntimeException("FAILED: expected security exception\"");
-            }
-            System.out.println (policy + " succeeded as expected");
-        } catch (BindException e) {
-            System.exit(10);
-        } catch (SecurityException e) {
-            if (succeeds) {
-                System.out.println("FAILED");
-                throw new RuntimeException(e);
-            }
-            System.out.println (policy + " threw exception as expected");
-        } catch (IOException | InterruptedException ee) {
-            throw new RuntimeException(ee);
-        }
+        HttpResponse<?> response = cl.send(request, ofString());
+        System.out.println("Received response:" + response);
     }
 
     public static void main(String[] args) throws Exception {
@@ -404,7 +430,7 @@
         } catch (BindException e) {
             System.exit(10);
         }
-        fileroot = System.getProperty ("test.src")+ "/docs";
+        fileroot = System.getProperty("test.src")+ "/docs";
         int testnum = Integer.parseInt(args[0]);
         String policy = args[0];
 
@@ -415,9 +441,8 @@
         clients.add(client);
 
         try {
-            setupTests();
             TestAndResult tr = tests[testnum];
-            runtest(tr.test, policy, tr.result);
+            tr.runWithPolicy(policy);
         } finally {
             s1.stop(0);
             executor.shutdownNow();
@@ -435,20 +460,17 @@
         logger.setLevel(Level.ALL);
         ch.setLevel(Level.ALL);
         logger.addHandler(ch);
-        String root = System.getProperty ("test.src")+ "/docs";
-        InetSocketAddress addr = new InetSocketAddress (port);
+        String root = System.getProperty("test.src")+ "/docs";
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
         s1 = HttpServer.create (addr, 0);
         if (s1 instanceof HttpsServer) {
-            throw new RuntimeException ("should not be httpsserver");
+            throw new RuntimeException("should not be httpsserver");
         }
-        HttpHandler h = new FileServerHandler (root);
-        HttpContext c = s1.createContext ("/files", h);
-
-        HttpHandler h1 = new RedirectHandler ("/redirect");
-        HttpContext c1 = s1.createContext ("/redirect", h1);
+        s1.createContext("/files", new FileServerHandler(root));
+        s1.createContext("/redirect", new RedirectHandler("/redirect"));
 
         executor = Executors.newCachedThreadPool();
-        s1.setExecutor (executor);
+        s1.setExecutor(executor);
         s1.start();
 
         if (port == 0)
@@ -459,8 +481,8 @@
             System.out.println("Port was assigned by Driver");
         }
         System.out.println("HTTP server port = " + port);
-        httproot = "http://127.0.0.1:" + port + "/files/";
-        redirectroot = "http://127.0.0.1:" + port + "/redirect/";
+        httproot = "http://localhost:" + port + "/files/";
+        redirectroot = "http://localhost:" + port + "/redirect/";
         uri = new URI(httproot);
         fileuri = httproot + "foo.txt";
     }
@@ -483,12 +505,10 @@
         }
 
         @Override
-        public synchronized void handle(HttpExchange t)
-                throws IOException {
-            byte[] buf = new byte[2048];
+        public synchronized void handle(HttpExchange t) throws IOException {
             System.out.println("Server: " + t.getRequestURI());
             try (InputStream is = t.getRequestBody()) {
-                while (is.read(buf) != -1) ;
+               is.readAllBytes();
             }
             increment();
             if (count() == 1) {
--- a/test/jdk/java/net/httpclient/security/filePerms/FileProcessorPermissionTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/security/filePerms/FileProcessorPermissionTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,9 +24,10 @@
 /*
  * @test
  * @summary Basic checks for SecurityException from body processors APIs
- * @run testng/othervm/java.security.policy=httpclient.policy FileProcessorPermissionTest
+ * @run testng/othervm/java.security.policy=allpermissions.policy FileProcessorPermissionTest
  */
 
+import java.io.File;
 import java.io.FilePermission;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -38,8 +39,10 @@
 import java.security.PrivilegedExceptionAction;
 import java.security.ProtectionDomain;
 import java.util.List;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
 import org.testng.annotations.Test;
 import static java.nio.file.StandardOpenOption.*;
 import static org.testng.Assert.*;
@@ -68,26 +71,18 @@
     @Test
     public void test() throws Exception {
         List<PrivilegedExceptionAction<?>> list = List.of(
-                () -> HttpRequest.BodyPublisher.fromFile(fromFilePath),
-
-                () -> HttpResponse.BodyHandler.asFile(asFilePath),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE, WRITE),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE, WRITE, READ),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE, WRITE, READ, DELETE_ON_CLOSE),
+                () -> HttpRequest.BodyPublishers.ofFile(fromFilePath),
 
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD),
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE),
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE, WRITE),
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE, WRITE, READ),
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE, WRITE, READ, DELETE_ON_CLOSE),
+                () -> BodyHandlers.ofFile(asFilePath),
+                () -> BodyHandlers.ofFile(asFilePath, CREATE),
+                () -> BodyHandlers.ofFile(asFilePath, CREATE, WRITE),
 
-                // TODO: what do these even mean by themselves, maybe ok means nothing?
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, DELETE_ON_CLOSE),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, READ)
+                () -> BodyHandlers.ofFileDownload(CWD),
+                () -> BodyHandlers.ofFileDownload(CWD, CREATE),
+                () -> BodyHandlers.ofFileDownload(CWD, CREATE, WRITE)
         );
 
-        // sanity, just run http ( no security manager )
+        // TEST 1 - sanity, just run ( no security manager )
         System.setSecurityManager(null);
         try {
             for (PrivilegedExceptionAction pa : list) {
@@ -108,11 +103,27 @@
             }
         }
 
-        // Run with limited permissions, i.e. just what is required
+        // TEST 2 - with all file permissions
+        AccessControlContext allFilesACC = withPermissions(
+                new FilePermission("<<ALL FILES>>" , "read,write")
+        );
+        for (PrivilegedExceptionAction pa : list) {
+            try {
+                assert System.getSecurityManager() != null;
+                AccessController.doPrivileged(pa, allFilesACC);
+            } catch (PrivilegedActionException pae) {
+                fail("UNEXPECTED Exception:" + pae);
+                pae.printStackTrace();
+            }
+        }
+
+        // TEST 3 - with limited permissions, i.e. just what is required
         AccessControlContext minimalACC = withPermissions(
                 new FilePermission(fromFilePath.toString() , "read"),
-                new FilePermission(asFilePath.toString(), "read,write,delete"),
-                new FilePermission(CWD.toString(), "read,write,delete")
+                new FilePermission(asFilePath.toString(), "write"),
+                // ofFileDownload requires read and write to the dir
+                new FilePermission(CWD.toString(), "read,write"),
+                new FilePermission(CWD.toString() + File.separator + "*", "read,write")
         );
         for (PrivilegedExceptionAction pa : list) {
             try {
@@ -124,7 +135,7 @@
             }
         }
 
-        // Run with NO permissions, i.e. expect SecurityException
+        // TEST 4 - with NO permissions, i.e. expect SecurityException
         for (PrivilegedExceptionAction pa : list) {
             try {
                 assert System.getSecurityManager() != null;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/security/filePerms/SecurityBeforeFile.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Verifies security checks are performed before existence checks
+ *          in pre-defined body processors APIs
+ * @run testng/othervm SecurityBeforeFile
+ * @run testng/othervm/java.security.policy=nopermissions.policy SecurityBeforeFile
+ */
+
+import java.io.FileNotFoundException;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodyHandlers;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static java.nio.file.StandardOpenOption.*;
+import static org.testng.Assert.*;
+
+public class SecurityBeforeFile {
+
+    static final boolean hasSecurityManager = System.getSecurityManager() != null;
+    static final boolean hasNoSecurityManager = !hasSecurityManager;
+
+    @Test
+    public void BodyPublishersOfFile() {
+        Path p = Paths.get("doesNotExist.txt");
+        if (hasNoSecurityManager && Files.exists(p))
+            throw new AssertionError("Unexpected " + p);
+
+        try {
+            BodyPublishers.ofFile(p);
+            fail("UNEXPECTED, file " + p.toString() + " exists?");
+        } catch (SecurityException se) {
+            assertTrue(hasSecurityManager);
+            out.println("caught expected security exception: " + se);
+        } catch (FileNotFoundException fnfe) {
+            assertTrue(hasNoSecurityManager);
+            out.println("caught expected file not found exception: " + fnfe);
+        }
+    }
+
+    @DataProvider(name = "handlerOpenOptions")
+    public Object[][] handlerOpenOptions() {
+        return new Object[][] {
+                { new OpenOption[] {               } },
+                { new OpenOption[] { CREATE        } },
+                { new OpenOption[] { CREATE, WRITE } },
+        };
+    }
+
+    @Test(dataProvider = "handlerOpenOptions")
+    public void BodyHandlersOfFileDownload(OpenOption[] openOptions) {
+        Path p = Paths.get("doesNotExistDir");
+        if (hasNoSecurityManager && Files.exists(p))
+            throw new AssertionError("Unexpected " + p);
+
+        try {
+            BodyHandlers.ofFileDownload(p, openOptions);
+            fail("UNEXPECTED, file " + p.toString() + " exists?");
+        } catch (SecurityException se) {
+            assertTrue(hasSecurityManager);
+            out.println("caught expected security exception: " + se);
+        } catch (IllegalArgumentException iae) {
+            assertTrue(hasNoSecurityManager);
+            out.println("caught expected illegal argument exception: " + iae);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/security/filePerms/allpermissions.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+// bootstrap to get the test going, it will do its own restrictions
+grant codeBase "file:${test.classes}/*" {
+    permission java.security.AllPermission;
+};
+
--- a/test/jdk/java/net/httpclient/security/filePerms/httpclient.policy	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +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.
-//
-
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write,delete";
-
-    // ## look at the different property names!
-    permission java.util.PropertyPermission "jdk.httpclient.HttpClient.log","read";  // name!
-    permission java.util.PropertyPermission "jdk.httpclient.auth.retrylimit","read";
-    permission java.util.PropertyPermission "jdk.httpclient.connectionWindowSize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.enablepush","read";
-    permission java.util.PropertyPermission "jdk.httpclient.hpack.maxheadertablesize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.keepalive.timeout","read";
-    permission java.util.PropertyPermission "jdk.httpclient.maxframesize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.maxstreams","read";
-    permission java.util.PropertyPermission "jdk.httpclient.redirects.retrylimit","read";
-    permission java.util.PropertyPermission "jdk.httpclient.windowsize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.receiveBufferSize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.bufsize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.internal.selector.timeout","read";
-    permission java.util.PropertyPermission "jdk.internal.httpclient.debug","read";
-    permission java.util.PropertyPermission "jdk.internal.httpclient.hpack.debug","read";
-    permission java.util.PropertyPermission "jdk.internal.httpclient.hpack.log.level","read";
-    permission java.util.PropertyPermission "test.src","read";
-
-    permission java.net.NetPermission "getProxySelector";
-
-    permission java.security.SecurityPermission "createAccessControlContext";
-};
-
-// bootstrap to get the test going, it will do its own restrictions
-grant codeBase "file:${test.classes}/*" {
-    permission java.security.AllPermission;
-};
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/security/filePerms/nopermissions.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,26 @@
+//
+// 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.
+//
+
+grant codeBase "file:${test.classes}/*" {
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ssltest/CertificateTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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.
+ */
+
+import java.io.File;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+
+/*
+ * @test
+ * @build Server CertificateTest
+ * @run main/othervm CertificateTest good.keystore expectSuccess
+ * @run main/othervm CertificateTest bad.keystore expectFailure
+ * @run main/othervm
+ *      -Djdk.internal.httpclient.disableHostnameVerification
+ *       CertificateTest bad.keystore expectSuccess
+ * @run main/othervm
+ *      -Djdk.internal.httpclient.disableHostnameVerification=true
+ *       CertificateTest bad.keystore expectSuccess
+ * @run main/othervm
+ *      -Djdk.internal.httpclient.disableHostnameVerification=false
+ *       CertificateTest bad.keystore expectFailure
+ * @run main/othervm
+ *      -Djdk.internal.httpclient.disableHostnameVerification=xxyyzz
+ *       CertificateTest bad.keystore expectFailure
+ * @run main/othervm CertificateTest loopback.keystore expectSuccess
+ */
+
+/**
+ * The test runs a number of times. In all cases it uses a valid self-signed certificate
+ * that is installed in the trust store (so is trusted) and the same cert is supplied
+ * by the server for its own identity. Two servers on two different ports are used
+ * on the remote end.
+ *
+ * For the "good" run the cert contains the correct hostname of the target server
+ * and therefore should be accepted by the cert checking code in the client.
+ * For the "bad" run, the cert contains an invalid hostname, and should be rejected.
+ */
+public class CertificateTest {
+    static SSLContext ctx;
+    static SSLParameters params;
+    static boolean expectSuccess;
+    static String trustStoreProp;
+    static Server server;
+    static int port;
+
+    static String TESTSRC = System.getProperty("test.src");
+    public static void main(String[] args) throws Exception
+    {
+        try {
+            String keystore = args[0];
+            trustStoreProp = TESTSRC + File.separatorChar + keystore;
+
+            String passOrFail = args[1];
+
+            if (passOrFail.equals("expectSuccess")) {
+                expectSuccess = true;
+            } else {
+                expectSuccess = false;
+            }
+            server = new Server(trustStoreProp);
+            port = server.getPort();
+            System.setProperty("javax.net.ssl.trustStore", trustStoreProp);
+            System.setProperty("javax.net.ssl.trustStorePassword", "passphrase");
+            init();
+            test(args);
+        } finally {
+            server.stop();
+        }
+    }
+
+    static void init() throws Exception
+    {
+        ctx = SSLContext.getDefault();
+        params = ctx.getDefaultSSLParameters();
+        //params.setProtocols(new String[] { "TLSv1.2" });
+    }
+
+    static void test(String[] args) throws Exception
+    {
+        String uri_s;
+        if (args[0].equals("loopback.keystore"))
+            uri_s = "https://127.0.0.1:" + Integer.toString(port) + "/foo";
+        else
+            uri_s = "https://localhost:" + Integer.toString(port) + "/foo";
+        String error = null;
+        Exception exception = null;
+        System.out.println("Making request to " + uri_s);
+        HttpClient client = HttpClient.newBuilder()
+                .sslContext(ctx)
+                .sslParameters(params)
+                .build();
+
+        HttpRequest request = HttpRequest.newBuilder(new URI(uri_s))
+                .version(HttpClient.Version.HTTP_1_1)
+                .GET()
+                .build();
+
+        try {
+            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
+            System.out.printf("Status code %d received\n", response.statusCode());
+            if (expectSuccess && response.statusCode() != 200)
+                error = "Test failed: good: status should be 200";
+            else if (!expectSuccess)
+                error = "Test failed: bad: status should not be 200";
+        } catch (SSLException e) {
+            System.err.println("Caught Exception " + e + ". expectSuccess = " + expectSuccess);
+            exception = e;
+            if (expectSuccess)
+                error = "Test failed: expectSuccess:true, but got unexpected exception";
+        }
+        if (error != null)
+            throw new RuntimeException(error, exception);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/ssltest/Server.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+import com.sun.net.httpserver.*;
+import java.io.*;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.security.*;
+import java.util.*;
+import java.util.logging.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import javax.net.ssl.*;
+
+public class Server {
+
+    HttpsServer server;
+    final ExecutorService exec;
+    final int port;
+
+    // certfile: needs to be good or bad, ie. bad contains an otherwise valid
+    // cert but whose CN contains a different host. good must be correct
+
+    // assuming the TLS handshake succeeds, the server returns a 200 OK
+    // response with a short text string.
+    public Server(String certfile) throws Exception {
+        initLogger();
+        SSLContext ctx = getContext("TLSv1.2", certfile);
+        Configurator cfg = new Configurator(ctx);
+        InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(),0);
+        server = HttpsServer.create(addr, 10);
+        server.setHttpsConfigurator(cfg);
+        server.createContext("/", new MyHandler());
+        server.setExecutor((exec=Executors.newCachedThreadPool()));
+        port = server.getAddress().getPort();
+        System.out.println ("Listening on port " + port);
+        server.start();
+    }
+
+    int getPort() {
+        return port;
+    }
+
+    void stop() {
+        server.stop(1);
+        exec.shutdownNow();
+    }
+
+    SSLContext getContext(String protocol, String certfile) throws Exception {
+        char[] passphrase = "passphrase".toCharArray();
+        KeyStore ks = KeyStore.getInstance("JKS");
+        ks.load(new FileInputStream(certfile), passphrase);
+
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+        kmf.init(ks, passphrase);
+
+        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+        tmf.init(ks);
+
+        SSLContext ssl = SSLContext.getInstance(protocol);
+        ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+        return ssl;
+    }
+
+    Logger logger;
+
+    void initLogger() {
+        logger = Logger.getLogger("com.sun.net.httpserver");
+        Handler h = new ConsoleHandler();
+        logger.setLevel(Level.ALL);
+        h.setLevel(Level.ALL);
+        logger.addHandler(h);
+    }
+
+    String responseBody = "Greetings from localhost";
+
+    class MyHandler implements HttpHandler {
+
+        @Override
+        public void handle(HttpExchange e) throws IOException {
+            System.out.println("Server: received " + e.getRequestURI());
+            InputStream is = e.getRequestBody();
+            byte[] buf = new byte[128];
+            while (is.read(buf) != -1);
+            is.close();
+            e.sendResponseHeaders(200, responseBody.length());
+            OutputStream os = e.getResponseBody();
+            os.write(responseBody.getBytes("ISO8859_1"));
+            os.close();
+        }
+    }
+
+    class Configurator extends HttpsConfigurator {
+        public Configurator(SSLContext ctx) throws Exception {
+            super(ctx);
+        }
+
+        public void configure(HttpsParameters params) {
+            SSLParameters p = getSSLContext().getDefaultSSLParameters();
+            for (String cipher : p.getCipherSuites())
+                System.out.println("Cipher: " + cipher);
+            System.err.println("PArams = " + p);
+            params.setSSLParameters(p);
+        }
+    }
+}
Binary file test/jdk/java/net/httpclient/ssltest/bad.keystore has changed
Binary file test/jdk/java/net/httpclient/ssltest/good.keystore has changed
Binary file test/jdk/java/net/httpclient/ssltest/loopback.keystore has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/Abort.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,416 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       Abort
+ */
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.WebSocket.NORMAL_CLOSURE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class Abort {
+
+    private static final Class<NullPointerException> NPE = NullPointerException.class;
+    private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
+    private static final Class<IOException> IOE = IOException.class;
+
+    private DummyWebSocketServer server;
+    private WebSocket webSocket;
+
+    @AfterTest
+    public void cleanup() {
+        server.close();
+        webSocket.abort();
+    }
+
+    @Test
+    public void onOpenThenAbort() throws Exception {
+        int[] bytes = new int[]{
+                0x88, 0x00, // opcode=close
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        // messages are available
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                // unbounded request
+                webSocket.request(Long.MAX_VALUE);
+                webSocket.abort();
+            }
+        };
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        TimeUnit.SECONDS.sleep(5);
+        List<MockListener.Invocation> inv = listener.invocationsSoFar();
+        // no more invocations after onOpen as WebSocket was aborted
+        assertEquals(inv, List.of(MockListener.Invocation.onOpen(webSocket)));
+    }
+
+    @Test
+    public void onOpenThenOnTextThenAbort() throws Exception {
+        int[] bytes = new int[]{
+                0x81, 0x00, // opcode=text, fin=true
+                0x88, 0x00, // opcode=close
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                // unbounded request
+                webSocket.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            protected CompletionStage<?> onText0(WebSocket webSocket,
+                                                 CharSequence message,
+                                                 boolean last) {
+                webSocket.abort();
+                return super.onText0(webSocket, message, last);
+            }
+        };
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        TimeUnit.SECONDS.sleep(5);
+        List<MockListener.Invocation> inv = listener.invocationsSoFar();
+        // no more invocations after onOpen, onBinary as WebSocket was aborted
+        List<MockListener.Invocation> expected = List.of(
+                MockListener.Invocation.onOpen(webSocket),
+                MockListener.Invocation.onText(webSocket, "", true));
+        assertEquals(inv, expected);
+    }
+
+    @Test
+    public void onOpenThenOnBinaryThenAbort() throws Exception {
+        int[] bytes = new int[]{
+                0x82, 0x00, // opcode=binary, fin=true
+                0x88, 0x00, // opcode=close
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                // unbounded request
+                webSocket.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            protected CompletionStage<?> onBinary0(WebSocket webSocket,
+                                                   ByteBuffer message,
+                                                   boolean last) {
+                webSocket.abort();
+                return super.onBinary0(webSocket, message, last);
+            }
+        };
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        TimeUnit.SECONDS.sleep(5);
+        List<MockListener.Invocation> inv = listener.invocationsSoFar();
+        // no more invocations after onOpen, onBinary as WebSocket was aborted
+        List<MockListener.Invocation> expected = List.of(
+                MockListener.Invocation.onOpen(webSocket),
+                MockListener.Invocation.onBinary(webSocket, ByteBuffer.allocate(0), true));
+        assertEquals(inv, expected);
+    }
+
+    @Test
+    public void onOpenThenOnPingThenAbort() throws Exception {
+        int[] bytes = {
+                0x89, 0x00, // opcode=ping
+                0x88, 0x00, // opcode=close
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                // unbounded request
+                webSocket.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            protected CompletionStage<?> onPing0(WebSocket webSocket,
+                                                 ByteBuffer message) {
+                webSocket.abort();
+                return super.onPing0(webSocket, message);
+            }
+        };
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        TimeUnit.SECONDS.sleep(5);
+        List<MockListener.Invocation> inv = listener.invocationsSoFar();
+        // no more invocations after onOpen, onPing as WebSocket was aborted
+        List<MockListener.Invocation> expected = List.of(
+                MockListener.Invocation.onOpen(webSocket),
+                MockListener.Invocation.onPing(webSocket, ByteBuffer.allocate(0)));
+        assertEquals(inv, expected);
+    }
+
+    @Test
+    public void onOpenThenOnPongThenAbort() throws Exception {
+        int[] bytes = {
+                0x8a, 0x00, // opcode=pong
+                0x88, 0x00, // opcode=close
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                // unbounded request
+                webSocket.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            protected CompletionStage<?> onPong0(WebSocket webSocket,
+                                                 ByteBuffer message) {
+                webSocket.abort();
+                return super.onPong0(webSocket, message);
+            }
+        };
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        TimeUnit.SECONDS.sleep(5);
+        List<MockListener.Invocation> inv = listener.invocationsSoFar();
+        // no more invocations after onOpen, onPong as WebSocket was aborted
+        List<MockListener.Invocation> expected = List.of(
+                MockListener.Invocation.onOpen(webSocket),
+                MockListener.Invocation.onPong(webSocket, ByteBuffer.allocate(0)));
+        assertEquals(inv, expected);
+    }
+
+    @Test
+    public void onOpenThenOnCloseThenAbort() throws Exception {
+        int[] bytes = {
+                0x88, 0x00, // opcode=close
+                0x8a, 0x00, // opcode=pong
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                // unbounded request
+                webSocket.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            protected CompletionStage<?> onClose0(WebSocket webSocket,
+                                                  int statusCode,
+                                                  String reason) {
+                webSocket.abort();
+                return super.onClose0(webSocket, statusCode, reason);
+            }
+        };
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        TimeUnit.SECONDS.sleep(5);
+        List<MockListener.Invocation> inv = listener.invocationsSoFar();
+        // no more invocations after onOpen, onClose
+        List<MockListener.Invocation> expected = List.of(
+                MockListener.Invocation.onOpen(webSocket),
+                MockListener.Invocation.onClose(webSocket, 1005, ""));
+        assertEquals(inv, expected);
+    }
+
+    @Test
+    public void onOpenThenOnErrorThenAbort() throws Exception {
+        // A header of 128 bytes long Ping (which is a protocol error)
+        int[] badPingHeader = new int[]{0x89, 0x7e, 0x00, 0x80};
+        int[] closeMessage = new int[]{0x88, 0x00};
+        int[] bytes = new int[badPingHeader.length + 128 + closeMessage.length];
+        System.arraycopy(badPingHeader, 0, bytes, 0, badPingHeader.length);
+        System.arraycopy(closeMessage, 0, bytes, badPingHeader.length + 128, closeMessage.length);
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                // unbounded request
+                webSocket.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            protected void onError0(WebSocket webSocket, Throwable error) {
+                webSocket.abort();
+                super.onError0(webSocket, error);
+            }
+        };
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        TimeUnit.SECONDS.sleep(5);
+        List<MockListener.Invocation> inv = listener.invocationsSoFar();
+        // no more invocations after onOpen, onError
+        List<MockListener.Invocation> expected = List.of(
+                MockListener.Invocation.onOpen(webSocket),
+                MockListener.Invocation.onError(webSocket, ProtocolException.class));
+        System.out.println("actual invocations:" + Arrays.toString(inv.toArray()));
+        assertEquals(inv, expected);
+    }
+
+    @Test
+    public void immediateAbort() throws Exception {
+        CompletableFuture<Void> messageReceived = new CompletableFuture<>();
+        WebSocket.Listener listener = new WebSocket.Listener() {
+
+            @Override
+            public void onOpen(WebSocket webSocket) {
+                /* no initial request */
+            }
+
+            @Override
+            public CompletionStage<?> onText(WebSocket webSocket,
+                                             CharSequence message,
+                                             boolean last) {
+                messageReceived.complete(null);
+                return null;
+            }
+
+            @Override
+            public CompletionStage<?> onBinary(WebSocket webSocket,
+                                               ByteBuffer message,
+                                               boolean last) {
+                messageReceived.complete(null);
+                return null;
+            }
+
+            @Override
+            public CompletionStage<?> onPing(WebSocket webSocket,
+                                             ByteBuffer message) {
+                messageReceived.complete(null);
+                return null;
+            }
+
+            @Override
+            public CompletionStage<?> onPong(WebSocket webSocket,
+                                             ByteBuffer message) {
+                messageReceived.complete(null);
+                return null;
+            }
+
+            @Override
+            public CompletionStage<?> onClose(WebSocket webSocket,
+                                              int statusCode,
+                                              String reason) {
+                messageReceived.complete(null);
+                return null;
+            }
+        };
+
+        int[] bytes = new int[]{
+                0x82, 0x00, // opcode=binary, fin=true
+                0x88, 0x00, // opcode=close
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+
+        WebSocket ws = newHttpClient()
+                .newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        for (int i = 0; i < 3; i++) {
+            System.out.printf("iteration #%s%n", i);
+            // after the first abort() each consecutive one must be a no-op,
+            // moreover, query methods should continue to return consistent
+            // values
+            for (int j = 0; j < 3; j++) {
+                System.out.printf("abort #%s%n", j);
+                ws.abort();
+                assertTrue(ws.isInputClosed());
+                assertTrue(ws.isOutputClosed());
+                assertEquals(ws.getSubprotocol(), "");
+            }
+            // at this point valid requests MUST be a no-op:
+            for (int j = 0; j < 3; j++) {
+                System.out.printf("request #%s%n", j);
+                ws.request(1);
+                ws.request(2);
+                ws.request(8);
+                ws.request(Integer.MAX_VALUE);
+                ws.request(Long.MAX_VALUE);
+                // invalid requests MUST throw IAE:
+                assertThrows(IAE, () -> ws.request(Integer.MIN_VALUE));
+                assertThrows(IAE, () -> ws.request(Long.MIN_VALUE));
+                assertThrows(IAE, () -> ws.request(-1));
+                assertThrows(IAE, () -> ws.request(0));
+            }
+        }
+        // even though there is a bunch of messages readily available on the
+        // wire we shouldn't have received any of them as we aborted before
+        // the first request
+        try {
+            messageReceived.get(5, TimeUnit.SECONDS);
+            fail();
+        } catch (TimeoutException expected) {
+            System.out.println("Finished waiting");
+        }
+        for (int i = 0; i < 3; i++) {
+            System.out.printf("send #%s%n", i);
+            Support.assertFails(IOE, ws.sendText("text!", false));
+            Support.assertFails(IOE, ws.sendText("text!", true));
+            Support.assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), false));
+            Support.assertFails(IOE, ws.sendBinary(ByteBuffer.allocate(16), true));
+            Support.assertFails(IOE, ws.sendPing(ByteBuffer.allocate(16)));
+            Support.assertFails(IOE, ws.sendPong(ByteBuffer.allocate(16)));
+            Support.assertFails(IOE, ws.sendClose(NORMAL_CLOSURE, "a reason"));
+            assertThrows(NPE, () -> ws.sendText(null, false));
+            assertThrows(NPE, () -> ws.sendText(null, true));
+            assertThrows(NPE, () -> ws.sendBinary(null, false));
+            assertThrows(NPE, () -> ws.sendBinary(null, true));
+            assertThrows(NPE, () -> ws.sendPing(null));
+            assertThrows(NPE, () -> ws.sendPong(null));
+            assertThrows(NPE, () -> ws.sendClose(NORMAL_CLOSURE, null));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/AutomaticPong.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       AutomaticPong
+ */
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static java.net.http.HttpClient.newHttpClient;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class AutomaticPong {
+
+    private DummyWebSocketServer server;
+    private WebSocket webSocket;
+
+    @AfterTest
+    public void cleanup() {
+        server.close();
+        webSocket.abort();
+    }
+
+    /*
+     * The sendClose method has been invoked and a Ping comes from the server.
+     * Naturally, the client cannot reply with a Pong (the output has been
+     * closed). However, this MUST not be treated as an error.
+     * At this stage the server either has received or pretty soon will receive
+     * the Close message sent by the sendClose. Thus, the server will know the
+     * client cannot send further messages and it's up to the server to decide
+     * how to react on the corresponding Pong not being received.
+     */
+    @Test
+    public void sendCloseThenAutomaticPong() throws IOException {
+        int[] bytes = {
+                0x89, 0x00,                                     // ping
+                0x89, 0x06, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3f, // ping hello?
+                0x88, 0x00,                                     // close
+        };
+        server = Support.serverWithCannedData(bytes);
+        server.open();
+        MockListener listener = new MockListener() {
+            @Override
+            protected void onOpen0(WebSocket webSocket) {
+                /* request nothing */
+            }
+        };
+        webSocket = newHttpClient()
+                .newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+
+        webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok").join();
+        // now request all messages available
+        webSocket.request(Long.MAX_VALUE);
+        List<MockListener.Invocation> actual = listener.invocations();
+        ByteBuffer hello = ByteBuffer.wrap("hello?".getBytes(StandardCharsets.UTF_8));
+        ByteBuffer empty = ByteBuffer.allocate(0);
+        List<MockListener.Invocation> expected = List.of(
+                MockListener.Invocation.onOpen(webSocket),
+                MockListener.Invocation.onPing(webSocket, empty),
+                MockListener.Invocation.onPing(webSocket, hello),
+                MockListener.Invocation.onClose(webSocket, 1005, "")
+        );
+        assertEquals(actual, expected);
+    }
+
+    /*
+     * The server sends a number of contiguous Ping messages. The client replies
+     * to these messages automatically. According to RFC 6455 a WebSocket client
+     * is free to reply only to the most recent Pings.
+     *
+     * What is checked here is that:
+     *
+     *     a) the order of Pong replies corresponds to the Pings received,
+     *     b) the last Pong corresponds to the last Ping
+     *     c) there are no unrelated Pongs
+     */
+    @Test(dataProvider = "nPings")
+    public void automaticPongs(int nPings) throws Exception {
+        // big enough to not bother with resize
+        ByteBuffer buffer = ByteBuffer.allocate(65536);
+        Frame.HeaderWriter w = new Frame.HeaderWriter();
+        for (int i = 0; i < nPings; i++) {
+            w.fin(true)
+             .opcode(Frame.Opcode.PING)
+             .noMask()
+             .payloadLen(4)    // the length of the number of the Ping (int)
+             .write(buffer);
+            buffer.putInt(i);  // the number of the Ping (int)
+        }
+        w.fin(true)
+         .opcode(Frame.Opcode.CLOSE)
+         .noMask()
+         .payloadLen(2)
+        .write(buffer);
+        buffer.putChar((char) 1000);
+        buffer.flip();
+        server = Support.serverWithCannedData(buffer.array());
+        server.open();
+        MockListener listener = new MockListener();
+        webSocket = newHttpClient()
+                .newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+        List<MockListener.Invocation> inv = listener.invocations();
+        assertEquals(inv.size(), nPings + 2); // n * onPing + onOpen + onClose
+
+        ByteBuffer data = server.read();
+        Frame.Reader reader = new Frame.Reader();
+
+        Frame.Consumer consumer = new Frame.Consumer() {
+
+            ByteBuffer number = ByteBuffer.allocate(4);
+            Frame.Masker masker = new Frame.Masker();
+            int i = -1;
+            boolean closed;
+
+            @Override
+            public void fin(boolean value) { assertTrue(value); }
+
+            @Override
+            public void rsv1(boolean value) { assertFalse(value); }
+
+            @Override
+            public void rsv2(boolean value) { assertFalse(value); }
+
+            @Override
+            public void rsv3(boolean value) { assertFalse(value); }
+
+            @Override
+            public void opcode(Frame.Opcode value) {
+                if (value == Frame.Opcode.CLOSE) {
+                    closed = true;
+                    return;
+                }
+                assertEquals(value, Frame.Opcode.PONG);
+            }
+
+            @Override
+            public void mask(boolean value) { assertTrue(value); }
+
+            @Override
+            public void payloadLen(long value) {
+                if (!closed)
+                    assertEquals(value, 4);
+            }
+
+            @Override
+            public void maskingKey(int value) {
+                masker.mask(value);
+            }
+
+            @Override
+            public void payloadData(ByteBuffer src) {
+                masker.transferMasking(src, number);
+                if (closed) {
+                    return;
+                }
+                number.flip();
+                int n = number.getInt();
+                System.out.printf("pong number=%s%n", n);
+                number.clear();
+                // a Pong with the number less than the maximum of Pongs already
+                // received MUST never be received
+                if (i >= n) {
+                    fail(String.format("i=%s, n=%s", i, n));
+                }
+                i = n;
+            }
+
+            @Override
+            public void endFrame() { }
+        };
+        while (data.hasRemaining()) {
+            reader.readFrame(data, consumer);
+        }
+    }
+
+
+    @DataProvider(name = "nPings")
+    public Object[][] nPings() {
+        return new Object[][]{{1}, {2}, {4}, {8}, {9}, {256}};
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/BlowupOutputQueue.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       BlowupOutputQueue
+ */
+
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+import static org.testng.Assert.assertFalse;
+
+public class BlowupOutputQueue extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfText;
+    CompletableFuture<WebSocket> cfPing;
+    CompletableFuture<WebSocket> cfClose;
+    MockListener listener;
+
+    /*
+     * The idea is to arrange things such that the internal queue will be fully
+     * utilized and then make sure there won't be any errors caused by that.
+     *
+     * First, fill the queue with Text messages. Once done, send a Ping message.
+     * At this stage, there are at least 2 messages are in queue. Now, start
+     * receiving. Received Ping messages will cause automatic Pong replies. When
+     * all done, there will be at least 3 messages in the queue. (As at least
+     * the a single Ping has to be replied). Then send a Close message. Now
+     * there are at least 4 messages in the queue. Finally, receive the last
+     * message which is a Close message. This will cause an automatic reply with
+     * a Close message from the client side. All in all there should be at least
+     * 5 messages in the queue.
+     */
+    @Test
+    public void full() throws Exception {
+        int N = 32;
+        int[] incoming = new int[2 * (N + 1)]; // 2 bytes per message
+        for (int i = 0; i < incoming.length - 2; i += 2) {
+            // <PING>
+            incoming[i + 0] = 0x89;
+            incoming[i + 1] = 0x00;
+        }
+        // <CLOSE>
+        incoming[incoming.length - 2] = 0x88;
+        incoming[incoming.length - 1] = 0x00;
+
+        repeatable(() -> {
+            CountDownLatch allButCloseReceived = new CountDownLatch(N);
+            server = Support.writingServer(incoming);
+            server.open();
+            listener = new MockListener() {
+
+                @Override
+                protected void replenish(WebSocket webSocket) {
+                    /* do nothing */
+                }
+
+                @Override
+                protected CompletionStage<?> onPing0(WebSocket webSocket,
+                                                     ByteBuffer message) {
+                    allButCloseReceived.countDown();
+                    return null;
+                }
+            };
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), listener)
+                    .join();
+            CharBuffer data = CharBuffer.allocate(65536);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfText = webSocket.sendText(data, true);
+                try {
+                    cfText.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            cfPing = webSocket.sendPing(ByteBuffer.allocate(125));
+            webSocket.request(N);
+            allButCloseReceived.await();
+            webSocket.request(1); // Receive the last message: Close
+            return null;
+        }, () -> cfText.isDone());
+        List<MockListener.Invocation> invocations = listener.invocations();
+        cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+
+        assertFalse(invocations.contains(new MockListener.OnError(webSocket, IOException.class)));
+        assertFalse(cfText.isDone());
+        assertFalse(cfPing.isDone());
+        assertFalse(cfClose.isDone());
+    }
+}
--- a/test/jdk/java/net/httpclient/websocket/BuildingWebSocketDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +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.
- */
-
-/*
- * @test
- * @bug 8159053
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.websocket:open
- * @run testng/othervm --add-reads jdk.incubator.httpclient=ALL-UNNAMED jdk.incubator.httpclient/jdk.incubator.http.internal.websocket.BuildingWebSocketTest
- */
-public final class BuildingWebSocketDriver { }
--- a/test/jdk/java/net/httpclient/websocket/ConnectionHandover.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +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.
- */
-
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.WebSocket;
-
-import java.io.IOException;
-import java.net.URI;
-
-/*
- * @test
- * @bug 8164625
- * @summary Verifies HttpClient yields the connection to the WebSocket
- * @build DummyWebSocketServer
- * @run main/othervm -Djdk.httpclient.HttpClient.log=trace ConnectionHandover
- */
-public class ConnectionHandover {
-    /*
-     * An I/O channel associated with the connection is closed by WebSocket.abort().
-     * If this connection is returned to the connection pool, then the second
-     * attempt to use it would fail with a ClosedChannelException.
-     *
-     * The assumption is that since the WebSocket client is connecting to the
-     * same URI, the pooled connection is to be used.
-     */
-    public static void main(String[] args) throws IOException {
-        try (DummyWebSocketServer server = new DummyWebSocketServer()) {
-            server.open();
-            URI uri = server.getURI();
-            WebSocket.Builder webSocketBuilder =
-                    HttpClient.newHttpClient().newWebSocketBuilder();
-
-            WebSocket ws1 = webSocketBuilder
-                    .buildAsync(uri, new WebSocket.Listener() { }).join();
-            ws1.abort();
-
-            WebSocket ws2 = webSocketBuilder
-                    .buildAsync(uri, new WebSocket.Listener() { }).join(); // Exception here if the connection was pooled
-            ws2.abort();
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/ConnectionHandoverTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8164625
+ * @summary Verifies HttpClient yields the connection to the WebSocket
+ * @build DummyWebSocketServer
+ * @run main/othervm -Djdk.httpclient.HttpClient.log=trace ConnectionHandoverTest
+ */
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+
+public class ConnectionHandoverTest {
+    /*
+     * An I/O channel associated with the connection is closed by WebSocket.abort().
+     * If this connection is returned to the connection pool, then the second
+     * attempt to use it would fail with a ClosedChannelException.
+     *
+     * The assumption is that since the WebSocket client is connecting to the
+     * same URI, the pooled connection is to be used.
+     */
+    public static void main(String[] args) throws IOException {
+        try (DummyWebSocketServer server = new DummyWebSocketServer()) {
+            server.open();
+            URI uri = server.getURI();
+            WebSocket.Builder webSocketBuilder =
+                    HttpClient.newHttpClient().newWebSocketBuilder();
+
+            WebSocket ws1 = webSocketBuilder
+                    .buildAsync(uri, new WebSocket.Listener() { }).join();
+            ws1.abort();
+
+            WebSocket ws2 = webSocketBuilder
+                    .buildAsync(uri, new WebSocket.Listener() { }).join(); // Exception here if the connection was pooled
+            ws2.abort();
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.UncheckedIOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.StandardSocketOptions;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
@@ -42,6 +44,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import java.util.regex.Pattern;
@@ -80,12 +83,14 @@
  *     Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  *     Sec-WebSocket-Protocol: chat
  */
-public final class DummyWebSocketServer implements Closeable {
+public class DummyWebSocketServer implements Closeable {
 
     private final AtomicBoolean started = new AtomicBoolean();
     private final Thread thread;
     private volatile ServerSocketChannel ssc;
     private volatile InetSocketAddress address;
+    private ByteBuffer read = ByteBuffer.allocate(16384);
+    private final CountDownLatch readReady = new CountDownLatch(1);
 
     public DummyWebSocketServer() {
         this(defaultMapping());
@@ -100,6 +105,7 @@
                     SocketChannel channel = ssc.accept();
                     err.println("Accepted: " + channel);
                     try {
+                        channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
                         channel.configureBlocking(true);
                         StringBuilder request = new StringBuilder();
                         if (!readRequest(channel, request)) {
@@ -108,21 +114,17 @@
                         List<String> strings = asList(request.toString().split("\r\n"));
                         List<String> response = mapping.apply(strings);
                         writeResponse(channel, response);
-                        // Read until the thread is interrupted or an error occurred
-                        // or the input is shutdown
-                        ByteBuffer b = ByteBuffer.allocate(1024);
-                        while (channel.read(b) != -1) {
-                            b.clear();
-                        }
+                        serve(channel);
                     } catch (IOException e) {
                         err.println("Error in connection: " + channel + ", " + e);
                     } finally {
                         err.println("Closed: " + channel);
                         close(channel);
+                        readReady.countDown();
                     }
                 }
             } catch (ClosedByInterruptException ignored) {
-            } catch (IOException e) {
+            } catch (Exception e) {
                 err.println(e);
             } finally {
                 close(ssc);
@@ -133,6 +135,58 @@
         thread.setDaemon(false);
     }
 
+    protected void read(SocketChannel ch) throws IOException {
+        // Read until the thread is interrupted or an error occurred
+        // or the input is shutdown
+        ByteBuffer b = ByteBuffer.allocate(65536);
+        while (ch.read(b) != -1) {
+            b.flip();
+            if (read.remaining() < b.remaining()) {
+                int required = read.capacity() - read.remaining() + b.remaining();
+                int log2required = 32 - Integer.numberOfLeadingZeros(required - 1);
+                ByteBuffer newBuffer = ByteBuffer.allocate(1 << log2required);
+                newBuffer.put(read.flip());
+                read = newBuffer;
+            }
+            read.put(b);
+            b.clear();
+        }
+    }
+
+    protected void write(SocketChannel ch) throws IOException { }
+
+    protected final void serve(SocketChannel channel)
+            throws InterruptedException
+    {
+        Thread reader = new Thread(() -> {
+            try {
+                read(channel);
+            } catch (IOException ignored) { }
+        });
+        Thread writer = new Thread(() -> {
+            try {
+                write(channel);
+            } catch (IOException ignored) { }
+        });
+        reader.start();
+        writer.start();
+        try {
+            reader.join();
+        } finally {
+            reader.interrupt();
+            try {
+                writer.join();
+            } finally {
+                writer.interrupt();
+            }
+        }
+    }
+
+    public ByteBuffer read() throws InterruptedException {
+        readReady.await();
+        return read.duplicate().asReadOnlyBuffer().flip();
+    }
+
     public void open() throws IOException {
         err.println("Starting");
         if (!started.compareAndSet(false, true)) {
@@ -141,7 +195,7 @@
         ssc = ServerSocketChannel.open();
         try {
             ssc.configureBlocking(true);
-            ssc.bind(new InetSocketAddress("localhost", 0));
+            ssc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             address = (InetSocketAddress) ssc.getLocalAddress();
             thread.start();
         } catch (IOException e) {
@@ -161,7 +215,7 @@
         if (!started.get()) {
             throw new IllegalStateException("Not yet started");
         }
-        return URI.create("ws://" + address.getHostName() + ":" + address.getPort());
+        return URI.create("ws://localhost:" + address.getPort());
     }
 
     private boolean readRequest(SocketChannel channel, StringBuilder request)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/Frame.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,497 @@
+/*
+ * 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.
+ */
+
+import java.nio.ByteBuffer;
+
+/* Copied from jdk.internal.net.http.websocket.Frame */
+final class Frame {
+
+    final Opcode opcode;
+    final ByteBuffer data;
+    final boolean last;
+
+    public Frame(Opcode opcode, ByteBuffer data, boolean last) {
+        this.opcode = opcode;
+        /* copy */
+        this.data = ByteBuffer.allocate(data.remaining()).put(data.slice()).flip();
+        this.last = last;
+    }
+
+    static final int MAX_HEADER_SIZE_BYTES = 2 + 8 + 4;
+    static final int MAX_CONTROL_FRAME_PAYLOAD_SIZE = 125;
+
+    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);
+
+        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();
+            }
+            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(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 IllegalArgumentException negativePayload(long payloadLength)
+        {
+            return new IllegalArgumentException("Negative payload length: "
+                                                        + payloadLength);
+        }
+
+        private static IllegalArgumentException notMinimalEncoding(long payloadLength)
+        {
+            return new IllegalArgumentException("Not minimally-encoded payload length:"
+                                                      + payloadLength);
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/websocket/HeaderWriterDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/HeaderWriterDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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 jdk.incubator.httpclient/jdk.incubator.http.internal.websocket:open
- * @run testng/othervm --add-reads jdk.incubator.httpclient=ALL-UNNAMED jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/MaskerDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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 jdk.incubator.httpclient/jdk.incubator.http.internal.websocket:open
- * @run testng/othervm --add-reads jdk.incubator.httpclient=ALL-UNNAMED jdk.incubator.httpclient/jdk.incubator.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 { }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/MessageQueueDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8159053
+ * @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.MessageQueueTest
+ */
+public final class MessageQueueDriver { }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/MockListener.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,479 @@
+/*
+ * 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.
+ */
+
+import java.net.http.WebSocket;
+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 java.util.function.Predicate;
+
+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<>();
+    private final Predicate<? super Invocation> collectUntil;
+
+    public MockListener() {
+        this(i -> i instanceof OnClose || i instanceof OnError);
+    }
+
+    public MockListener(Predicate<? super Invocation> collectUntil) {
+        this(2, collectUntil);
+    }
+
+    /*
+     * Typical buffer sizes: 1, n, Long.MAX_VALUE
+     */
+    public MockListener(long bufferSize,
+                        Predicate<? super Invocation> collectUntil) {
+        if (bufferSize < 1) {
+            throw new IllegalArgumentException();
+        }
+        Objects.requireNonNull(collectUntil);
+        this.bufferSize = bufferSize;
+        this.collectUntil = collectUntil;
+    }
+
+    @Override
+    public void onOpen(WebSocket webSocket) {
+        System.out.printf("onOpen(%s)%n", webSocket);
+        OnOpen inv = new OnOpen(webSocket);
+        synchronized (invocations) {
+            invocations.add(inv);
+        }
+        if (collectUntil.test(inv)) {
+            lastCall.complete(null);
+        }
+        onOpen0(webSocket);
+    }
+
+    protected void onOpen0(WebSocket webSocket) {
+        replenish(webSocket);
+    }
+
+    @Override
+    public CompletionStage<?> onText(WebSocket webSocket,
+                                     CharSequence message,
+                                     boolean last) {
+        System.out.printf("onText(%s, message.length=%s, %s)%n", webSocket, message.length(), last);
+        OnText inv = new OnText(webSocket, message.toString(), last);
+        synchronized (invocations) {
+            invocations.add(inv);
+        }
+        if (collectUntil.test(inv)) {
+            lastCall.complete(null);
+        }
+        return onText0(webSocket, message, last);
+    }
+
+    protected CompletionStage<?> onText0(WebSocket webSocket,
+                                         CharSequence message,
+                                         boolean last) {
+        replenish(webSocket);
+        return null;
+    }
+
+    @Override
+    public CompletionStage<?> onBinary(WebSocket webSocket,
+                                       ByteBuffer message,
+                                       boolean last) {
+        System.out.printf("onBinary(%s, %s, %s)%n", webSocket, message, last);
+        OnBinary inv = new OnBinary(webSocket, fullCopy(message), last);
+        synchronized (invocations) {
+            invocations.add(inv);
+        }
+        if (collectUntil.test(inv)) {
+            lastCall.complete(null);
+        }
+        return onBinary0(webSocket, message, last);
+    }
+
+    protected CompletionStage<?> onBinary0(WebSocket webSocket,
+                                           ByteBuffer message,
+                                           boolean last) {
+        replenish(webSocket);
+        return null;
+    }
+
+    @Override
+    public CompletionStage<?> onPing(WebSocket webSocket, ByteBuffer message) {
+        System.out.printf("onPing(%s, %s)%n", webSocket, message);
+        OnPing inv = new OnPing(webSocket, fullCopy(message));
+        synchronized (invocations) {
+            invocations.add(inv);
+        }
+        if (collectUntil.test(inv)) {
+            lastCall.complete(null);
+        }
+        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);
+        OnPong inv = new OnPong(webSocket, fullCopy(message));
+        synchronized (invocations) {
+            invocations.add(inv);
+        }
+        if (collectUntil.test(inv)) {
+            lastCall.complete(null);
+        }
+        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);
+        OnClose inv = new OnClose(webSocket, statusCode, reason);
+        synchronized (invocations) {
+            invocations.add(inv);
+        }
+        if (collectUntil.test(inv)) {
+            lastCall.complete(null);
+        }
+        return onClose0(webSocket, statusCode, reason);
+    }
+
+    protected CompletionStage<?> onClose0(WebSocket webSocket,
+                                          int statusCode,
+                                          String reason) {
+        return null;
+    }
+
+    @Override
+    public void onError(WebSocket webSocket, Throwable error) {
+        System.out.printf("onError(%s, %s)%n", webSocket, error);
+        error.printStackTrace(System.out);
+        OnError inv = new OnError(webSocket, error == null ? null : error.getClass());
+        synchronized (invocations) {
+            invocations.add(inv);
+        }
+        if (collectUntil.test(inv)) {
+            lastCall.complete(null);
+        }
+        onError0(webSocket, error);
+    }
+
+    protected void onError0(WebSocket webSocket, Throwable error) { }
+
+    public List<Invocation> invocationsSoFar() {
+        synchronized (invocations) {
+            return new ArrayList<>(invocations);
+        }
+    }
+
+    public List<Invocation> invocations() {
+        lastCall.join();
+        synchronized (invocations) {
+            return new ArrayList<>(invocations);
+        }
+    }
+
+    protected void replenish(WebSocket webSocket) {
+        if (--count <= 0) {
+            count = bufferSize - bufferSize / 2;
+        }
+        webSocket.request(count);
+    }
+
+    public abstract static class Invocation {
+
+        public static OnOpen onOpen(WebSocket webSocket) {
+            return new OnOpen(webSocket);
+        }
+
+        public static OnText onText(WebSocket webSocket,
+                                    String text,
+                                    boolean last) {
+            return new OnText(webSocket, text, last);
+        }
+
+        public static OnBinary onBinary(WebSocket webSocket,
+                                        ByteBuffer data,
+                                        boolean last) {
+            return new OnBinary(webSocket, data, last);
+        }
+
+        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);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onOpen(%s)", webSocket);
+        }
+    }
+
+    public static final class OnText extends Invocation {
+
+        final String text;
+        final boolean last;
+
+        public OnText(WebSocket webSocket, String text, boolean last) {
+            super(webSocket);
+            this.text = text;
+            this.last = last;
+        }
+
+        @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) &&
+                    last == onText.last &&
+                    Objects.equals(webSocket, onText.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(text, last, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onText(%s, message.length=%s, %s)", webSocket, text.length(), last);
+        }
+    }
+
+    public static final class OnBinary extends Invocation {
+
+        final ByteBuffer data;
+        final boolean last;
+
+        public OnBinary(WebSocket webSocket, ByteBuffer data, boolean last) {
+            super(webSocket);
+            this.data = data;
+            this.last = last;
+        }
+
+        @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) &&
+                    last == onBinary.last &&
+                    Objects.equals(webSocket, onBinary.webSocket);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(data, last, webSocket);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("onBinary(%s, %s, %s)", webSocket, data, last);
+        }
+    }
+
+    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);
+        }
+    }
+
+    private 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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingBinaryPingClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       PendingBinaryPingClose
+ */
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingBinaryPingClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfBinary;
+    CompletableFuture<WebSocket> cfPing;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingBinaryPingClose(boolean last) throws Exception {
+        repeatable(() -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ByteBuffer data = ByteBuffer.allocate(65536);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfBinary = webSocket.sendBinary(data, last);
+                try {
+                    cfBinary.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendText("", true));
+            assertFails(ISE, webSocket.sendText("", false));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
+            cfPing = webSocket.sendPing(ByteBuffer.allocate(125));
+            assertHangs(cfPing);
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return null;
+        }, () -> cfBinary.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfBinary);
+        assertFails(IOE, cfPing);
+        assertFails(IOE, cfClose);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingBinaryPongClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       PendingBinaryPongClose
+ */
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingBinaryPongClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfBinary;
+    CompletableFuture<WebSocket> cfPong;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingBinaryPongClose(boolean last) throws Exception {
+        repeatable(() -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ByteBuffer data = ByteBuffer.allocate(65536);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfBinary = webSocket.sendBinary(data, last);
+                try {
+                    cfBinary.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendText("", true));
+            assertFails(ISE, webSocket.sendText("", false));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
+            cfPong = webSocket.sendPong(ByteBuffer.allocate(125));
+            assertHangs(cfPong);
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return null;
+        }, () -> cfBinary.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfBinary);
+        assertFails(IOE, cfPong);
+        assertFails(IOE, cfClose);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingOperations.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.DataProvider;
+
+import java.io.IOException;
+import java.net.http.WebSocket;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionStage;
+import java.util.function.BooleanSupplier;
+
+/* Common infrastructure for tests that check pending operations */
+public class PendingOperations {
+
+    static final Class<IllegalStateException> ISE = IllegalStateException.class;
+    static final Class<IOException> IOE = IOException.class;
+    // Time after which we deem that the local send buffer and remote
+    // receive buffer must be full. This has been heuristically determined.
+    // At the time of writing, using anything <= 5s on Mac will make the
+    // tests fail intermittently.
+    static final long MAX_WAIT_SEC = 10; // seconds.
+
+    DummyWebSocketServer server;
+    WebSocket webSocket;
+
+    @AfterTest
+    public void cleanup() {
+        server.close();
+        webSocket.abort();
+    }
+
+    /* shortcut */
+    static void assertHangs(CompletionStage<?> stage) {
+        Support.assertHangs(stage);
+    }
+
+    /* shortcut */
+    static void assertFails(Class<? extends Throwable> clazz,
+                            CompletionStage<?> stage) {
+        Support.assertCompletesExceptionally(clazz, stage);
+    }
+
+    @DataProvider(name = "booleans")
+    public Object[][] booleans() {
+        return new Object[][]{{Boolean.TRUE}, {Boolean.FALSE}};
+    }
+
+    static boolean isMacOS() {
+        return System.getProperty("os.name").contains("OS X");
+    }
+
+    private static final int ITERATIONS = 3;
+
+    static void repeatable(Callable<Void> callable,
+                           BooleanSupplier repeatCondition)
+        throws Exception
+    {
+        int iterations = 0;
+        do {
+            iterations++;
+            System.out.println("--- iteration " + iterations + " ---");
+            try {
+                callable.call();
+                break;
+            } catch (AssertionError e) {
+                if (isMacOS() && repeatCondition.getAsBoolean()) {
+                    // ## This is loathsome, but necessary because of observed
+                    // ## automagic socket buffer resizing on recent macOS platforms
+                    continue;
+                } else {
+                    throw e;
+                }
+            }
+        } while (iterations <= ITERATIONS);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingPingBinaryClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *       PendingPingBinaryClose
+ */
+
+// This test produce huge logs (14Mb+) so disable logging by default
+// *      -Djdk.internal.httpclient.debug=true
+// *      -Djdk.internal.httpclient.websocket.debug=true
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingPingBinaryClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfBinary;
+    CompletableFuture<WebSocket> cfPing;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingPingBinaryClose(boolean last) throws Exception {
+        repeatable( () -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ByteBuffer data = ByteBuffer.allocate(125);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfPing = webSocket.sendPing(data);
+                try {
+                    cfPing.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfBinary = webSocket.sendBinary(ByteBuffer.allocate(4), last);
+            assertHangs(cfBinary);
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return null;
+        }, () -> cfPing.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfPing);
+        assertFails(IOE, cfBinary);
+        assertFails(IOE, cfClose);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingPingTextClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *       PendingPingTextClose
+ */
+
+// This test produce huge logs (14Mb+) so disable logging by default
+// *      -Djdk.internal.httpclient.debug=true
+// *      -Djdk.internal.httpclient.websocket.debug=true
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingPingTextClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfText;
+    CompletableFuture<WebSocket> cfPing;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingPingTextClose(boolean last) throws Exception {
+        repeatable( () -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ByteBuffer data = ByteBuffer.allocate(125);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfPing = webSocket.sendPing(data);
+                try {
+                    cfPing.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfText = webSocket.sendText("hello", last);
+            assertHangs(cfText);
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return null;
+        }, () -> cfPing.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfPing);
+        assertFails(IOE, cfText);
+        assertFails(IOE, cfClose);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingPongBinaryClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *       PendingPongBinaryClose
+ */
+
+// This test produce huge logs (14Mb+) so disable logging by default
+// *      -Djdk.internal.httpclient.debug=true
+// *      -Djdk.internal.httpclient.websocket.debug=true
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingPongBinaryClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfBinary;
+    CompletableFuture<WebSocket> cfPong;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingPongBinaryClose(boolean last) throws Exception {
+        repeatable( () -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ByteBuffer data = ByteBuffer.allocate(125);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfPong = webSocket.sendPong(data);
+                try {
+                    cfPong.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfBinary = webSocket.sendBinary(ByteBuffer.allocate(4), last);
+            assertHangs(cfBinary);
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return null;
+        }, () -> cfPong.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfPong);
+        assertFails(IOE, cfBinary);
+        assertFails(IOE, cfClose);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingPongTextClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *       PendingPongTextClose
+ */
+
+// This test produce huge logs (14Mb+) so disable logging by default
+// *      -Djdk.internal.httpclient.debug=true
+// *      -Djdk.internal.httpclient.websocket.debug=true
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingPongTextClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfText;
+    CompletableFuture<WebSocket> cfPong;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingPongTextClose(boolean last) throws Exception {
+        repeatable( () -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ByteBuffer data = ByteBuffer.allocate(125);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfPong = webSocket.sendPong(data);
+                try {
+                    cfPong.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfText = webSocket.sendText("hello", last);
+            assertHangs(cfText);
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return  null;
+        }, () -> cfPong.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfPong);
+        assertFails(IOE, cfText);
+        assertFails(IOE, cfClose);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingTextPingClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       PendingTextPingClose
+ */
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingTextPingClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfText;
+    CompletableFuture<WebSocket> cfPing;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingTextPingClose(boolean last) throws Exception {
+        repeatable(() -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            CharBuffer data = CharBuffer.allocate(65536);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfText = webSocket.sendText(data, last);
+                try {
+                    cfText.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendText("", true));
+            assertFails(ISE, webSocket.sendText("", false));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
+            cfPing = webSocket.sendPing(ByteBuffer.allocate(125));
+            assertHangs(cfPing);
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return null;
+        }, () -> cfText.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfText);
+        assertFails(IOE, cfPing);
+        assertFails(IOE, cfClose);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/PendingTextPongClose.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       PendingTextPongClose
+ */
+
+import org.testng.annotations.Test;
+
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static java.net.http.HttpClient.newHttpClient;
+
+public class PendingTextPongClose extends PendingOperations {
+
+    CompletableFuture<WebSocket> cfText;
+    CompletableFuture<WebSocket> cfPong;
+    CompletableFuture<WebSocket> cfClose;
+
+    @Test(dataProvider = "booleans")
+    public void pendingTextPongClose(boolean last) throws Exception {
+        repeatable(() -> {
+            server = Support.notReadingServer();
+            server.open();
+            webSocket = newHttpClient().newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            CharBuffer data = CharBuffer.allocate(65536);
+            for (int i = 0; ; i++) {  // fill up the send buffer
+                long start = System.currentTimeMillis();
+                System.out.printf("begin cycle #%s at %s%n", i, start);
+                cfText = webSocket.sendText(data, last);
+                try {
+                    cfText.get(MAX_WAIT_SEC, TimeUnit.SECONDS);
+                    data.clear();
+                } catch (TimeoutException e) {
+                    break;
+                } finally {
+                    long stop = System.currentTimeMillis();
+                    System.out.printf("end cycle #%s at %s (%s ms)%n", i, stop, stop - start);
+                }
+            }
+            assertFails(ISE, webSocket.sendText("", true));
+            assertFails(ISE, webSocket.sendText("", false));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
+            assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
+            cfPong = webSocket.sendPong(ByteBuffer.allocate(125));
+            assertHangs(cfPong);
+            assertFails(ISE, webSocket.sendPing(ByteBuffer.allocate(125)));
+            assertFails(ISE, webSocket.sendPong(ByteBuffer.allocate(125)));
+            cfClose = webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok");
+            assertHangs(cfClose);
+            return null;
+        }, () -> cfText.isDone() ? true : false);
+        webSocket.abort();
+        assertFails(IOE, cfText);
+        assertFails(IOE, cfPong);
+        assertFails(IOE, cfClose);
+    }
+}
--- a/test/jdk/java/net/httpclient/websocket/ReaderDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/ReaderDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8159053
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.websocket:open
- * @run testng/othervm --add-reads jdk.incubator.httpclient=ALL-UNNAMED jdk.incubator.httpclient/jdk.incubator.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/ReceivingTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +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.
- */
-
-/*
- * @test
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.websocket:open
- * @run testng/othervm --add-reads jdk.incubator.httpclient=ALL-UNNAMED jdk.incubator.httpclient/jdk.incubator.http.internal.websocket.ReceivingTest
- */
-public class ReceivingTestDriver { }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/SendTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       SendTest
+ */
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.http.WebSocket;
+
+import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.WebSocket.NORMAL_CLOSURE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+
+public class SendTest {
+
+    private static final Class<NullPointerException> NPE = NullPointerException.class;
+
+    private DummyWebSocketServer server;
+    private WebSocket webSocket;
+
+    @AfterTest
+    public void cleanup() {
+        server.close();
+        webSocket.abort();
+    }
+
+    @Test
+    public void sendMethodsThrowNPE() throws IOException {
+        server = new DummyWebSocketServer();
+        server.open();
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                .join();
+
+        assertThrows(NPE, () -> webSocket.sendText(null, false));
+        assertThrows(NPE, () -> webSocket.sendText(null, true));
+        assertThrows(NPE, () -> webSocket.sendBinary(null, false));
+        assertThrows(NPE, () -> webSocket.sendBinary(null, true));
+        assertThrows(NPE, () -> webSocket.sendPing(null));
+        assertThrows(NPE, () -> webSocket.sendPong(null));
+        assertThrows(NPE, () -> webSocket.sendClose(NORMAL_CLOSURE, null));
+
+        webSocket.abort();
+
+        assertThrows(NPE, () -> webSocket.sendText(null, false));
+        assertThrows(NPE, () -> webSocket.sendText(null, true));
+        assertThrows(NPE, () -> webSocket.sendBinary(null, false));
+        assertThrows(NPE, () -> webSocket.sendBinary(null, true));
+        assertThrows(NPE, () -> webSocket.sendPing(null));
+        assertThrows(NPE, () -> webSocket.sendPong(null));
+        assertThrows(NPE, () -> webSocket.sendClose(NORMAL_CLOSURE, null));
+    }
+
+    // TODO: request in onClose/onError
+    // TODO: throw exception in onClose/onError
+    // TODO: exception is thrown from request()
+
+    @Test
+    public void sendCloseCompleted() throws IOException {
+        server = new DummyWebSocketServer();
+        server.open();
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                .join();
+        webSocket.sendClose(NORMAL_CLOSURE, "").join();
+        assertTrue(webSocket.isOutputClosed());
+        assertEquals(webSocket.getSubprotocol(), "");
+        webSocket.request(1); // No exceptions must be thrown
+    }
+}
--- a/test/jdk/java/net/httpclient/websocket/SendingTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +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.
- */
-
-/*
- * @test
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.websocket:open
- * @run testng/othervm --add-reads jdk.incubator.httpclient=ALL-UNNAMED jdk.incubator.httpclient/jdk.incubator.http.internal.websocket.SendingTest
- */
-public class SendingTestDriver { }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/Support.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.testng.Assert.assertThrows;
+
+public class Support {
+
+    private Support() { }
+
+    public static void assertFails(Class<? extends Throwable> clazz,
+                                    CompletionStage<?> stage) {
+        Support.assertCompletesExceptionally(clazz, stage);
+    }
+
+    public static void assertCompletesExceptionally(Class<? extends Throwable> clazz,
+                                                    CompletionStage<?> stage) {
+        CompletableFuture<?> cf =
+                CompletableFuture.completedFuture(null).thenCompose(x -> stage);
+        assertThrows(clazz, () -> {
+            try {
+                cf.join();
+            } catch (CompletionException e) {
+                throw e.getCause();
+            }
+        });
+    }
+
+    public static void assertHangs(CompletionStage<?> stage) {
+        Support.assertDoesNotCompleteWithin(5, TimeUnit.SECONDS, stage);
+    }
+
+    public static void assertDoesNotCompleteWithin(long timeout,
+                                                   TimeUnit unit,
+                                                   CompletionStage<?> stage) {
+        CompletableFuture<?> cf =
+                CompletableFuture.completedFuture(null).thenCompose(x -> stage);
+        assertThrows(TimeoutException.class, () -> cf.get(timeout, unit));
+    }
+
+    public 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;
+    }
+
+    public static DummyWebSocketServer serverWithCannedData(int... data) {
+        byte[] copy = new byte[data.length];
+        for (int i = 0; i < data.length; i++) {
+            copy[i] = (byte) data[i];
+        }
+        return serverWithCannedData(copy);
+    }
+
+    public static DummyWebSocketServer serverWithCannedData(byte... data) {
+        byte[] copy = Arrays.copyOf(data, data.length);
+        return new DummyWebSocketServer() {
+            @Override
+            protected void write(SocketChannel ch) throws IOException {
+                int off = 0; int n = 1; // 1 byte at a time
+                while (off + n < copy.length + n) {
+//                    try {
+//                        TimeUnit.MICROSECONDS.sleep(500);
+//                    } catch (InterruptedException e) {
+//                        return;
+//                    }
+                    int len = Math.min(copy.length - off, n);
+                    ByteBuffer bytes = ByteBuffer.wrap(copy, off, len);
+                    off += len;
+                    ch.write(bytes);
+                }
+                super.write(ch);
+            }
+        };
+    }
+
+    /*
+     * This server does not read from the wire, allowing its client to fill up
+     * their send buffer. Used to test scenarios with outstanding send
+     * operations.
+     */
+    public static DummyWebSocketServer notReadingServer() {
+        return new DummyWebSocketServer() {
+            @Override
+            protected void read(SocketChannel ch) throws IOException {
+                try {
+                    Thread.sleep(Long.MAX_VALUE);
+                } catch (InterruptedException e) {
+                    throw new IOException(e);
+                }
+            }
+        };
+    }
+
+    public static DummyWebSocketServer writingServer(int... data) {
+        byte[] copy = new byte[data.length];
+        for (int i = 0; i < data.length; i++) {
+            copy[i] = (byte) data[i];
+        }
+        return new DummyWebSocketServer() {
+
+            @Override
+            protected void read(SocketChannel ch) throws IOException {
+                try {
+                    Thread.sleep(Long.MAX_VALUE);
+                } catch (InterruptedException e) {
+                    throw new IOException(e);
+                }
+            }
+
+            @Override
+            protected void write(SocketChannel ch) throws IOException {
+                int off = 0; int n = 1; // 1 byte at a time
+                while (off + n < copy.length + n) {
+//                    try {
+//                        TimeUnit.MICROSECONDS.sleep(500);
+//                    } catch (InterruptedException e) {
+//                        return;
+//                    }
+                    int len = Math.min(copy.length - off, n);
+                    ByteBuffer bytes = ByteBuffer.wrap(copy, off, len);
+                    off += len;
+                    ch.write(bytes);
+                }
+                super.write(ch);
+            }
+        };
+
+    }
+
+    public static String stringWith2NBytes(int n) {
+        // -- Russian Alphabet (33 characters, 2 bytes per char) --
+        char[] abc = {
+                0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0401, 0x0416,
+                0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E,
+                0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426,
+                0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E,
+                0x042F,
+        };
+        // repeat cyclically
+        StringBuilder sb = new StringBuilder(n);
+        for (int i = 0, j = 0; i < n; i++, j = (j + 1) % abc.length) {
+            sb.append(abc[j]);
+        }
+        String s = sb.toString();
+        assert s.length() == n && s.getBytes(StandardCharsets.UTF_8).length == 2 * n;
+        return s;
+    }
+
+    public static String malformedString() {
+        return new String(new char[]{0xDC00, 0xD800});
+    }
+
+    public static String incompleteString() {
+        return new String(new char[]{0xD800});
+    }
+
+    public static String stringWithNBytes(int n) {
+        char[] chars = new char[n];
+        Arrays.fill(chars, 'A');
+        return new String(chars);
+    }
+}
--- a/test/jdk/java/net/httpclient/websocket/WSHandshakeException.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +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.
- */
-
-/*
- * @test
- * @summary Basic test for WebSocketHandshakeException
- * @library /lib/testlibrary
- * @build jdk.testlibrary.SimpleSSLContext
- * @modules jdk.incubator.httpclient
- *          jdk.httpserver
- * @run testng/othervm WSHandshakeException
- */
-;
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import com.sun.net.httpserver.HttpServer;
-import com.sun.net.httpserver.HttpsConfigurator;
-import com.sun.net.httpserver.HttpsServer;
-import jdk.incubator.http.HttpClient;
-import javax.net.ssl.SSLContext;
-import jdk.incubator.http.WebSocket;
-import jdk.incubator.http.WebSocketHandshakeException;
-import jdk.testlibrary.SimpleSSLContext;
-import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeTest;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.fail;
-import static org.testng.Assert.assertTrue;
-
-public class WSHandshakeException {
-
-    SSLContext sslContext;
-    HttpServer httpTestServer;         // HTTP/1.1    [ 2 servers ]
-    HttpsServer httpsTestServer;       // HTTPS/1.1
-    String httpURI;
-    String httpsURI;
-
-
-    static final int ITERATION_COUNT = 10;
-    // a shared executor helps reduce the amount of threads created by the test
-    static final Executor executor = Executors.newCachedThreadPool();
-
-    @DataProvider(name = "variants")
-    public Object[][] variants() {
-        return new Object[][]{
-                { httpURI,    false },
-                { httpsURI,   false },
-                { httpURI,    true },
-                { httpsURI,   true },
-        };
-    }
-
-    HttpClient newHttpClient() {
-        return HttpClient.newBuilder()
-                         .executor(executor)
-                         .sslContext(sslContext)
-                         .build();
-    }
-
-    @Test(dataProvider = "variants")
-    public void test(String uri, boolean sameClient) throws Exception {
-        HttpClient client = null;
-        for (int i=0; i< ITERATION_COUNT; i++) {
-            if (!sameClient || client == null)
-                client = newHttpClient();
-
-            try {
-                client.newWebSocketBuilder()
-                      .buildAsync(URI.create(uri), new WebSocket.Listener() { })
-                      .join();
-                fail("Expected to throw");
-            } catch (CompletionException ce) {
-                Throwable t = ce.getCause();
-                assertTrue(t instanceof WebSocketHandshakeException);
-                WebSocketHandshakeException wse = (WebSocketHandshakeException) t;
-                assertEquals(wse.getResponse().statusCode(), 404);
-            }
-        }
-    }
-
-
-    @BeforeTest
-    public void setup() throws Exception {
-        sslContext = new SimpleSSLContext().get();
-        if (sslContext == null)
-            throw new AssertionError("Unexpected null sslContext");
-
-        // HTTP/1.1
-        InetSocketAddress sa = new InetSocketAddress("localhost", 0);
-        httpTestServer = HttpServer.create(sa, 0);
-        httpURI = "ws://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/";
-
-        httpsTestServer = HttpsServer.create(sa, 0);
-        httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
-        httpsURI = "wss://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/";
-
-        httpTestServer.start();
-        httpsTestServer.start();
-    }
-
-    @AfterTest
-    public void teardown() throws Exception {
-        httpTestServer.stop(0);
-        httpsTestServer.stop(0);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/WSHandshakeExceptionTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Basic test for WebSocketHandshakeException
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @modules java.net.http
+ *          jdk.httpserver
+ * @run testng/othervm -Djdk.internal.httpclient.debug=true WSHandshakeExceptionTest
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+import java.net.InetAddress;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.net.http.WebSocketHandshakeException;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import javax.net.ssl.SSLContext;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+public class WSHandshakeExceptionTest {
+
+    SSLContext sslContext;
+    HttpServer httpTestServer;         // HTTP/1.1    [ 2 servers ]
+    HttpsServer httpsTestServer;       // HTTPS/1.1
+    String httpURI;
+    String httpsURI;
+
+    static final int ITERATION_COUNT = 10;
+    // a shared executor helps reduce the amount of threads created by the test
+    static final ExecutorService executor = Executors.newCachedThreadPool();
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][]{
+                { httpURI,    false },
+                { httpsURI,   false },
+                { httpURI,    true },
+                { httpsURI,   true },
+        };
+    }
+
+    HttpClient newHttpClient() {
+        return HttpClient.newBuilder()
+                         .executor(executor)
+                         .sslContext(sslContext)
+                         .build();
+    }
+
+    @Test(dataProvider = "variants")
+    public void test(String uri, boolean sameClient) {
+        HttpClient client = null;
+        for (int i = 0; i < ITERATION_COUNT; i++) {
+            System.out.printf("iteration %s%n", i);
+            if (!sameClient || client == null)
+                client = newHttpClient();
+
+            try {
+                client.newWebSocketBuilder()
+                      .buildAsync(URI.create(uri), new WebSocket.Listener() { })
+                      .join();
+                fail("Expected to throw");
+            } catch (CompletionException ce) {
+                Throwable t = getCompletionCause(ce);
+                if (!(t instanceof WebSocketHandshakeException)) {
+                    throw new AssertionError("Unexpected exception", t);
+                }
+                WebSocketHandshakeException wse = (WebSocketHandshakeException) t;
+                assertNotNull(wse.getResponse());
+                assertEquals(wse.getResponse().statusCode(), 404);
+            }
+        }
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        // HTTP/1.1
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpServer.create(sa, 0);
+        httpURI = "ws://localhost:" + httpTestServer.getAddress().getPort() + "/";
+
+        httpsTestServer = HttpsServer.create(sa, 0);
+        httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsURI = "wss://localhost:" + httpsTestServer.getAddress().getPort() + "/";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() {
+        httpTestServer.stop(0);
+        httpsTestServer.stop(0);
+        executor.shutdownNow();
+    }
+
+    private 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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/WebSocketBuilderTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,234 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8159053
+ * @run testng/othervm WebSocketBuilderTest
+ */
+
+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 org.testng.Assert.assertThrows;
+
+/*
+ * In some places in this test 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 final class WebSocketBuilderTest {
+
+    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();
+        assertFails(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 -> assertFails(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);
+        assertFails(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);
+        assertFails(IllegalArgumentException.class,
+                    b.buildAsync(VALID_URI, listener()));
+    }
+
+    @Test(dataProvider = "badConnectTimeouts")
+    public void illegalConnectTimeout(Duration d) {
+        WebSocket.Builder b = HttpClient.newHttpClient()
+                .newWebSocketBuilder()
+                .connectTimeout(d);
+        assertFails(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() { };
+    }
+
+    /* shortcut */
+    public static void assertFails(Class<? extends Throwable> clazz,
+                                   CompletionStage<?> stage) {
+        Support.assertCompletesExceptionally(clazz, stage);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/WebSocketExtendedTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,323 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8159053
+ *
+ *
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *      -Djdk.internal.httpclient.debug=true
+ *      -Djdk.httpclient.websocket.writeBufferSize=1024
+ *      -Djdk.httpclient.websocket.intermediateBufferSize=2048 WebSocketExtendedTest
+ */
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static java.net.http.HttpClient.newHttpClient;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+
+/*
+ * This battery of tests exercises sending data (Text/Binary) messages with
+ * possible fragmentation.
+ */
+public class WebSocketExtendedTest {
+// * run testng/othervm
+// *      -Djdk.httpclient.websocket.writeBufferSize=16
+// *      -Djdk.httpclient.sendBufferSize=32 WebSocketTextTest
+
+    private final static Random random;
+    static {
+        long seed = System.currentTimeMillis();
+        System.out.println("seed=" + seed);
+        random = new Random(seed);
+    }
+
+    // FIXME ensure subsequent (sendText/Binary, false) only CONTINUATIONs
+
+    @Test(dataProvider = "binary")
+    public void binary(ByteBuffer expected) throws IOException, InterruptedException {
+        try (DummyWebSocketServer server = new DummyWebSocketServer()) {
+            server.open();
+            WebSocket ws = newHttpClient()
+                    .newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ws.sendBinary(expected.duplicate(), true).join();
+            ws.abort();
+            ByteBuffer data = server.read();
+            List<Frame> frames = readFrames(data);
+            assertEquals(frames.size(), 1);
+            Frame f = frames.get(0);
+            assertTrue(f.last);
+            assertEquals(f.opcode, Frame.Opcode.BINARY);
+            assertEquals(f.data, expected);
+        }
+    }
+
+    private static List<Frame> readFrames(ByteBuffer src) {
+        List<Frame> frames = new ArrayList<>();
+        Frame.Consumer consumer = new Frame.Consumer() {
+
+            ByteBuffer data;
+            Frame.Opcode opcode;
+            Frame.Masker masker = new Frame.Masker();
+            boolean last;
+
+            @Override
+            public void fin(boolean value) {
+                last = value;
+            }
+
+            @Override
+            public void rsv1(boolean value) {
+                if (value) {
+                    throw new AssertionError();
+                }
+            }
+
+            @Override
+            public void rsv2(boolean value) {
+                if (value) {
+                    throw new AssertionError();
+                }
+            }
+
+            @Override
+            public void rsv3(boolean value) {
+                if (value) {
+                    throw new AssertionError();
+                }
+            }
+
+            @Override
+            public void opcode(Frame.Opcode value) {
+                opcode = value;
+            }
+
+            @Override
+            public void mask(boolean value) {
+                if (!value) { // Frames from the client MUST be masked
+                    throw new AssertionError();
+                }
+            }
+
+            @Override
+            public void payloadLen(long value) {
+                data = ByteBuffer.allocate((int) value);
+            }
+
+            @Override
+            public void maskingKey(int value) {
+                masker.mask(value);
+            }
+
+            @Override
+            public void payloadData(ByteBuffer data) {
+                masker.transferMasking(data, this.data);
+            }
+
+            @Override
+            public void endFrame() {
+                frames.add(new Frame(opcode, this.data.flip(), last));
+            }
+        };
+
+        Frame.Reader r = new Frame.Reader();
+        while (src.hasRemaining()) {
+            r.readFrame(src, consumer);
+        }
+        return frames;
+    }
+
+    @Test(dataProvider = "pingPong")
+    public void ping(ByteBuffer expected) throws Exception {
+        try (DummyWebSocketServer server = new DummyWebSocketServer()) {
+            server.open();
+            WebSocket ws = newHttpClient()
+                    .newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ws.sendPing(expected.duplicate()).join();
+            ws.abort();
+            ByteBuffer data = server.read();
+            List<Frame> frames = readFrames(data);
+            assertEquals(frames.size(), 1);
+            Frame f = frames.get(0);
+            assertEquals(f.opcode, Frame.Opcode.PING);
+            ByteBuffer actual = ByteBuffer.allocate(expected.remaining());
+            actual.put(f.data);
+            actual.flip();
+            assertEquals(actual, expected);
+        }
+    }
+
+    @Test(dataProvider = "pingPong")
+    public void pong(ByteBuffer expected) throws Exception {
+        try (DummyWebSocketServer server = new DummyWebSocketServer()) {
+            server.open();
+            WebSocket ws = newHttpClient()
+                    .newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ws.sendPong(expected.duplicate()).join();
+            ws.abort();
+            ByteBuffer data = server.read();
+            List<Frame> frames = readFrames(data);
+            assertEquals(frames.size(), 1);
+            Frame f = frames.get(0);
+            assertEquals(f.opcode, Frame.Opcode.PONG);
+            ByteBuffer actual = ByteBuffer.allocate(expected.remaining());
+            actual.put(f.data);
+            actual.flip();
+            assertEquals(actual, expected);
+        }
+    }
+
+    @Test(dataProvider = "close")
+    public void close(int statusCode, String reason) throws Exception {
+        try (DummyWebSocketServer server = new DummyWebSocketServer()) {
+            server.open();
+            WebSocket ws = newHttpClient()
+                    .newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ws.sendClose(statusCode, reason).join();
+            ws.abort();
+            ByteBuffer data = server.read();
+            List<Frame> frames = readFrames(data);
+            assertEquals(frames.size(), 1);
+            Frame f = frames.get(0);
+            assertEquals(f.opcode, Frame.Opcode.CLOSE);
+            ByteBuffer actual = ByteBuffer.allocate(Frame.MAX_CONTROL_FRAME_PAYLOAD_SIZE);
+            actual.put(f.data);
+            actual.flip();
+            assertEquals(actual.getChar(), statusCode);
+            assertEquals(StandardCharsets.UTF_8.decode(actual).toString(), reason);
+        }
+    }
+
+    @Test(dataProvider = "text")
+    public void text(String expected) throws Exception {
+        try (DummyWebSocketServer server = new DummyWebSocketServer()) {
+            server.open();
+            WebSocket ws = newHttpClient()
+                    .newWebSocketBuilder()
+                    .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                    .join();
+            ws.sendText(expected, true).join();
+            ws.abort();
+            ByteBuffer data = server.read();
+            List<Frame> frames = readFrames(data);
+
+            int maxBytes = (int) StandardCharsets.UTF_8.newEncoder().maxBytesPerChar() * expected.length();
+            ByteBuffer actual = ByteBuffer.allocate(maxBytes);
+            frames.stream().forEachOrdered(f -> actual.put(f.data));
+            actual.flip();
+            assertEquals(StandardCharsets.UTF_8.decode(actual).toString(), expected);
+        }
+    }
+
+    @DataProvider(name = "pingPong")
+    public Object[][] pingPongSizes() {
+        return new Object[][]{
+                {bytes(  0)},
+                {bytes(  1)},
+                {bytes( 63)},
+                {bytes(125)},
+        };
+    }
+
+    @DataProvider(name = "close")
+    public Object[][] closeArguments() {
+        return new Object[][]{
+                {WebSocket.NORMAL_CLOSURE, utf8String( 0)},
+                {WebSocket.NORMAL_CLOSURE, utf8String( 1)},
+                // 123 / 3 = max reason bytes / max bytes per char
+                {WebSocket.NORMAL_CLOSURE, utf8String(41)},
+        };
+    }
+
+    private static String utf8String(int n) {
+        char[] abc = {
+                // -- English Alphabet (26 characters, 1 byte per char) --
+                0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048,
+                0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
+                0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
+                0x0059, 0x005A,
+                // -- Russian Alphabet (33 characters, 2 bytes per char) --
+                0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0401, 0x0416,
+                0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E,
+                0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426,
+                0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E,
+                0x042F,
+                // -- Hiragana base characters (46 characters, 3 bytes per char) --
+                0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, 0x304F,
+                0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, 0x305F,
+                0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, 0x306C, 0x306D,
+                0x306E, 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, 0x307E, 0x307F,
+                0x3080, 0x3081, 0x3082, 0x3084, 0x3086, 0x3088, 0x3089, 0x308A,
+                0x308B, 0x308C, 0x308D, 0x308F, 0x3092, 0x3093,
+        };
+
+        assert new String(abc).getBytes(StandardCharsets.UTF_8).length > abc.length;
+
+        StringBuilder str = new StringBuilder(n);
+        random.ints(0, abc.length).limit(n).forEach(i -> str.append(abc[i]));
+        return str.toString();
+    }
+
+    @DataProvider(name = "text")
+    public Object[][] texts() {
+        return new Object[][]{
+                {utf8String(   0)},
+                {utf8String(1024)},
+        };
+    }
+
+    @DataProvider(name = "binary")
+    public Object[][] binary() {
+        return new Object[][]{
+                {bytes(   0)},
+                {bytes(1024)},
+        };
+    }
+
+    private static ByteBuffer bytes(int n) {
+        byte[] array = new byte[n];
+        random.nextBytes(array);
+        return ByteBuffer.wrap(array);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/websocket/WebSocketTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,510 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @build DummyWebSocketServer
+ * @run testng/othervm
+ *      -Djdk.internal.httpclient.websocket.debug=true
+ *       WebSocketTest
+ */
+
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.WebSocket.NORMAL_CLOSURE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+public class WebSocketTest {
+
+    private static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
+    private static final Class<IllegalStateException> ISE = IllegalStateException.class;
+    private static final Class<IOException> IOE = IOException.class;
+
+    /* shortcut */
+    private static void assertFails(Class<? extends Throwable> clazz,
+                                    CompletionStage<?> stage) {
+        Support.assertCompletesExceptionally(clazz, stage);
+    }
+
+    private DummyWebSocketServer server;
+    private WebSocket webSocket;
+
+    @AfterTest
+    public void cleanup() {
+        server.close();
+        webSocket.abort();
+    }
+
+    @Test
+    public void illegalArgument() throws IOException {
+        server = new DummyWebSocketServer();
+        server.open();
+        webSocket = newHttpClient()
+                .newWebSocketBuilder()
+                .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                .join();
+
+        assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(126)));
+        assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(127)));
+        assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(128)));
+        assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(129)));
+        assertFails(IAE, webSocket.sendPing(ByteBuffer.allocate(256)));
+
+        assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(126)));
+        assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(127)));
+        assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(128)));
+        assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(129)));
+        assertFails(IAE, webSocket.sendPong(ByteBuffer.allocate(256)));
+
+        assertFails(IOE, webSocket.sendText(Support.incompleteString(), true));
+        assertFails(IOE, webSocket.sendText(Support.incompleteString(), false));
+        assertFails(IOE, webSocket.sendText(Support.malformedString(), true));
+        assertFails(IOE, webSocket.sendText(Support.malformedString(), false));
+
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.stringWithNBytes(124)));
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.stringWithNBytes(125)));
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.stringWithNBytes(128)));
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.stringWithNBytes(256)));
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.stringWithNBytes(257)));
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.stringWith2NBytes((123 / 2) + 1)));
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.malformedString()));
+        assertFails(IAE, webSocket.sendClose(NORMAL_CLOSURE, Support.incompleteString()));
+
+        assertFails(IAE, webSocket.sendClose(-2, "a reason"));
+        assertFails(IAE, webSocket.sendClose(-1, "a reason"));
+        assertFails(IAE, webSocket.sendClose(0, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1, "a reason"));
+        assertFails(IAE, webSocket.sendClose(500, "a reason"));
+        assertFails(IAE, webSocket.sendClose(998, "a reason"));
+        assertFails(IAE, webSocket.sendClose(999, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1002, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1003, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1006, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1007, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1009, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1010, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1012, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1013, "a reason"));
+        assertFails(IAE, webSocket.sendClose(1015, "a reason"));
+        assertFails(IAE, webSocket.sendClose(5000, "a reason"));
+        assertFails(IAE, webSocket.sendClose(32768, "a reason"));
+        assertFails(IAE, webSocket.sendClose(65535, "a reason"));
+        assertFails(IAE, webSocket.sendClose(65536, "a reason"));
+        assertFails(IAE, webSocket.sendClose(Integer.MAX_VALUE, "a reason"));
+        assertFails(IAE, webSocket.sendClose(Integer.MIN_VALUE, "a reason"));
+
+        assertThrows(IAE, () -> webSocket.request(Integer.MIN_VALUE));
+        assertThrows(IAE, () -> webSocket.request(Long.MIN_VALUE));
+        assertThrows(IAE, () -> webSocket.request(-1));
+        assertThrows(IAE, () -> webSocket.request(0));
+    }
+
+    @Test
+    public void partialBinaryThenText() throws IOException {
+        server = new DummyWebSocketServer();
+        server.open();
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                .join();
+        webSocket.sendBinary(ByteBuffer.allocate(16), false).join();
+        assertFails(ISE, webSocket.sendText("text", false));
+        assertFails(ISE, webSocket.sendText("text", true));
+        // Pings & Pongs are fine
+        webSocket.sendPing(ByteBuffer.allocate(125)).join();
+        webSocket.sendPong(ByteBuffer.allocate(125)).join();
+    }
+
+    @Test
+    public void partialTextThenBinary() throws IOException {
+        server = new DummyWebSocketServer();
+        server.open();
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                .join();
+
+        webSocket.sendText("text", false).join();
+        assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(16), false));
+        assertFails(ISE, webSocket.sendBinary(ByteBuffer.allocate(16), true));
+        // Pings & Pongs are fine
+        webSocket.sendPing(ByteBuffer.allocate(125)).join();
+        webSocket.sendPong(ByteBuffer.allocate(125)).join();
+    }
+
+    @Test
+    public void sendMethodsThrowIOE1() throws IOException {
+        server = new DummyWebSocketServer();
+        server.open();
+        webSocket = newHttpClient()
+                .newWebSocketBuilder()
+                .buildAsync(server.getURI(), new WebSocket.Listener() { })
+                .join();
+
+        webSocket.sendClose(NORMAL_CLOSURE, "ok").join();
+
+        assertFails(IOE, webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok"));
+
+        assertFails(IOE, webSocket.sendText("", true));
+        assertFails(IOE, webSocket.sendText("", false));
+        assertFails(IOE, webSocket.sendText("abc", true));
+        assertFails(IOE, webSocket.sendText("abc", false));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), true));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), false));
+
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(125)));
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(124)));
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(1)));
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(0)));
+
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(125)));
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
+    }
+
+    @Test
+    public void sendMethodsThrowIOE2() throws Exception {
+        server = Support.serverWithCannedData(0x88, 0x00);
+        server.open();
+        CompletableFuture<Void> onCloseCalled = new CompletableFuture<>();
+        CompletableFuture<Void> canClose = new CompletableFuture<>();
+
+        WebSocket.Listener listener = new WebSocket.Listener() {
+            @Override
+            public CompletionStage<?> onClose(WebSocket webSocket,
+                                              int statusCode,
+                                              String reason) {
+                System.out.printf("onClose(%s, '%s')%n", statusCode, reason);
+                onCloseCalled.complete(null);
+                return canClose;
+            }
+
+            @Override
+            public void onError(WebSocket webSocket, Throwable error) {
+                System.out.println("onError(" + error + ")");
+                onCloseCalled.completeExceptionally(error);
+            }
+        };
+
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+
+        onCloseCalled.join();      // Wait for onClose to be called
+        canClose.complete(null);   // Signal to the WebSocket it can close the output
+        TimeUnit.SECONDS.sleep(5); // Give canClose some time to reach the WebSocket
+
+        assertFails(IOE, webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok"));
+
+        assertFails(IOE, webSocket.sendText("", true));
+        assertFails(IOE, webSocket.sendText("", false));
+        assertFails(IOE, webSocket.sendText("abc", true));
+        assertFails(IOE, webSocket.sendText("abc", false));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), true));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(0), false));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), true));
+        assertFails(IOE, webSocket.sendBinary(ByteBuffer.allocate(1), false));
+
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(125)));
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(124)));
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(1)));
+        assertFails(IOE, webSocket.sendPing(ByteBuffer.allocate(0)));
+
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(125)));
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(124)));
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(1)));
+        assertFails(IOE, webSocket.sendPong(ByteBuffer.allocate(0)));
+    }
+
+    @Test
+    public void simpleAggregatingBinaryMessages() throws IOException {
+        List<byte[]> expected = List.of("alpha", "beta", "gamma", "delta")
+                .stream()
+                .map(s -> s.getBytes(StandardCharsets.US_ASCII))
+                .collect(Collectors.toList());
+        int[] binary = new int[]{
+                0x82, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // [alpha]
+                0x02, 0x02, 0x62, 0x65,                   // [be
+                0x80, 0x02, 0x74, 0x61,                   // ta]
+                0x02, 0x01, 0x67,                         // [g
+                0x00, 0x01, 0x61,                         // a
+                0x00, 0x00,                               //
+                0x00, 0x00,                               //
+                0x00, 0x01, 0x6d,                         // m
+                0x00, 0x01, 0x6d,                         // m
+                0x80, 0x01, 0x61,                         // a]
+                0x8a, 0x00,                               // <PONG>
+                0x02, 0x04, 0x64, 0x65, 0x6c, 0x74,       // [delt
+                0x00, 0x01, 0x61,                         // a
+                0x80, 0x00,                               // ]
+                0x88, 0x00                                // <CLOSE>
+        };
+        CompletableFuture<List<byte[]>> actual = new CompletableFuture<>();
+
+        server = Support.serverWithCannedData(binary);
+        server.open();
+
+        WebSocket.Listener listener = new WebSocket.Listener() {
+
+            List<byte[]> collectedBytes = new ArrayList<>();
+            ByteBuffer buffer = ByteBuffer.allocate(1024);
+
+            @Override
+            public CompletionStage<?> onBinary(WebSocket webSocket,
+                                               ByteBuffer message,
+                                               boolean last) {
+                System.out.printf("onBinary(%s, %s)%n", message, last);
+                webSocket.request(1);
+
+                append(message);
+                if (last) {
+                    buffer.flip();
+                    byte[] bytes = new byte[buffer.remaining()];
+                    buffer.get(bytes);
+                    buffer.clear();
+                    processWholeBinary(bytes);
+                }
+                return null;
+            }
+
+            private void append(ByteBuffer message) {
+                if (buffer.remaining() < message.remaining()) {
+                    assert message.remaining() > 0;
+                    int cap = (buffer.capacity() + message.remaining()) * 2;
+                    ByteBuffer b = ByteBuffer.allocate(cap);
+                    b.put(buffer.flip());
+                    buffer = b;
+                }
+                buffer.put(message);
+            }
+
+            private void processWholeBinary(byte[] bytes) {
+                String stringBytes = new String(bytes, StandardCharsets.UTF_8);
+                System.out.println("processWholeBinary: " + stringBytes);
+                collectedBytes.add(bytes);
+            }
+
+            @Override
+            public CompletionStage<?> onClose(WebSocket webSocket,
+                                              int statusCode,
+                                              String reason) {
+                actual.complete(collectedBytes);
+                return null;
+            }
+
+            @Override
+            public void onError(WebSocket webSocket, Throwable error) {
+                actual.completeExceptionally(error);
+            }
+        };
+
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+
+        List<byte[]> a = actual.join();
+        assertEquals(a, expected);
+    }
+
+    @Test
+    public void simpleAggregatingTextMessages() throws IOException {
+
+        List<String> expected = List.of("alpha", "beta", "gamma", "delta");
+
+        int[] binary = new int[]{
+                0x81, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // "alpha"
+                0x01, 0x02, 0x62, 0x65,                   // "be
+                0x80, 0x02, 0x74, 0x61,                   // ta"
+                0x01, 0x01, 0x67,                         // "g
+                0x00, 0x01, 0x61,                         // a
+                0x00, 0x00,                               //
+                0x00, 0x00,                               //
+                0x00, 0x01, 0x6d,                         // m
+                0x00, 0x01, 0x6d,                         // m
+                0x80, 0x01, 0x61,                         // a"
+                0x8a, 0x00,                               // <PONG>
+                0x01, 0x04, 0x64, 0x65, 0x6c, 0x74,       // "delt
+                0x00, 0x01, 0x61,                         // a
+                0x80, 0x00,                               // "
+                0x88, 0x00                                // <CLOSE>
+        };
+        CompletableFuture<List<String>> actual = new CompletableFuture<>();
+
+        server = Support.serverWithCannedData(binary);
+        server.open();
+
+        WebSocket.Listener listener = new WebSocket.Listener() {
+
+            List<String> collectedStrings = new ArrayList<>();
+            StringBuilder text = new StringBuilder();
+
+            @Override
+            public CompletionStage<?> onText(WebSocket webSocket,
+                                             CharSequence message,
+                                             boolean last) {
+                System.out.printf("onText(%s, %s)%n", message, last);
+                webSocket.request(1);
+                text.append(message);
+                if (last) {
+                    String str = text.toString();
+                    text.setLength(0);
+                    processWholeText(str);
+                }
+                return null;
+            }
+
+            private void processWholeText(String string) {
+                System.out.println(string);
+                collectedStrings.add(string);
+            }
+
+            @Override
+            public CompletionStage<?> onClose(WebSocket webSocket,
+                                              int statusCode,
+                                              String reason) {
+                actual.complete(collectedStrings);
+                return null;
+            }
+
+            @Override
+            public void onError(WebSocket webSocket, Throwable error) {
+                actual.completeExceptionally(error);
+            }
+        };
+
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+
+        List<String> a = actual.join();
+        assertEquals(a, expected);
+    }
+
+    /*
+     * Exercises the scenario where requests for more messages are made prior to
+     * completing the returned CompletionStage instances.
+     */
+    @Test
+    public void aggregatingTextMessages() throws IOException {
+
+        List<String> expected = List.of("alpha", "beta", "gamma", "delta");
+
+        int[] binary = new int[]{
+                0x81, 0x05, 0x61, 0x6c, 0x70, 0x68, 0x61, // "alpha"
+                0x01, 0x02, 0x62, 0x65,                   // "be
+                0x80, 0x02, 0x74, 0x61,                   // ta"
+                0x01, 0x01, 0x67,                         // "g
+                0x00, 0x01, 0x61,                         // a
+                0x00, 0x00,                               //
+                0x00, 0x00,                               //
+                0x00, 0x01, 0x6d,                         // m
+                0x00, 0x01, 0x6d,                         // m
+                0x80, 0x01, 0x61,                         // a"
+                0x8a, 0x00,                               // <PONG>
+                0x01, 0x04, 0x64, 0x65, 0x6c, 0x74,       // "delt
+                0x00, 0x01, 0x61,                         // a
+                0x80, 0x00,                               // "
+                0x88, 0x00                                // <CLOSE>
+        };
+        CompletableFuture<List<String>> actual = new CompletableFuture<>();
+
+
+        server = Support.serverWithCannedData(binary);
+        server.open();
+
+        WebSocket.Listener listener = new WebSocket.Listener() {
+
+            List<CharSequence> parts = new ArrayList<>();
+            /*
+             * A CompletableFuture which will complete once the current
+             * message has been fully assembled. Until then the listener
+             * returns this instance for every call.
+             */
+            CompletableFuture<?> currentCf = new CompletableFuture<>();
+            List<String> collected = new ArrayList<>();
+
+            @Override
+            public CompletionStage<?> onText(WebSocket webSocket,
+                                             CharSequence message,
+                                             boolean last) {
+                parts.add(message);
+                if (!last) {
+                    webSocket.request(1);
+                } else {
+                    this.currentCf.thenRun(() -> webSocket.request(1));
+                    CompletableFuture<?> refCf = this.currentCf;
+                    processWholeMessage(new ArrayList<>(parts), refCf);
+                    currentCf = new CompletableFuture<>();
+                    parts.clear();
+                    return refCf;
+                }
+                return currentCf;
+            }
+
+            @Override
+            public CompletionStage<?> onClose(WebSocket webSocket,
+                                              int statusCode,
+                                              String reason) {
+                actual.complete(collected);
+                return null;
+            }
+
+            @Override
+            public void onError(WebSocket webSocket, Throwable error) {
+                actual.completeExceptionally(error);
+            }
+
+            public void processWholeMessage(List<CharSequence> data,
+                                            CompletableFuture<?> cf) {
+                StringBuilder b = new StringBuilder();
+                data.forEach(b::append);
+                String s = b.toString();
+                System.out.println(s);
+                cf.complete(null);
+                collected.add(s);
+            }
+        };
+
+        webSocket = newHttpClient().newWebSocketBuilder()
+                .buildAsync(server.getURI(), listener)
+                .join();
+
+        List<String> a = actual.join();
+        assertEquals(a, 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/HeaderWriterTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,115 @@
+/*
+ * 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 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,158 @@
+/*
+ * 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 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/MessageQueueTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,479 @@
+/*
+ * 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.websocket;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+import static jdk.internal.net.http.websocket.MessageQueue.effectiveCapacityOf;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+
+/*
+ * A unit test for MessageQueue. The test is aware of the details of the queue
+ * implementation.
+ */
+public class MessageQueueTest {
+
+    private static final Random r = new SecureRandom();
+
+    @DataProvider(name = "illegalCapacities")
+    public static Object[][] illegalCapacities() {
+        return new Object[][]{
+                new Object[]{Integer.MIN_VALUE},
+                new Object[]{-2},
+                new Object[]{-1},
+                new Object[]{ 0},
+        };
+    }
+
+    @Test(dataProvider = "illegalCapacities")
+    public void illegalCapacity(int n) {
+        assertThrows(IllegalArgumentException.class, () -> new MessageQueue(n));
+    }
+
+    @Test(dataProvider = "capacities")
+    public void emptiness(int n) {
+        assertTrue(new MessageQueue(n).isEmpty());
+    }
+
+    @Test(dataProvider = "capacities")
+    public void fullness(int n) throws IOException {
+        MessageQueue q = new MessageQueue(n);
+        int cap = effectiveCapacityOf(n);
+        Adder adder = new Adder();
+        Queue<Message> referenceQueue = new LinkedList<>();
+        for (int i = 0; i < cap; i++) {
+            Message m = createRandomMessage();
+            referenceQueue.add(m);
+            adder.add(q, m);
+        }
+        for (int i = 0; i < cap + 1; i++) {
+            Message m = createRandomMessage();
+            assertThrows(IOException.class, () -> adder.add(q, m));
+        }
+        for (int i = 0; i < cap; i++) {
+            Message expected = referenceQueue.remove();
+            Message actual = new Remover().removeFrom(q);
+            assertEquals(actual, expected);
+        }
+    }
+
+    private Message createRandomMessage() {
+        Message.Type[] values = Message.Type.values();
+        Message.Type type = values[r.nextInt(values.length)];
+        Supplier<? extends ByteBuffer> binarySupplier = null;
+        ByteBuffer binary = null;
+        CharBuffer text = null;
+        boolean isLast = false;
+        int statusCode = -1;
+        switch (type) {
+            case TEXT:
+                text = CharBuffer.allocate(r.nextInt(17));
+                isLast = r.nextBoolean();
+                break;
+            case BINARY:
+                binary = ByteBuffer.allocate(r.nextInt(19));
+                isLast = r.nextBoolean();
+                break;
+            case PING:
+                binary = ByteBuffer.allocate(r.nextInt(19));
+                break;
+            case PONG:
+                if (r.nextBoolean()) {
+                    binary = ByteBuffer.allocate(r.nextInt(19));
+                } else {
+                    binarySupplier = () -> ByteBuffer.allocate(r.nextInt(19));
+                }
+                break;
+            case CLOSE:
+                text = CharBuffer.allocate(r.nextInt(17));
+                statusCode = r.nextInt();
+                break;
+            default:
+                throw new AssertionError();
+        }
+        BiConsumer<Integer, Throwable> action = new BiConsumer<>() {
+            @Override
+            public void accept(Integer o, Throwable throwable) { }
+        };
+        CompletableFuture<Integer> future = new CompletableFuture<>();
+        return new Message(type, binarySupplier, binary, text, isLast, statusCode, r.nextInt(),
+                           action, future);
+    }
+
+    @Test(dataProvider = "capacities")
+    public void caterpillarWalk(int n) throws IOException {
+//        System.out.println("n: " + n);
+        int cap = effectiveCapacityOf(n);
+        for (int p = 1; p <= cap; p++) { // pace
+//            System.out.println("  pace: " + p);
+            MessageQueue q = new MessageQueue(n);
+            Queue<Message> referenceQueue = new LinkedList<>();
+            Adder adder = new Adder();
+            for (int k = 0; k < (cap / p) + 1; k++) {
+//                System.out.println("    cycle: " + k);
+                for (int i = 0; i < p; i++) {
+                    Message m = createRandomMessage();
+                    referenceQueue.add(m);
+                    adder.add(q, m);
+                }
+                Remover remover = new Remover();
+                for (int i = 0; i < p; i++) {
+                    Message expected = referenceQueue.remove();
+                    Message actual = remover.removeFrom(q);
+                    assertEquals(actual, expected);
+                }
+                assertTrue(q.isEmpty());
+            }
+        }
+    }
+
+    /* Exercises only concurrent additions */
+    @Test
+    public void halfConcurrency() throws ExecutionException, InterruptedException {
+        int n = Runtime.getRuntime().availableProcessors() + 2;
+        ExecutorService executorService = Executors.newFixedThreadPool(n);
+        CyclicBarrier start = new CyclicBarrier(n);
+        Adder adder = new Adder();
+        List<Future<?>> futures = new ArrayList<>(n);
+        try {
+            for (int k = 0; k < 1024; k++) {
+                MessageQueue q = new MessageQueue(n);
+                for (int i = 0; i < n; i++) {
+                    Message m = createRandomMessage();
+                    Future<Void> f = executorService.submit(() -> {
+                        start.await();
+                        adder.add(q, m);
+                        return null;
+                    });
+                    futures.add(f);
+                }
+                for (Future<?> f : futures) {
+                    f.get();    // Just to check for exceptions
+                }
+                futures.clear();
+            }
+        } finally {
+            executorService.shutdownNow();
+        }
+    }
+
+    // TODO: same message; different messages; a mix thereof
+
+    @Test
+    public void concurrency() throws ExecutionException, InterruptedException {
+        int nProducers = Runtime.getRuntime().availableProcessors() + 2;
+        int nThreads = nProducers + 1;
+        ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
+        CyclicBarrier start = new CyclicBarrier(nThreads);
+        MessageQueue q = new MessageQueue(nProducers);
+        Adder adder = new Adder();
+        Remover remover = new Remover();
+        List<Message> expectedList = new ArrayList<>(nProducers);
+        List<Message> actualList = new ArrayList<>(nProducers);
+        List<Future<?>> futures = new ArrayList<>(nProducers);
+        try {
+            for (int k = 0; k < 1024; k++) {
+                for (int i = 0; i < nProducers; i++) {
+                    Message m = createRandomMessage();
+                    expectedList.add(m);
+                    Future<Void> f = executorService.submit(() -> {
+                        start.await();
+                        adder.add(q, m);
+                        return null;
+                    });
+                    futures.add(f);
+                }
+                Future<Void> consumer = executorService.submit(() -> {
+                    int i = 0;
+                    start.await();
+                    while (i < nProducers) {
+                        Message m = remover.removeFrom(q);
+                        if (m != null) {
+                            actualList.add(m);
+                            i++;
+                        }
+                    }
+                    return null;
+                });
+                for (Future<?> f : futures) {
+                    f.get();    // Just to check for exceptions
+                }
+                consumer.get(); // Waiting for consumer to collect all the messages
+                assertEquals(actualList.size(), expectedList.size());
+                for (Message m : expectedList) {
+                    assertTrue(actualList.remove(m));
+                }
+                assertTrue(actualList.isEmpty());
+                assertTrue(q.isEmpty());
+                expectedList.clear();
+                futures.clear();
+            }
+        } finally {
+            executorService.shutdownNow();
+        }
+    }
+
+    @Test(dataProvider = "capacities")
+    public void testSingleThreaded(int n) throws IOException {
+        Queue<Message> referenceQueue = new LinkedList<>();
+        MessageQueue q = new MessageQueue(n);
+        int cap = effectiveCapacityOf(n);
+        Adder adder = new Adder();
+        for (int i = 0; i < cap; i++) {
+            Message m = createRandomMessage();
+            referenceQueue.add(m);
+            adder.add(q, m);
+        }
+        for (int i = 0; i < cap; i++) {
+            Message expected = referenceQueue.remove();
+            Message actual = new Remover().removeFrom(q);
+            assertEquals(actual, expected);
+        }
+        assertTrue(q.isEmpty());
+    }
+
+    @DataProvider(name = "capacities")
+    public Object[][] capacities() {
+        return new Object[][]{
+                new Object[]{  1},
+                new Object[]{  2},
+                new Object[]{  3},
+                new Object[]{  4},
+                new Object[]{  5},
+                new Object[]{  6},
+                new Object[]{  7},
+                new Object[]{  8},
+                new Object[]{  9},
+                new Object[]{128},
+                new Object[]{256},
+        };
+    }
+
+    // -- auxiliary test infrastructure --
+
+    static class Adder {
+
+        @SuppressWarnings("unchecked")
+        void add(MessageQueue q, Message m) throws IOException {
+            switch (m.type) {
+                case TEXT:
+                    q.addText(m.text, m.isLast, m.attachment, m.action, m.future);
+                    break;
+                case BINARY:
+                    q.addBinary(m.binary, m.isLast, m.attachment, m.action, m.future);
+                    break;
+                case PING:
+                    q.addPing(m.binary, m.attachment, m.action, m.future);
+                    break;
+                case PONG:
+                    if (m.binarySupplier != null) {
+                        q.addPong(m.binarySupplier, m.attachment, m.action, m.future);
+                    } else {
+                        q.addPong(m.binary, m.attachment, m.action, m.future);
+                    }
+                    break;
+                case CLOSE:
+                    q.addClose(m.statusCode, m.text, m.attachment, m.action, m.future);
+                    break;
+                default:
+                    throw new InternalError();
+            }
+        }
+    }
+
+    static class Remover {
+
+        Message removeFrom(MessageQueue q) {
+            Message m = q.peek(new MessageQueue.QueueCallback<>() {
+
+                boolean called;
+
+                @Override
+                public <T> Message onText(CharBuffer message,
+                                          boolean isLast,
+                                          T attachment,
+                                          BiConsumer<? super T, ? super Throwable> action,
+                                          CompletableFuture<? super T> future) {
+                    assertFalse(called);
+                    called = true;
+                    return new Message(Message.Type.TEXT, null, null, message, isLast,
+                                       -1, attachment, action, future);
+                }
+
+                @Override
+                public <T> Message onBinary(ByteBuffer message,
+                                            boolean isLast,
+                                            T attachment,
+                                            BiConsumer<? super T, ? super Throwable> action,
+                                            CompletableFuture<? super T> future) {
+                    assertFalse(called);
+                    called = true;
+                    return new Message(Message.Type.BINARY, null, message, null, isLast,
+                                       -1, attachment, action, future);
+                }
+
+                @Override
+                public <T> Message onPing(ByteBuffer message,
+                                          T attachment,
+                                          BiConsumer<? super T, ? super Throwable> action,
+                                          CompletableFuture<? super T> future) {
+                    assertFalse(called);
+                    called = true;
+                    return new Message(Message.Type.PING, null, message, null, false,
+                                       -1, attachment, action, future);
+                }
+
+                @Override
+                public <T> Message onPong(ByteBuffer message,
+                                          T attachment,
+                                          BiConsumer<? super T, ? super Throwable> action,
+                                          CompletableFuture<? super T> future) {
+                    assertFalse(called);
+                    called = true;
+                    return new Message(Message.Type.PONG, null, message, null, false,
+                                       -1, attachment, action, future);
+                }
+
+                @Override
+                public <T> Message onPong(Supplier<? extends ByteBuffer> message,
+                                          T attachment,
+                                          BiConsumer<? super T, ? super Throwable> action,
+                                          CompletableFuture<? super T> future) {
+                    assertFalse(called);
+                    called = true;
+                    return new Message(Message.Type.PONG, message, null, null, false,
+                                       -1, attachment, action, future);
+                }
+
+                @Override
+                public <T> Message onClose(int statusCode,
+                                           CharBuffer reason,
+                                           T attachment,
+                                           BiConsumer<? super T, ? super Throwable> action,
+                                           CompletableFuture<? super T> future) {
+                    assertFalse(called);
+                    called = true;
+                    return new Message(Message.Type.CLOSE, null, null, reason, false,
+                                       statusCode, attachment, action, future);
+                }
+
+                @Override
+                public Message onEmpty() throws RuntimeException {
+                    return null;
+                }
+            });
+            if (m != null) {
+                q.remove();
+            }
+            return m;
+        }
+    }
+
+    static class Message {
+
+        private final Type type;
+        private final Supplier<? extends ByteBuffer> binarySupplier;
+        private final ByteBuffer binary;
+        private final CharBuffer text;
+        private final boolean isLast;
+        private final int statusCode;
+        private final Object attachment;
+        @SuppressWarnings("rawtypes")
+        private final BiConsumer action;
+        @SuppressWarnings("rawtypes")
+        private final CompletableFuture future;
+
+        <T> Message(Type type,
+                    Supplier<? extends ByteBuffer> binarySupplier,
+                    ByteBuffer binary,
+                    CharBuffer text,
+                    boolean isLast,
+                    int statusCode,
+                    T attachment,
+                    BiConsumer<? super T, ? super Throwable> action,
+                    CompletableFuture<? super T> future) {
+            this.type = type;
+            this.binarySupplier = binarySupplier;
+            this.binary = binary;
+            this.text = text;
+            this.isLast = isLast;
+            this.statusCode = statusCode;
+            this.attachment = attachment;
+            this.action = action;
+            this.future = future;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(type, binarySupplier, binary, text, isLast, statusCode, attachment, action, future);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Message message = (Message) o;
+            return isLast == message.isLast &&
+                    statusCode == message.statusCode &&
+                    type == message.type &&
+                    Objects.equals(binarySupplier, message.binarySupplier) &&
+                    Objects.equals(binary, message.binary) &&
+                    Objects.equals(text, message.text) &&
+                    Objects.equals(attachment, message.attachment) &&
+                    Objects.equals(action, message.action) &&
+                    Objects.equals(future, message.future);
+        }
+
+        enum Type {
+            TEXT,
+            BINARY,
+            PING,
+            PONG,
+            CLOSE
+        }
+    }
+}
--- /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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,271 @@
+/*
+ * 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 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,324 @@
+/*
+ * 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.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);
+    }
+
+    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/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/BuildingWebSocketTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http.internal.websocket;
-
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import java.net.URI;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.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.incubator.http.internal.websocket.TestSupport.assertCompletesExceptionally;
-import static jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/HeaderWriterTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http.internal.websocket;
-
-import org.testng.annotations.Test;
-import jdk.incubator.http.internal.websocket.Frame.HeaderWriter;
-import jdk.incubator.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 jdk.incubator.http.internal.websocket.TestSupport.assertThrows;
-import static jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MaskerTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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 jdk.incubator.http.internal.websocket.Frame.Masker.transferMasking;
-import static jdk.incubator.http.internal.websocket.TestSupport.forEachBufferPartition;
-import static jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockListener.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.WebSocket;
-import jdk.incubator.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.incubator.http.internal.websocket.TestSupport.fullCopy;
-
-public class MockListener implements WebSocket.Listener {
-
-    private final long bufferSize;
-    private long count;
-    private final List<ListenerInvocation> 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<ListenerInvocation> invocations() {
-        return new ArrayList<>(invocations);
-    }
-
-    public abstract static class ListenerInvocation {
-
-        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 ListenerInvocation(WebSocket webSocket) {
-            this.webSocket = webSocket;
-        }
-    }
-
-    public static final class OnOpen extends ListenerInvocation {
-
-        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;
-            ListenerInvocation that = (ListenerInvocation) o;
-            return Objects.equals(webSocket, that.webSocket);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(webSocket);
-        }
-    }
-
-    public static final class OnText extends ListenerInvocation {
-
-        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 ListenerInvocation {
-
-        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 ListenerInvocation {
-
-        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 ListenerInvocation {
-
-        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 ListenerInvocation {
-
-        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 ListenerInvocation {
-
-        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/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockReceiver.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.internal.common.Pair;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.http.internal.common.SequentialScheduler.DeferredCompleter;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.function.Consumer;
-
-public class MockReceiver extends Receiver {
-
-    private final Iterator<Pair<CompletionStage<?>, Consumer<MessageStreamConsumer>>> iterator;
-    private final MessageStreamConsumer consumer;
-
-    public MockReceiver(MessageStreamConsumer consumer, RawChannel channel,
-                        Pair<CompletionStage<?>, Consumer<MessageStreamConsumer>>... pairs) {
-        super(consumer, channel);
-        this.consumer = consumer;
-        iterator = Arrays.asList(pairs).iterator();
-    }
-
-    @Override
-    protected SequentialScheduler createScheduler() {
-        class X { // Class is hack needed to allow the task to refer to the scheduler
-            SequentialScheduler scheduler = new SequentialScheduler(task());
-
-            SequentialScheduler.RestartableTask task() {
-                return new SequentialScheduler.RestartableTask() {
-                    @Override
-                    public void run(DeferredCompleter taskCompleter) {
-                        if (!scheduler.isStopped() && !demand.isFulfilled()) {
-                            if (!iterator.hasNext()) {
-                                taskCompleter.complete();
-                                return;
-                            }
-                            Pair<CompletionStage<?>, Consumer<MessageStreamConsumer>> p = iterator.next();
-                            CompletableFuture<?> cf = p.first.toCompletableFuture();
-                            if (cf.isDone()) { // Forcing synchronous execution
-                                p.second.accept(consumer);
-                                repeat(taskCompleter);
-                            } else {
-                                cf.whenCompleteAsync((r, e) -> {
-                                    p.second.accept(consumer);
-                                    repeat(taskCompleter);
-                                });
-                            }
-                        } else {
-                            taskCompleter.complete();
-                        }
-                    }
-
-                    private void repeat(DeferredCompleter taskCompleter) {
-                        taskCompleter.complete();
-                        scheduler.runOrSchedule();
-                    }
-                };
-            }
-        }
-        return new X().scheduler;
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockTransmitter.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import java.util.Queue;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.function.Consumer;
-
-public abstract class MockTransmitter extends Transmitter {
-
-    private final long startTime = System.currentTimeMillis();
-
-    private final Queue<OutgoingMessage> messages = new ConcurrentLinkedQueue<>();
-
-    public MockTransmitter() {
-        super(null);
-    }
-
-    @Override
-    public void send(OutgoingMessage message,
-                     Consumer<Exception> completionHandler) {
-        System.out.printf("[%6s ms.] begin send(%s)%n",
-                          System.currentTimeMillis() - startTime,
-                          message);
-        messages.add(message);
-        whenSent().whenComplete((r, e) -> {
-            System.out.printf("[%6s ms.] complete send(%s)%n",
-                              System.currentTimeMillis() - startTime,
-                              message);
-            if (e != null) {
-                completionHandler.accept((Exception) e);
-            } else {
-                completionHandler.accept(null);
-            }
-        });
-        System.out.printf("[%6s ms.] end send(%s)%n",
-                          System.currentTimeMillis() - startTime,
-                          message);
-    }
-
-    @Override
-    public void close() { }
-
-    protected abstract CompletionStage<?> whenSent();
-
-    public Queue<OutgoingMessage> queue() {
-        return messages;
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/MockTransport.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import java.nio.ByteBuffer;
-
-public class MockTransport extends TransportSupplier {
-
-    public MockTransport() {
-        super(new NullRawChannel());
-    }
-
-    public static class NullRawChannel implements RawChannel {
-
-        @Override
-        public void registerEvent(RawEvent event) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public ByteBuffer initialByteBuffer() {
-            return ByteBuffer.allocate(0);
-        }
-
-        @Override
-        public ByteBuffer read() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public long write(ByteBuffer[] srcs, int offset, int length) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void shutdownInput() {
-        }
-
-        @Override
-        public void shutdownOutput() {
-        }
-
-        @Override
-        public void close() {
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/ReaderTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http.internal.websocket;
-
-import org.testng.annotations.Test;
-import jdk.incubator.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 jdk.incubator.http.internal.websocket.TestSupport.assertThrows;
-import static jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/ReceivingTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.WebSocket;
-import org.testng.annotations.Test;
-
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.TimeUnit;
-
-import static java.util.concurrent.CompletableFuture.completedStage;
-import static jdk.incubator.http.WebSocket.MessagePart.FIRST;
-import static jdk.incubator.http.WebSocket.MessagePart.LAST;
-import static jdk.incubator.http.WebSocket.MessagePart.PART;
-import static jdk.incubator.http.WebSocket.MessagePart.WHOLE;
-import static jdk.incubator.http.WebSocket.NORMAL_CLOSURE;
-import static jdk.incubator.http.internal.common.Pair.pair;
-import static jdk.incubator.http.internal.websocket.MockListener.ListenerInvocation.onClose;
-import static jdk.incubator.http.internal.websocket.MockListener.ListenerInvocation.onError;
-import static jdk.incubator.http.internal.websocket.MockListener.ListenerInvocation.onOpen;
-import static jdk.incubator.http.internal.websocket.MockListener.ListenerInvocation.onPing;
-import static jdk.incubator.http.internal.websocket.MockListener.ListenerInvocation.onPong;
-import static jdk.incubator.http.internal.websocket.MockListener.ListenerInvocation.onText;
-import static org.testng.Assert.assertEquals;
-
-public class ReceivingTest {
-
-    // TODO: request in onClose/onError
-    // TODO: throw exception in onClose/onError
-    // TODO: exception is thrown from request()
-
-    @Test
-    public void testNonPositiveRequest() throws Exception {
-        MockListener listener = new MockListener(Long.MAX_VALUE) {
-            @Override
-            protected void onOpen0(WebSocket webSocket) {
-                webSocket.request(0);
-            }
-        };
-        MockTransport transport = new MockTransport() {
-            @Override
-            protected Receiver newReceiver(MessageStreamConsumer consumer) {
-                return new MockReceiver(consumer, channel, pair(now(), m -> m.onText("1", WHOLE)));
-            }
-        };
-        WebSocket ws = newInstance(listener, transport);
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.ListenerInvocation> 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);
-        MockTransport transport = new MockTransport() {
-            @Override
-            protected Receiver newReceiver(MessageStreamConsumer consumer) {
-                return new MockReceiver(consumer, channel,
-                                        pair(now(), m -> m.onText("1", FIRST)),
-                                        pair(now(), m -> m.onText("2", PART)),
-                                        pair(now(), m -> m.onText("3", LAST)),
-                                        pair(now(), m -> m.onClose(NORMAL_CLOSURE, "no reason")));
-            }
-        };
-        WebSocket ws = newInstance(listener, transport);
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.ListenerInvocation> 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);
-        MockTransport transport = new MockTransport() {
-            @Override
-            protected Receiver newReceiver(MessageStreamConsumer consumer) {
-                return new MockReceiver(consumer, channel,
-                                        pair(now(),      m -> m.onText("1", FIRST)),
-                                        pair(seconds(1), m -> m.onText("2", PART)),
-                                        pair(now(),      m -> m.onText("3", LAST)),
-                                        pair(seconds(1), m -> m.onClose(NORMAL_CLOSURE, "no reason")));
-            }
-        };
-        WebSocket ws = newInstance(listener, transport);
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.ListenerInvocation> 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);
-        MockTransport transport = new MockTransport() {
-            @Override
-            protected Receiver newReceiver(MessageStreamConsumer consumer) {
-                return new MockReceiver(consumer, channel,
-                                        pair(now(),      m -> m.onText("1", FIRST)),
-                                        pair(now(),      m -> m.onText("2", PART)),
-                                        pair(now(),      m -> m.onPong(ByteBuffer.allocate(16))),
-                                        pair(seconds(1), m -> m.onPong(ByteBuffer.allocate(32))),
-                                        pair(now(),      m -> m.onText("3", LAST)),
-                                        pair(now(),      m -> m.onPong(ByteBuffer.allocate(64))),
-                                        pair(now(),      m -> m.onClose(NORMAL_CLOSURE, "no reason")));
-            }
-        };
-        WebSocket ws = newInstance(listener, transport);
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.ListenerInvocation> 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);
-        MockTransport transport = new MockTransport() {
-            @Override
-            protected Receiver newReceiver(MessageStreamConsumer consumer) {
-                return new MockReceiver(consumer, channel,
-                                        pair(now(),      m -> m.onText("1", FIRST)),
-                                        pair(now(),      m -> m.onText("2", PART)),
-                                        pair(now(),      m -> m.onPing(ByteBuffer.allocate(16))),
-                                        pair(seconds(1), m -> m.onPing(ByteBuffer.allocate(32))),
-                                        pair(now(),      m -> m.onText("3", LAST)),
-                                        pair(now(),      m -> m.onPing(ByteBuffer.allocate(64))),
-                                        pair(now(),      m -> m.onClose(NORMAL_CLOSURE, "no reason")));
-            }
-
-            @Override
-            protected Transmitter newTransmitter() {
-                return new MockTransmitter() {
-                    @Override
-                    protected CompletionStage<?> whenSent() {
-                        return now();
-                    }
-                };
-            }
-        };
-        WebSocket ws = newInstance(listener, transport);
-        listener.onCloseOrOnErrorCalled().get(10, TimeUnit.SECONDS);
-        List<MockListener.ListenerInvocation> invocations = listener.invocations();
-        System.out.println(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")));
-    }
-
-    private static CompletionStage<?> seconds(long s) {
-        return new CompletableFuture<>().completeOnTimeout(null, s, TimeUnit.SECONDS);
-    }
-
-    private static CompletionStage<?> now() {
-        return completedStage(null);
-    }
-
-    private static WebSocket newInstance(WebSocket.Listener listener,
-                                         TransportSupplier transport) {
-        URI uri = URI.create("ws://localhost");
-        String subprotocol = "";
-        return WebSocketImpl.newInstance(uri, subprotocol, listener, transport);
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/SendingTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http.internal.websocket;
-
-import jdk.incubator.http.WebSocket;
-import org.testng.annotations.Test;
-
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static jdk.incubator.http.WebSocket.NORMAL_CLOSURE;
-import static jdk.incubator.http.internal.websocket.TestSupport.assertCompletesExceptionally;
-import static jdk.incubator.http.internal.websocket.WebSocketImpl.newInstance;
-import static org.testng.Assert.assertEquals;
-
-public class SendingTest {
-
-    @Test
-    public void sendTextImmediately() {
-        MockTransmitter t = new MockTransmitter() {
-            @Override
-            protected CompletionStage<?> whenSent() {
-                return CompletableFuture.completedFuture(null);
-            }
-        };
-        WebSocket ws = newWebSocket(t);
-        CompletableFuture.completedFuture(ws)
-                .thenCompose(w -> w.sendText("1", true))
-                .thenCompose(w -> w.sendText("2", true))
-                .thenCompose(w -> w.sendText("3", true))
-                .join();
-
-        assertEquals(t.queue().size(), 3);
-    }
-
-    @Test
-    public void sendTextWithDelay() {
-        MockTransmitter t = new MockTransmitter() {
-            @Override
-            protected CompletionStage<?> whenSent() {
-                return new CompletableFuture<>()
-                        .completeOnTimeout(null, 1, TimeUnit.SECONDS);
-            }
-        };
-        WebSocket ws = newWebSocket(t);
-        CompletableFuture.completedFuture(ws)
-                .thenCompose(w -> w.sendText("1", true))
-                .thenCompose(w -> w.sendText("2", true))
-                .thenCompose(w -> w.sendText("3", true))
-                .join();
-
-        assertEquals(t.queue().size(), 3);
-    }
-
-    @Test
-    public void sendTextMixedDelay() {
-        Random r = new Random();
-
-        MockTransmitter t = new MockTransmitter() {
-            @Override
-            protected CompletionStage<?> whenSent() {
-                return r.nextBoolean() ?
-                        new CompletableFuture<>().completeOnTimeout(null, 1, TimeUnit.SECONDS) :
-                        CompletableFuture.completedFuture(null);
-            }
-        };
-        WebSocket ws = newWebSocket(t);
-        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(t.queue().size(), 9);
-    }
-
-    @Test
-    public void sendControlMessagesConcurrently() {
-
-        CompletableFuture<?> first = new CompletableFuture<>();
-
-        MockTransmitter t = new MockTransmitter() {
-
-            final AtomicInteger i = new AtomicInteger();
-
-            @Override
-            protected CompletionStage<?> whenSent() {
-                if (i.incrementAndGet() == 1) {
-                    return first;
-                } else {
-                    return CompletableFuture.completedFuture(null);
-                }
-            }
-        };
-        WebSocket ws = newWebSocket(t);
-
-        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(t.queue().size(), 3); // 6 minus 3 that were not accepted
-    }
-
-    private static WebSocket newWebSocket(Transmitter transmitter) {
-        URI uri = URI.create("ws://localhost");
-        String subprotocol = "";
-        TransportSupplier transport = new MockTransport() {
-            @Override
-            public Transmitter transmitter() {
-                return transmitter;
-            }
-        };
-        return newInstance(uri,
-                           subprotocol,
-                           new MockListener(Long.MAX_VALUE),
-                           transport);
-    }
-}
--- a/test/jdk/java/net/httpclient/websocket/jdk.incubator.httpclient/jdk/incubator/http/internal/websocket/TestSupport.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,336 +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 jdk.incubator.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 (predicate.test(caught)) {
-            System.out.println("Got expected exception: " + caught);
-            return caught;
-        }
-        if (caught == null) {
-            throw new AssertionFailedException("No exception was thrown");
-        }
-        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 == t.getCause().getClass(), 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/security/WSURLPermissionTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/security/WSURLPermissionTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -29,6 +29,7 @@
  */
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Proxy;
 import java.net.ProxySelector;
@@ -44,8 +45,8 @@
 import java.security.ProtectionDomain;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.WebSocket;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.DataProvider;
@@ -74,7 +75,8 @@
     @BeforeTest
     public void setup() throws Exception {
         ProxyServer proxyServer = new ProxyServer(0, true);
-        proxyAddress = new InetSocketAddress("127.0.0.1", proxyServer.getPort());
+        proxyAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(),
+                                             proxyServer.getPort());
         webSocketServer = new DummyWebSocketServer();
         webSocketServer.open();
         wsURI = webSocketServer.getURI();
--- a/test/jdk/java/net/httpclient/websocket/security/httpclient.policy	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/security/httpclient.policy	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -21,49 +21,7 @@
 // questions.
 //
 
-grant codeBase "jrt:/jdk.incubator.httpclient" {
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.util";
-    permission java.lang.RuntimePermission "accessClassInPackage.sun.net.www";
-    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.misc";
-
-    // ## why is SP not good enough. Check API @throws signatures and impl
-    permission java.net.SocketPermission "*","connect,resolve";
-    permission java.net.URLPermission "http:*","*:*";
-    permission java.net.URLPermission "https:*","*:*";
-    permission java.net.URLPermission "ws:*","*:*";
-    permission java.net.URLPermission "wss:*","*:*";
-    permission java.net.URLPermission "socket:*","CONNECT";  // proxy
-
-    // For request/response body processors, fromFile, asFile
-    permission java.io.FilePermission "<<ALL FILES>>","read,write,delete";
-
-    // ## look at the different property names!
-    permission java.util.PropertyPermission "jdk.httpclient.HttpClient.log","read";  // name!
-    permission java.util.PropertyPermission "jdk.httpclient.auth.retrylimit","read";
-    permission java.util.PropertyPermission "jdk.httpclient.connectionWindowSize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.enablepush","read";
-    permission java.util.PropertyPermission "jdk.httpclient.hpack.maxheadertablesize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.keepalive.timeout","read";
-    permission java.util.PropertyPermission "jdk.httpclient.maxframesize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.maxstreams","read";
-    permission java.util.PropertyPermission "jdk.httpclient.redirects.retrylimit","read";
-    permission java.util.PropertyPermission "jdk.httpclient.windowsize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.receiveBufferSize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.bufsize","read";
-    permission java.util.PropertyPermission "jdk.httpclient.internal.selector.timeout","read";
-    permission java.util.PropertyPermission "jdk.internal.httpclient.debug","read";
-    permission java.util.PropertyPermission "jdk.internal.httpclient.hpack.debug","read";
-    permission java.util.PropertyPermission "jdk.internal.httpclient.hpack.log.level","read";
-    permission java.util.PropertyPermission "test.src","read";
-
-    permission java.net.NetPermission "getProxySelector";
-
-    permission java.security.SecurityPermission "createAccessControlContext";
-};
-
 // bootstrap to get the test going, it will do its own restrictions
 grant codeBase "file:${test.classes}/*" {
     permission java.security.AllPermission;
 };
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/AuthenticationFilterTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.AuthenticationFilterTest
+ */
--- a/test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/ConnectionPoolTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,9 @@
  * @summary Verifies that the ConnectionPool correctly handle
  *          connection deadlines and purges the right connections
  *          from the cache.
- * @modules jdk.incubator.httpclient java.management
- * @run main/othervm --add-reads jdk.incubator.httpclient=java.management jdk.incubator.httpclient/jdk.incubator.http.ConnectionPoolTest
+ * @modules java.net.http/jdk.internal.net.http
+  *         java.management
+ * @run main/othervm
+ *       --add-reads java.net.http=java.management
+ *       java.net.http/jdk.internal.net.http.ConnectionPoolTest
  */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/DefaultProxyDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @modules java.net.http/jdk.internal.net.http
+ * @summary Verifies that the HTTP Client, by default, uses the system-wide
+ * proxy selector, and that that selector supports the standard HTTP proxy
+ * system properties.
+ * @run testng/othervm
+ *     -Dhttp.proxyHost=foo.proxy.com
+ *     -Dhttp.proxyPort=9876
+ *     -Dhttp.nonProxyHosts=*.direct.com
+ *     -Dhttps.proxyHost=secure.proxy.com
+ *     -Dhttps.proxyPort=5443
+ *     java.net.http/jdk.internal.net.http.DefaultProxy
+ */
\ No newline at end of file
--- a/test/jdk/java/net/httpclient/whitebox/DemandTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/DemandTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -23,6 +23,6 @@
 
 /*
  * @test
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common
- * @run testng jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +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.
- */
-
-/*
- * @test
- * @bug 8151299 8164704
- * @modules jdk.incubator.httpclient
- * @run testng jdk.incubator.httpclient/jdk.incubator.http.SelectorTest
- * @run testng jdk.incubator.httpclient/jdk.incubator.http.RawChannelTest
- */
--- a/test/jdk/java/net/httpclient/whitebox/FlowTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/FlowTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -23,6 +23,6 @@
 
 /*
  * @test
- * @modules jdk.incubator.httpclient
- * @run testng jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/FramesDecoderTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -24,7 +24,9 @@
 /*
  * @test
  * @bug 8195823
- * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.frame
- * @run testng/othervm -Djdk.internal.httpclient.debug=true jdk.incubator.httpclient/jdk.incubator.http.internal.frame.FramesDecoderTest
+ * @modules java.net.http/jdk.internal.net.http.frame
+ * @run testng/othervm
+ *       -Djdk.internal.httpclient.debug=true
+ *       java.net.http/jdk.internal.net.http.frame.FramesDecoderTest
  */
 
--- a/test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/Http1HeaderParserTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,6 @@
 /*
  * @test
  * @bug 8195138
- * @modules jdk.incubator.httpclient
- * @run testng jdk.incubator.httpclient/jdk.incubator.http.Http1HeaderParserTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.Http1HeaderParserTest
  */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/MinimalFutureTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @modules java.net.http/jdk.internal.net.http.common
+ * @run testng java.net.http/jdk.internal.net.http.common.MinimalFutureTest
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/RawChannelTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8151299 8164704
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.RawChannelTest
+ */
+//-Djdk.internal.httpclient.websocket.debug=true
+
--- a/test/jdk/java/net/httpclient/whitebox/SSLEchoTubeTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/SSLEchoTubeTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -23,6 +23,8 @@
 
 /*
  * @test
- * @modules jdk.incubator.httpclient
- * @run testng/othervm -Djdk.internal.httpclient.debug=true jdk.incubator.httpclient/jdk.incubator.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	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/SSLTubeTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -23,6 +23,8 @@
 
 /*
  * @test
- * @modules jdk.incubator.httpclient
- * @run testng/othervm -Djdk.internal.httpclient.debug=true jdk.incubator.httpclient/jdk.incubator.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
  */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/SelectorTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8151299 8164704
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.SelectorTest
+ */
--- a/test/jdk/java/net/httpclient/whitebox/WrapperTestDriver.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/java/net/httpclient/whitebox/WrapperTestDriver.java	Tue Apr 17 08:54:17 2018 -0700
@@ -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
@@ -23,6 +23,6 @@
 
 /*
  * @test
- * @modules jdk.incubator.httpclient
- * @run testng jdk.incubator.httpclient/jdk.incubator.http.WrapperTest
+ * @modules java.net.http/jdk.internal.net.http
+ * @run testng java.net.http/jdk.internal.net.http.WrapperTest
  */
--- /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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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/AuthenticationFilterTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,565 @@
+/*
+ * 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;
+
+import jdk.internal.net.http.common.HttpHeadersImpl;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.annotations.AfterClass;
+
+import java.lang.ref.Reference;
+import java.net.Authenticator;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.URL;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.security.AccessController;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.net.http.HttpClient.Version;
+
+import static java.lang.String.format;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.util.stream.Collectors.joining;
+import static java.net.http.HttpClient.Version.HTTP_1_1;
+import static java.net.http.HttpClient.Version.HTTP_2;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static org.testng.Assert.*;
+
+public class AuthenticationFilterTest {
+
+    @DataProvider(name = "uris")
+    public Object[][] responses() {
+        return new Object[][] {
+                { "http://foo.com", HTTP_1_1, null },
+                { "http://foo.com", HTTP_2, null },
+                { "http://foo.com#blah", HTTP_1_1, null },
+                { "http://foo.com#blah", HTTP_2, null },
+                { "http://foo.com/x/y/z", HTTP_1_1, null },
+                { "http://foo.com/x/y/z", HTTP_2, null },
+                { "http://foo.com/x/y/z#blah", HTTP_1_1, null },
+                { "http://foo.com/x/y/z#blah", HTTP_2, null },
+                { "http://foo.com:80", HTTP_1_1, null },
+                { "http://foo.com:80", HTTP_2, null },
+                { "http://foo.com:80#blah", HTTP_1_1, null },
+                { "http://foo.com:80#blah", HTTP_2, null },
+                { "http://foo.com", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com", HTTP_2, "localhost:8080" },
+                { "http://foo.com#blah", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com#blah", HTTP_2, "localhost:8080" },
+                { "http://foo.com:8080", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com:8080", HTTP_2, "localhost:8080" },
+                { "http://foo.com:8080#blah", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com:8080#blah", HTTP_2, "localhost:8080" },
+                { "https://foo.com", HTTP_1_1, null },
+                { "https://foo.com", HTTP_2, null },
+                { "https://foo.com#blah", HTTP_1_1, null },
+                { "https://foo.com#blah", HTTP_2, null },
+                { "https://foo.com:443", HTTP_1_1, null },
+                { "https://foo.com:443", HTTP_2, null },
+                { "https://foo.com:443#blah", HTTP_1_1, null },
+                { "https://foo.com:443#blah", HTTP_2, null },
+                { "https://foo.com", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com", HTTP_2, "localhost:8080" },
+                { "https://foo.com#blah", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com#blah", HTTP_2, "localhost:8080" },
+                { "https://foo.com:8080", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com:8080", HTTP_2, "localhost:8080" },
+                { "https://foo.com:8080#blah", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com:8080#blah", HTTP_2, "localhost:8080" },
+                { "http://foo.com:80/x/y/z", HTTP_1_1, null },
+                { "http://foo.com:80/x/y/z", HTTP_2, null },
+                { "http://foo.com:80/x/y/z#blah", HTTP_1_1, null },
+                { "http://foo.com:80/x/y/z#blah", HTTP_2, null },
+                { "http://foo.com/x/y/z", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com/x/y/z", HTTP_2, "localhost:8080" },
+                { "http://foo.com/x/y/z#blah", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com/x/y/z#blah", HTTP_2, "localhost:8080" },
+                { "http://foo.com:8080/x/y/z", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com:8080/x/y/z", HTTP_2, "localhost:8080" },
+                { "http://foo.com:8080/x/y/z#blah", HTTP_1_1, "localhost:8080" },
+                { "http://foo.com:8080/x/y/z#blah", HTTP_2, "localhost:8080" },
+                { "https://foo.com/x/y/z", HTTP_1_1, null },
+                { "https://foo.com/x/y/z", HTTP_2, null },
+                { "https://foo.com/x/y/z#blah", HTTP_1_1, null },
+                { "https://foo.com/x/y/z#blah", HTTP_2, null },
+                { "https://foo.com:443/x/y/z", HTTP_1_1, null },
+                { "https://foo.com:443/x/y/z", HTTP_2, null },
+                { "https://foo.com:443/x/y/z#blah", HTTP_1_1, null },
+                { "https://foo.com:443/x/y/z#blah", HTTP_2, null },
+                { "https://foo.com/x/y/z", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com/x/y/z", HTTP_2, "localhost:8080" },
+                { "https://foo.com/x/y/z#blah", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com/x/y/z#blah", HTTP_2, "localhost:8080" },
+                { "https://foo.com:8080/x/y/z", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com:8080/x/y/z", HTTP_2, "localhost:8080" },
+                { "https://foo.com:8080/x/y/z#blah", HTTP_1_1, "localhost:8080" },
+                { "https://foo.com:8080/x/y/z#blah", HTTP_2, "localhost:8080" },
+        };
+    }
+
+    static final ConcurrentMap<String,Throwable> FAILED = new ConcurrentHashMap<>();
+
+    static boolean isNullOrEmpty(String s) {
+        return s == null || s.isEmpty();
+    }
+
+    @Test(dataProvider = "uris")
+    public void testAuthentication(String uri, Version v, String proxy) throws Exception {
+        String test = format("testAuthentication: {\"%s\", %s, \"%s\"}", uri, v, proxy);
+        try {
+            doTestAuthentication(uri, v, proxy);
+        } catch(Exception | Error x) {
+            FAILED.putIfAbsent(test, x);
+            throw x;
+        }
+    }
+
+    @AfterClass
+    public void printDiagnostic() {
+        if (FAILED.isEmpty()) {
+            out.println("All tests passed");
+            return;
+        }
+        // make sure failures don't disappear in the overflow
+        out.println("Failed tests: ");
+        FAILED.keySet().forEach(s ->
+                out.println("\t " + s.substring(s.indexOf(':')+1) + ","));
+        out.println();
+        FAILED.entrySet().forEach(e -> {
+                System.err.println("\n" + e.getKey()
+                        + " FAILED: " + e.getValue());
+                e.getValue().printStackTrace();
+        });
+    }
+
+    private void doTestAuthentication(String uri, Version v, String proxy) throws Exception {
+        int colon = proxy == null ? -1 : proxy.lastIndexOf(":");
+        ProxySelector ps = proxy == null ? NO_PROXY
+                : ProxySelector.of(InetSocketAddress.createUnresolved(
+                        proxy.substring(0, colon),
+                        Integer.parseInt(proxy.substring(colon+1))));
+        int unauthorized = proxy == null ? 401 : 407;
+
+        TestAuthenticator authenticator = new TestAuthenticator();
+
+        // Creates a HttpClientImpl
+        HttpClientBuilderImpl clientBuilder = new HttpClientBuilderImpl()
+                .authenticator(authenticator).proxy(ps);
+        HttpClientFacade facade = HttpClientImpl.create(clientBuilder);
+        HttpClientImpl client = facade.impl;
+        AuthenticationFilter filter = new AuthenticationFilter();
+
+        assertEquals(authenticator.COUNTER.get(), 0);
+
+        // Creates the first HttpRequestImpl, and call filter.request() with
+        // it. The expectation is that the filter will not add any credentials,
+        // because the cache is empty and we don't know which auth schemes the
+        // server supports yet.
+        URI reqURI = URI.create(uri);
+        HttpRequestBuilderImpl reqBuilder =
+                new HttpRequestBuilderImpl(reqURI);
+        HttpRequestImpl origReq = new HttpRequestImpl(reqBuilder);
+        HttpRequestImpl req = new HttpRequestImpl(origReq, ps);
+        MultiExchange<?> multi = new MultiExchange<Void>(origReq, req, client,
+                BodyHandlers.replacing(null),
+                null, AccessController.getContext());
+        Exchange<?> exchange = new Exchange<>(req, multi);
+        out.println("\nSimulating unauthenticated request to " + uri);
+        filter.request(req, multi);
+        assertFalse(req.getSystemHeaders().firstValue(authorization(true)).isPresent());
+        assertFalse(req.getSystemHeaders().firstValue(authorization(false)).isPresent());
+        assertEquals(authenticator.COUNTER.get(), 0);
+
+        // Creates the Response to the first request, and call filter.response
+        // with it. That response has a 401 or 407 status code.
+        // The expectation is that the filter will return a new request containing
+        // credentials, and will also cache the credentials in the multi exchange.
+        // The credentials shouldn't be put in the cache until the 200 response
+        // for that request arrives.
+        HttpHeadersImpl headers = new HttpHeadersImpl();
+        headers.addHeader(authenticate(proxy!=null),
+                "Basic realm=\"earth\"");
+        Response response = new Response(req, exchange, headers, null, unauthorized, v);
+        out.println("Simulating " + unauthorized
+                + " response from " + uri);
+        HttpRequestImpl next = filter.response(response);
+
+        out.println("Checking filter's response to "
+                + unauthorized + " from " + uri);
+        assertTrue(next != null, "next should not be null");
+        String[] up = check(reqURI, next.getSystemHeaders(), proxy);
+        assertEquals(authenticator.COUNTER.get(), 1);
+
+        // Now simulate a new successful exchange to get the credentials in the cache
+        // We first call filter.request with the request that was previously
+        // returned by the filter, then create a new Response with a 200 status
+        // code, and feed that to the filter with filter.response.
+        // At this point, the credentials will be added to the cache.
+        out.println("Simulating next request with credentials to " + uri);
+        exchange = new Exchange<>(next, multi);
+        filter.request(next, multi);
+        out.println("Checking credentials in request header after filter for " + uri);
+        check(reqURI, next.getSystemHeaders(), proxy);
+        check(next.uri(), next.getSystemHeaders(), proxy);
+        out.println("Simulating  successful response 200 from " + uri);
+        response = new Response(next, exchange, new HttpHeadersImpl(), null, 200, v);
+        next = filter.response(response);
+        assertTrue(next == null, "next should be null");
+        assertEquals(authenticator.COUNTER.get(), 1);
+
+        // Now verify that the cache is used for the next request to the same server.
+        // We're going to create a request to the same server by appending "/bar" to
+        // the original request path. Then we're going to feed that to filter.request
+        // The expectation is that filter.request will add the credentials to the
+        // request system headers, because it should find them in the cache.
+        int fragmentIndex = uri.indexOf('#');
+        String subpath = "/bar";
+        String prefix = uri;
+        String fragment =  "";
+        if (fragmentIndex > -1) {
+            prefix = uri.substring(0, fragmentIndex);
+            fragment = uri.substring(fragmentIndex);
+        }
+        URI reqURI2 = URI.create(prefix + subpath + fragment);
+        out.println("Simulating new request to " + reqURI2);
+        HttpRequestBuilderImpl reqBuilder2 =
+                new HttpRequestBuilderImpl(reqURI2);
+        HttpRequestImpl origReq2 = new HttpRequestImpl(reqBuilder2);
+        HttpRequestImpl req2 = new HttpRequestImpl(origReq2, ps);
+        MultiExchange<?> multi2 = new MultiExchange<Void>(origReq2, req2, client,
+                HttpResponse.BodyHandlers.replacing(null),
+                null, AccessController.getContext());
+        filter.request(req2, multi2);
+        out.println("Check that filter has added credentials from cache for " + reqURI2
+                + " with proxy " + req2.proxy());
+        String[] up2 = check(reqURI, req2.getSystemHeaders(), proxy);
+        assertTrue(Arrays.deepEquals(up, up2), format("%s:%s != %s:%s", up2[0], up2[1], up[0], up[1]));
+        assertEquals(authenticator.COUNTER.get(), 1);
+
+        // Now verify that the cache is not used if we send a request to a different server.
+        // We're going to append ".bar" to the original request host name, and feed that
+        // to filter.request.
+        // There are actually two cases: if we were using a proxy, then the new request
+        // should contain proxy credentials. If we were not using a proxy, then it should
+        // not contain any credentials at all.
+        URI reqURI3;
+        if (isNullOrEmpty(reqURI.getPath())
+                && isNullOrEmpty(reqURI.getFragment())
+                && reqURI.getPort() == -1) {
+            reqURI3 = URI.create(uri + ".bar");
+        } else {
+            reqURI3 = new URI(reqURI.getScheme(), reqURI.getUserInfo(),
+                         reqURI.getHost() + ".bar", reqURI.getPort(),
+                              reqURI.getPath(), reqURI.getQuery(),
+                              reqURI.getFragment());
+        }
+        out.println("Simulating new request to " + reqURI3);
+        HttpRequestBuilderImpl reqBuilder3 =
+                new HttpRequestBuilderImpl(reqURI3);
+        HttpRequestImpl origReq3 = new HttpRequestImpl(reqBuilder3);
+        HttpRequestImpl req3 = new HttpRequestImpl(origReq3, ps);
+        MultiExchange<?> multi3 = new MultiExchange<Void>(origReq3, req3, client,
+                HttpResponse.BodyHandlers.replacing(null),
+                null, AccessController.getContext());
+        filter.request(req3, multi3);
+        if (proxy == null) {
+            out.println("Check that filter has not added proxy credentials from cache for " + reqURI3);
+            assert !req3.getSystemHeaders().firstValue(authorization(true)).isPresent()
+                    : format("Unexpected proxy credentials found: %s",
+                    java.util.stream.Stream.of(getAuthorization(req3.getSystemHeaders(), true))
+                            .collect(joining(":")));
+            assertFalse(req3.getSystemHeaders().firstValue(authorization(true)).isPresent());
+        } else {
+            out.println("Check that filter has added proxy credentials from cache for " + reqURI3);
+            String[] up3 = check(reqURI, req3.getSystemHeaders(), proxy);
+            assertTrue(Arrays.deepEquals(up, up3), format("%s:%s != %s:%s", up3[0], up3[1], up[0], up[1]));
+        }
+        out.println("Check that filter has not added server credentials from cache for " + reqURI3);
+        assert !req3.getSystemHeaders().firstValue(authorization(false)).isPresent()
+                : format("Unexpected server credentials found: %s",
+                java.util.stream.Stream.of(getAuthorization(req3.getSystemHeaders(), false))
+                        .collect(joining(":")));
+        assertFalse(req3.getSystemHeaders().firstValue(authorization(false)).isPresent());
+        assertEquals(authenticator.COUNTER.get(), 1);
+
+        // Now we will verify that credentials for proxies are not used for servers and
+        // conversely.
+        // If we were using a proxy, we're now going to send a request to the proxy host,
+        // without using a proxy, and verify that filter.request neither add proxy credential
+        // or server credential to that host.
+        // I we were not using a proxy, we're going to send a request to the original
+        // server, using a proxy whose address matches the original server.
+        // We expect that the cache will add server credentials, but not proxy credentials.
+        int port = reqURI.getPort();
+        port = port == -1 ? defaultPort(reqURI.getScheme()) : port;
+        ProxySelector fakeProxy = proxy == null
+                ? ProxySelector.of(InetSocketAddress.createUnresolved(
+                reqURI.getHost(), port))
+                : NO_PROXY;
+        URI reqURI4 = proxy == null ? reqURI : new URI("http", null, req.proxy().getHostName(),
+                    req.proxy().getPort(), "/", null, null);
+        HttpRequestBuilderImpl reqBuilder4 = new HttpRequestBuilderImpl(reqURI4);
+        HttpRequestImpl origReq4 = new HttpRequestImpl(reqBuilder4);
+        HttpRequestImpl req4 = new HttpRequestImpl(origReq4, fakeProxy);
+        MultiExchange<?> multi4 = new MultiExchange<Void>(origReq4, req4, client,
+                HttpResponse.BodyHandlers.replacing(null), null,
+                AccessController.getContext());
+        out.println("Simulating new request to " + reqURI4 + " with a proxy " + req4.proxy());
+        assertTrue((req4.proxy() == null) == (proxy != null),
+                "(req4.proxy() == null) == (proxy != null) should be true");
+        filter.request(req4, multi4);
+        out.println("Check that filter has not added proxy credentials from cache for "
+                + reqURI4 + " (proxy: " + req4.proxy()  + ")");
+        assert !req4.getSystemHeaders().firstValue(authorization(true)).isPresent()
+                : format("Unexpected proxy credentials found: %s",
+                java.util.stream.Stream.of(getAuthorization(req4.getSystemHeaders(), true))
+                        .collect(joining(":")));
+        assertFalse(req4.getSystemHeaders().firstValue(authorization(true)).isPresent());
+        if (proxy != null) {
+            out.println("Check that filter has not added server credentials from cache for "
+                    + reqURI4 + " (proxy: " + req4.proxy()  + ")");
+            assert !req4.getSystemHeaders().firstValue(authorization(false)).isPresent()
+                    : format("Unexpected server credentials found: %s",
+                    java.util.stream.Stream.of(getAuthorization(req4.getSystemHeaders(), false))
+                            .collect(joining(":")));
+            assertFalse(req4.getSystemHeaders().firstValue(authorization(false)).isPresent());
+        } else {
+            out.println("Check that filter has added server credentials from cache for "
+                    + reqURI4 + " (proxy: " + req4.proxy()  + ")");
+            String[] up4 = check(reqURI, req4.getSystemHeaders(), proxy);
+            assertTrue(Arrays.deepEquals(up, up4),  format("%s:%s != %s:%s", up4[0], up4[1], up[0], up[1]));
+        }
+        assertEquals(authenticator.COUNTER.get(), 1);
+
+        if (proxy != null) {
+            // Now if we were using a proxy, we're going to send the same request than
+            // the original request, but without a proxy, and verify that this time
+            // the cache does not add any server or proxy credential. It should not
+            // add server credential because it should not have them (we only used
+            // proxy authentication so far) and it should not add proxy credentials
+            // because the request has no proxy.
+            HttpRequestBuilderImpl reqBuilder5 = new HttpRequestBuilderImpl(reqURI);
+            HttpRequestImpl origReq5 = new HttpRequestImpl(reqBuilder5);
+            HttpRequestImpl req5 = new HttpRequestImpl(origReq5, NO_PROXY);
+            MultiExchange<?> multi5 = new MultiExchange<Void>(origReq5, req5, client,
+                    HttpResponse.BodyHandlers.replacing(null), null,
+                    AccessController.getContext());
+            out.println("Simulating new request to " + reqURI + " with a proxy " + req5.proxy());
+            assertTrue(req5.proxy() == null, "req5.proxy() should be null");
+            Exchange<?> exchange5 = new Exchange<>(req5, multi5);
+            filter.request(req5, multi5);
+            out.println("Check that filter has not added server credentials from cache for "
+                    + reqURI + " (proxy: " + req5.proxy()  + ")");
+            assert !req5.getSystemHeaders().firstValue(authorization(false)).isPresent()
+                    : format("Unexpected server credentials found: %s",
+                    java.util.stream.Stream.of(getAuthorization(req5.getSystemHeaders(), false))
+                            .collect(joining(":")));
+            assertFalse(req5.getSystemHeaders().firstValue(authorization(false)).isPresent());
+            out.println("Check that filter has not added proxy credentials from cache for "
+                    + reqURI + " (proxy: " + req5.proxy()  + ")");
+            assert !req5.getSystemHeaders().firstValue(authorization(true)).isPresent()
+                    : format("Unexpected proxy credentials found: %s",
+                    java.util.stream.Stream.of(getAuthorization(req5.getSystemHeaders(), true))
+                            .collect(joining(":")));
+            assertFalse(req5.getSystemHeaders().firstValue(authorization(true)).isPresent());
+            assertEquals(authenticator.COUNTER.get(), 1);
+
+            // Now simulate a 401 response from the server
+            HttpHeadersImpl headers5 = new HttpHeadersImpl();
+            headers5.addHeader(authenticate(false),
+                    "Basic realm=\"earth\"");
+            unauthorized = 401;
+            Response response5 = new Response(req5, exchange5, headers5, null, unauthorized, v);
+            out.println("Simulating " + unauthorized
+                    + " response from " + uri);
+            HttpRequestImpl next5 = filter.response(response5);
+            assertEquals(authenticator.COUNTER.get(), 2);
+
+            out.println("Checking filter's response to "
+                    + unauthorized + " from " + uri);
+            assertTrue(next5 != null, "next5 should not be null");
+            String[] up5 = check(reqURI, next5.getSystemHeaders(), null);
+
+            // now simulate a 200 response from the server
+            exchange5 = new Exchange<>(next5, multi5);
+            filter.request(next5, multi5);
+            response5 = new Response(next5, exchange5, new HttpHeadersImpl(), null, 200, v);
+            filter.response(response5);
+            assertEquals(authenticator.COUNTER.get(), 2);
+
+            // now send the request again, with proxy this time, and it should have both
+            // server auth and proxy auth
+            HttpRequestBuilderImpl reqBuilder6 = new HttpRequestBuilderImpl(reqURI);
+            HttpRequestImpl origReq6 = new HttpRequestImpl(reqBuilder6);
+            HttpRequestImpl req6 = new HttpRequestImpl(origReq6, ps);
+            MultiExchange<?> multi6 = new MultiExchange<Void>(origReq6, req6, client,
+                    HttpResponse.BodyHandlers.replacing(null), null,
+                    AccessController.getContext());
+            out.println("Simulating new request to " + reqURI + " with a proxy " + req6.proxy());
+            assertTrue(req6.proxy() != null, "req6.proxy() should not be null");
+            Exchange<?> exchange6 = new Exchange<>(req6, multi6);
+            filter.request(req6, multi6);
+            out.println("Check that filter has added server credentials from cache for "
+                    + reqURI + " (proxy: " + req6.proxy()  + ")");
+            check(reqURI, req6.getSystemHeaders(), null);
+            out.println("Check that filter has added proxy credentials from cache for "
+                    + reqURI + " (proxy: " + req6.proxy()  + ")");
+            String[] up6 = check(reqURI, req6.getSystemHeaders(), proxy);
+            assertTrue(Arrays.deepEquals(up, up6), format("%s:%s != %s:%s", up6[0], up6[1], up[0], up[1]));
+            assertEquals(authenticator.COUNTER.get(), 2);
+        }
+
+        if (proxy == null && uri.contains("x/y/z")) {
+            URI reqURI7 = URI.create(prefix + "/../../w/z" + fragment);
+            assertTrue(reqURI7.getPath().contains("../../"));
+            HttpRequestBuilderImpl reqBuilder7 = new HttpRequestBuilderImpl(reqURI7);
+            HttpRequestImpl origReq7 = new HttpRequestImpl(reqBuilder7);
+            HttpRequestImpl req7 = new HttpRequestImpl(origReq7, ps);
+            MultiExchange<?> multi7 = new MultiExchange<Void>(origReq7, req7, client,
+                    HttpResponse.BodyHandlers.replacing(null), null,
+                    AccessController.getContext());
+            out.println("Simulating new request to " + reqURI7 + " with a proxy " + req7.proxy());
+            assertTrue(req7.proxy() == null, "req7.proxy() should be null");
+            Exchange<?> exchange7 = new Exchange<>(req7, multi7);
+            filter.request(req7, multi7);
+            out.println("Check that filter has not added server credentials from cache for "
+                    + reqURI7 + " (proxy: " + req7.proxy()  + ") [resolved uri: "
+                    + reqURI7.resolve(".") + " should not match " + reqURI.resolve(".") + "]");
+            assert !req7.getSystemHeaders().firstValue(authorization(false)).isPresent()
+                    : format("Unexpected server credentials found: %s",
+                    java.util.stream.Stream.of(getAuthorization(req7.getSystemHeaders(), false))
+                            .collect(joining(":")));
+            assertFalse(req7.getSystemHeaders().firstValue(authorization(false)).isPresent());
+            out.println("Check that filter has not added proxy credentials from cache for "
+                    + reqURI7 + " (proxy: " + req7.proxy()  + ")");
+            assert !req7.getSystemHeaders().firstValue(authorization(true)).isPresent()
+                    : format("Unexpected proxy credentials found: %s",
+                    java.util.stream.Stream.of(getAuthorization(req7.getSystemHeaders(), true))
+                            .collect(joining(":")));
+            assertFalse(req7.getSystemHeaders().firstValue(authorization(true)).isPresent());
+            assertEquals(authenticator.COUNTER.get(), 1);
+
+        }
+
+        Reference.reachabilityFence(facade);
+    }
+
+    static int defaultPort(String protocol) {
+        if ("http".equalsIgnoreCase(protocol)) return 80;
+        if ("https".equalsIgnoreCase(protocol)) return 443;
+        return -1;
+    }
+
+    static String authenticate(boolean proxy) {
+        return proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
+    }
+    static String authorization(boolean proxy) {
+        return proxy ? "Proxy-Authorization" : "Authorization";
+    }
+
+    static String[] getAuthorization(HttpHeaders headers, boolean proxy) {
+        String auth = headers.firstValue(authorization(proxy)).get().substring(6);
+        String pw = new String(Base64.getDecoder().decode(auth), US_ASCII);
+        String[] up = pw.split(":");
+        up[1] = new String(Base64.getDecoder().decode(up[1]), US_ASCII);
+        return up;
+    }
+
+    static Authenticator.RequestorType requestorType(boolean proxy) {
+        return proxy ? Authenticator.RequestorType.PROXY
+                     : Authenticator.RequestorType.SERVER;
+    }
+
+    static String[] check(URI reqURI, HttpHeaders headers, String proxy) throws Exception {
+        out.println("Next request headers: " + headers.map());
+        String[] up = getAuthorization(headers, proxy != null);
+        String u = up[0];
+        String p = up[1];
+        out.println("user:password: " + u + ":" + p);
+        String protocol = proxy != null ? "http" : reqURI.getScheme();
+        String expectedUser = "u." + protocol;
+        assertEquals(u, expectedUser);
+        String host = proxy == null ? reqURI.getHost() :
+                proxy.substring(0, proxy.lastIndexOf(':'));
+        int port = proxy == null ? reqURI.getPort()
+                : Integer.parseInt(proxy.substring(proxy.lastIndexOf(':')+1));
+        String expectedPw = concat(requestorType(proxy!=null),
+                "basic", protocol, host,
+                port, "earth", reqURI.toURL());
+        assertEquals(p, expectedPw);
+        return new String[] {u, p};
+    }
+
+    static String concat(Authenticator.RequestorType reqType,
+                           String authScheme,
+                           String requestingProtocol,
+                           String requestingHost,
+                           int requestingPort,
+                           String realm,
+                           URL requestingURL) {
+        return new StringBuilder()
+                .append(reqType).append(":")
+                .append(authScheme).append(":")
+                .append(String.valueOf(realm))
+                .append("[")
+                .append(requestingProtocol).append(':')
+                .append(requestingHost).append(':')
+                .append(requestingPort).append("]")
+                .append("/").append(String.valueOf(requestingURL))
+                .toString();
+    }
+
+    static class TestAuthenticator extends Authenticator {
+        final AtomicLong COUNTER = new AtomicLong();
+        @Override
+        public PasswordAuthentication getPasswordAuthentication() {
+            COUNTER.incrementAndGet();
+            return new PasswordAuthentication("u."+getRequestingProtocol(),
+                    Base64.getEncoder().encodeToString(concat().getBytes(US_ASCII))
+                            .toCharArray());
+        }
+
+        String concat() {
+            return AuthenticationFilterTest.concat(
+                    getRequestorType(),
+                    getRequestingScheme(),
+                    getRequestingProtocol(),
+                    getRequestingHost(),
+                    getRequestingPort(),
+                    getRequestingPrompt(),
+                    getRequestingURL());
+        }
+
+    }
+}
--- /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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,246 @@
+/*
+ * 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
+        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
+        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/DefaultProxy.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.util.List;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+
+public class DefaultProxy {
+
+    static ProxySelector getProxySelector() {
+        return (((HttpClientFacade)HttpClient.newHttpClient()).impl).proxySelector();
+    }
+
+    @Test
+    public void testDefault() {
+        ProxySelector ps = getProxySelector();
+        System.out.println("HttpClientImpl proxySelector:" + ps);
+        assertEquals(ps, ProxySelector.getDefault());
+    }
+
+    // From the test driver
+    // -Dhttp.proxyHost=foo.proxy.com
+    // -Dhttp.proxyPort=9876
+    // -Dhttp.nonProxyHosts=*.direct.com
+    @Test
+    public void testHttpProxyProps() {
+        ProxySelector ps = getProxySelector();
+
+        URI uri = URI.create("http://foo.com/example.html");
+        List<Proxy> plist = ps.select(uri);
+        System.out.println("proxy list for " + uri + " : " + plist);
+        assertEquals(plist.size(), 1);
+        Proxy proxy = plist.get(0);
+        assertEquals(proxy.type(), Proxy.Type.HTTP);
+        InetSocketAddress expectedAddr =
+                InetSocketAddress.createUnresolved("foo.proxy.com", 9876);
+        assertEquals(proxy.address(), expectedAddr);
+
+        // nonProxyHosts
+        uri = URI.create("http://foo.direct.com/example.html");
+        plist = ps.select(uri);
+        System.out.println("proxy list for " + uri + " : " + plist);
+        assertEquals(plist.size(), 1);
+        proxy = plist.get(0);
+        assertEquals(proxy.type(), Proxy.Type.DIRECT);
+        assertEquals(proxy.address(), null);
+    }
+
+    // From the test driver
+    // -Dhttp.proxyHost=secure.proxy.com
+    // -Dhttp.proxyPort=5443
+    @Test
+    public void testHttpsProxyProps() {
+        ProxySelector ps = getProxySelector();
+
+        URI uri = URI.create("https://foo.com/example.html");
+        List<Proxy> plist = ps.select(uri);
+        System.out.println("proxy list for " + uri + " : " + plist);
+        assertEquals(plist.size(), 1);
+        Proxy proxy = plist.get(0);
+        assertEquals(proxy.type(), Proxy.Type.HTTP);
+        InetSocketAddress expectedAddr =
+                InetSocketAddress.createUnresolved("secure.proxy.com", 5443);
+        assertEquals(proxy.address(), expectedAddr);
+
+        // nonProxyHosts
+        uri = URI.create("https://foo.direct.com/example.html");
+        plist = ps.select(uri);
+        System.out.println("proxy list for " + uri + " : " + plist);
+        assertEquals(plist.size(), 1);
+        proxy = plist.get(0);
+        assertEquals(proxy.type(), Proxy.Type.DIRECT);
+        assertEquals(proxy.address(), 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/FlowTest.java	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,551 @@
+/*
+ * 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.InetAddress;
+import java.net.InetSocketAddress;
+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();
+            serv.setReuseAddress(false);
+            serv.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+            SSLParameters params = serv.getSSLParameters();
+            params.setApplicationProtocols(new String[]{"proto2"});
+            serv.setSSLParameters(params);
+
+
+            int serverPort = serv.getLocalPort();
+            clientSock = new Socket("localhost", 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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,361 @@
+/*
+ * 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.InetAddress;
+import java.net.InetSocketAddress;
+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.CompletableFuture;
+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.BodyHandlers.discarding;
+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();
+    private CompletableFuture<Void> outputCompleted = new CompletableFuture<>();
+    private CompletableFuture<Void> inputCompleted = new CompletableFuture<>();
+
+    /*
+     * 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()) {
+            server.setReuseAddress(false);
+            server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 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();
+                            outputCompleted.complete(null);
+                        } catch (IOException e) {
+                            outputCompleted.completeExceptionally(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) {
+                            inputCompleted.completeExceptionally(e);
+                            e.printStackTrace();
+                        }
+                        if (read == null) {
+                            print("OP_READ EOF");
+                            inputCompleted.complete(null);
+                            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());
+                }
+            });
+            CompletableFuture.allOf(outputCompleted,inputCompleted)
+                    .whenComplete((r,t) -> {
+                        try {
+                            print("closing channel");
+                            chan.close();
+                        } catch (IOException x) {
+                            x.printStackTrace();
+                        }
+                    });
+            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://localhost:" + 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, discarding());
+            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);
+                        s.shutdownInput();
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                });
+
+                Thread writer = new Thread(() -> {
+                    try {
+                        long n = writeSlowly(os);
+                        print("Server written %s bytes", 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();
+            serverWritten.addAndGet(first.length);
+
+            // wait until initial bytes were read
+            print("Server wrote total %d: awaiting initialReadStall", total);
+            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;
+            serverWritten.addAndGet(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
+            print("Server wrote total %d: awaiting readStall", total);
+            readStall.await();
+            print("readStall unblocked, writing 32k");
+            for (int i = 0; i < 32; i++) {
+                byte[] b = byteArrayOfSize(1024);
+                os.write(b);
+                os.flush();
+                serverWritten.addAndGet(b.length);
+                total += b.length;
+                print("Server wrote total %d", total);
+                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();
+            print("writingStall unblocked, start reading");
+            long overall = 0;
+            byte[] array = new byte[1024];
+            for (int n = 0; n != -1; n = is.read(array)) {
+                serverRead.addAndGet(n);
+                TimeUnit.MILLISECONDS.sleep(1);
+                overall += n;
+                print("Server read total: %d", overall);
+            }
+            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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,279 @@
+/*
+ * 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.InetAddress;
+import java.net.InetSocketAddress;
+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();
+            serv.setReuseAddress(false);
+            serv.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+            SSLParameters params = serv.getSSLParameters();
+            params.setApplicationProtocols(new String[]{"proto2"});
+            serv.setSSLParameters(params);
+
+
+            int serverPort = serv.getLocalPort();
+            clientSock = new Socket("localhost", 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	Tue Apr 17 08:54:17 2018 -0700
@@ -0,0 +1,226 @@
+/*
+ * 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.BodyHandlers.discarding;
+
+/**
+ * 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()) {
+            server.setReuseAddress(false);
+            server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 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://localhost:" + 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, discarding());
+        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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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	Tue Apr 17 08:54:17 2018 -0700
@@ -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);
+    }
+}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/AbstractRandomTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/AbstractSSLTubeTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,320 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.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.Random;
-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/jdk.incubator.httpclient/jdk/incubator/http/ConnectionPoolTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,248 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.InetSocketAddress;
-import java.net.ProxySelector;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-import java.util.Optional;
-import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.stream.IntStream;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLParameters;
-import jdk.incubator.http.internal.common.FlowTube;
-
-/**
- * @summary Verifies that the ConnectionPool correctly handle
- *          connection deadlines and purges the right connections
- *          from the cache.
- * @bug 8187044 8187111
- * @author danielfuchs
- */
-public class ConnectionPoolTest {
-
-    static long getActiveCleaners() throws ClassNotFoundException {
-        // ConnectionPool.ACTIVE_CLEANER_COUNTER.get()
-        // ConnectionPoolTest.class.getModule().addReads(
-        //      Class.forName("java.lang.management.ManagementFactory").getModule());
-        return java.util.stream.Stream.of(ManagementFactory.getThreadMXBean()
-                .dumpAllThreads(false, false))
-              .filter(t -> t.getThreadName().startsWith("HTTP-Cache-cleaner"))
-              .count();
-    }
-
-    public static void main(String[] args) throws Exception {
-        testCacheCleaners();
-    }
-
-    public static void testCacheCleaners() throws Exception {
-        ConnectionPool pool = new ConnectionPool(666);
-        HttpClient client = new HttpClientStub(pool);
-        InetSocketAddress proxy = InetSocketAddress.createUnresolved("bar", 80);
-        System.out.println("Adding 10 connections to pool");
-        Random random = new Random();
-
-        final int count = 20;
-        Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
-        int[] keepAlives = new int[count];
-        HttpConnectionStub[] connections = new HttpConnectionStub[count];
-        long purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
-        long expected = 0;
-        if (purge != expected) {
-            throw new RuntimeException("Bad purge delay: " + purge
-                                        + ", expected " + expected);
-        }
-        expected = Long.MAX_VALUE;
-        for (int i=0; i<count; i++) {
-            InetSocketAddress addr = InetSocketAddress.createUnresolved("foo"+i, 80);
-            keepAlives[i] = random.nextInt(10) * 10  + 10;
-            connections[i] = new HttpConnectionStub(client, addr, proxy, true);
-            System.out.println("Adding connection: " + now
-                                + " keepAlive: " + keepAlives[i]
-                                + " /" + connections[i]);
-            pool.returnToPool(connections[i], now, keepAlives[i]);
-            expected = Math.min(expected, keepAlives[i] * 1000);
-            purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
-            if (purge != expected) {
-                throw new RuntimeException("Bad purge delay: " + purge
-                                        + ", expected " + expected);
-            }
-        }
-        int min = IntStream.of(keepAlives).min().getAsInt();
-        int max = IntStream.of(keepAlives).max().getAsInt();
-        int mean = (min + max)/2;
-        System.out.println("min=" + min + ", max=" + max + ", mean=" + mean);
-        purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(now);
-        System.out.println("first purge would be in " + purge + " ms");
-        if (Math.abs(purge/1000 - min) > 0) {
-            throw new RuntimeException("expected " + min + " got " + purge/1000);
-        }
-        long opened = java.util.stream.Stream.of(connections)
-                     .filter(HttpConnectionStub::connected).count();
-        if (opened != count) {
-            throw new RuntimeException("Opened: expected "
-                                       + count + " got " + opened);
-        }
-        purge = mean * 1000;
-        System.out.println("start purging at " + purge + " ms");
-        Instant next = now;
-        do {
-           System.out.println("next purge is in " + purge + " ms");
-           next = next.plus(purge, ChronoUnit.MILLIS);
-           purge = pool.purgeExpiredConnectionsAndReturnNextDeadline(next);
-           long k = now.until(next, ChronoUnit.SECONDS);
-           System.out.println("now is " + k + "s from start");
-           for (int i=0; i<count; i++) {
-               if (connections[i].connected() != (k < keepAlives[i])) {
-                   throw new RuntimeException("Bad connection state for "
-                             + i
-                             + "\n\t connected=" + connections[i].connected()
-                             + "\n\t keepAlive=" + keepAlives[i]
-                             + "\n\t elapsed=" + k);
-               }
-           }
-        } while (purge > 0);
-        opened = java.util.stream.Stream.of(connections)
-                     .filter(HttpConnectionStub::connected).count();
-        if (opened != 0) {
-           throw new RuntimeException("Closed: expected "
-                                       + count + " got "
-                                       + (count-opened));
-        }
-    }
-
-    static <T> T error() {
-        throw new InternalError("Should not reach here: wrong test assumptions!");
-    }
-
-    static class FlowTubeStub implements FlowTube {
-        final HttpConnectionStub conn;
-        FlowTubeStub(HttpConnectionStub conn) { this.conn = conn; }
-        @Override
-        public void onSubscribe(Flow.Subscription subscription) { }
-        @Override public void onError(Throwable error) { error(); }
-        @Override public void onComplete() { error(); }
-        @Override public void onNext(List<ByteBuffer> item) { error();}
-        @Override
-        public void subscribe(Flow.Subscriber<? super List<ByteBuffer>> subscriber) {
-        }
-        @Override public boolean isFinished() { return conn.closed; }
-    }
-
-    // Emulates an HttpConnection that has a strong reference to its HttpClient.
-    static class HttpConnectionStub extends HttpConnection {
-
-        public HttpConnectionStub(HttpClient client,
-                InetSocketAddress address,
-                InetSocketAddress proxy,
-                boolean secured) {
-            super(address, null);
-            this.key = ConnectionPool.cacheKey(address, proxy);
-            this.address = address;
-            this.proxy = proxy;
-            this.secured = secured;
-            this.client = client;
-            this.flow = new FlowTubeStub(this);
-        }
-
-        final InetSocketAddress proxy;
-        final InetSocketAddress address;
-        final boolean secured;
-        final ConnectionPool.CacheKey key;
-        final HttpClient client;
-        final FlowTubeStub flow;
-        volatile boolean closed;
-
-        // All these return something
-        @Override boolean connected() {return !closed;}
-        @Override boolean isSecure() {return secured;}
-        @Override boolean isProxied() {return proxy!=null;}
-        @Override ConnectionPool.CacheKey cacheKey() {return key;}
-        @Override void shutdownInput() throws IOException {}
-        @Override void shutdownOutput() throws IOException {}
-        @Override
-        public void close() {
-            closed=true;
-            System.out.println("closed: " + this);
-        }
-        @Override
-        public String toString() {
-            return "HttpConnectionStub: " + address + " proxy: " + proxy;
-        }
-
-        // All these throw errors
-        @Override public HttpPublisher publisher() {return error();}
-        @Override public CompletableFuture<Void> connectAsync() {return error();}
-        @Override SocketChannel channel() {return error();}
-        @Override
-        HttpConnection.DetachedConnectionChannel detachChannel() {
-            return error();
-        }
-        @Override
-        FlowTube getConnectionFlow() {return flow;}
-    }
-    // Emulates an HttpClient that has a strong reference to its connection pool.
-    static class HttpClientStub extends HttpClient {
-        public HttpClientStub(ConnectionPool pool) {
-            this.pool = pool;
-        }
-        final ConnectionPool pool;
-        @Override public Optional<CookieHandler> cookieHandler() {return error();}
-        @Override public HttpClient.Redirect followRedirects() {return error();}
-        @Override public Optional<ProxySelector> proxy() {return error();}
-        @Override public SSLContext sslContext() {return error();}
-        @Override public SSLParameters sslParameters() {return error();}
-        @Override public Optional<Authenticator> authenticator() {return error();}
-        @Override public HttpClient.Version version() {return HttpClient.Version.HTTP_1_1;}
-        @Override public Optional<Executor> executor() {return error();}
-        @Override
-        public <T> HttpResponse<T> send(HttpRequest req,
-                HttpResponse.BodyHandler<T> responseBodyHandler)
-                throws IOException, InterruptedException {
-            return error();
-        }
-        @Override
-        public <T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req,
-                HttpResponse.BodyHandler<T> responseBodyHandler) {
-            return error();
-        }
-        @Override
-        public <U, T> CompletableFuture<U> sendAsync(HttpRequest req,
-                HttpResponse.MultiSubscriber<U, T> multiSubscriber) {
-            return error();
-        }
-    }
-
-}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/FlowTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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.incubator.http.internal.common.Utils;
-import org.testng.annotations.Test;
-import jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/Http1HeaderParserTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,379 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.io.ByteArrayInputStream;
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.IntStream;
-import sun.net.www.MessageHeader;
-import org.testng.annotations.Test;
-import org.testng.annotations.DataProvider;
-import static java.lang.System.out;
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.util.stream.Collectors.toList;
-import static org.testng.Assert.*;
-
-// Mostly verifies the "new" Http1HeaderParser returns the same results as the
-// tried and tested sun.net.www.MessageHeader.
-
-public class Http1HeaderParserTest {
-
-    @DataProvider(name = "responses")
-    public Object[][] responses() {
-        List<String> responses = new ArrayList<>();
-
-        String[] basic =
-            { "HTTP/1.1 200 OK\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
-              "Server: Apache/1.3.14 (Unix)\r\n" +
-              "Connection: close\r\n" +
-              "Content-Type: text/html; charset=iso-8859-1\r\n" +
-              "Content-Length: 10\r\n\r\n" +
-              "123456789",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 9\r\n" +
-              "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
-              "XXXXX",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length:   9\r\n" +
-              "Content-Type:   text/html; charset=UTF-8\r\n\r\n" +   // more than one SP after ':'
-              "XXXXX",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length:\t10\r\n" +
-              "Content-Type:\ttext/html; charset=UTF-8\r\n\r\n" +   // HT separator
-              "XXXXX",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length:\t\t10\r\n" +
-              "Content-Type:\t\ttext/html; charset=UTF-8\r\n\r\n" +   // more than one HT after ':'
-              "XXXXX",
-
-              "HTTP/1.1 407 Proxy Authorization Required\r\n" +
-              "Proxy-Authenticate: Basic realm=\"a fake realm\"\r\n\r\n",
-
-              "HTTP/1.1 401 Unauthorized\r\n" +
-              "WWW-Authenticate: Digest realm=\"wally land\" domain=/ " +
-              "nonce=\"2B7F3A2B\" qop=\"auth\"\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n\r\n",      // no value
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n\r\n" +     // no value, with response body
-              "Some Response Body",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n" +    // no value, followed by another header
-              "Content-Length: 10\r\n\r\n" +
-              "Some Response Body",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n" +    // no value, followed by another header, with response body
-              "Content-Length: 10\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo: chegar\r\n" +
-              "X-Foo: dfuchs\r\n" +  // same header appears multiple times
-              "Content-Length: 0\r\n" +
-              "X-Foo: michaelm\r\n" +
-              "X-Foo: prappo\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "X-Foo:\r\n" +    // no value, same header appears multiple times
-              "X-Foo: dfuchs\r\n" +
-              "Content-Length: 0\r\n" +
-              "X-Foo: michaelm\r\n" +
-              "X-Foo: prappo\r\n\r\n",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Accept-Ranges: bytes\r\n" +
-              "Cache-control: max-age=0, no-cache=\"set-cookie\"\r\n" +
-              "Content-Length: 132868\r\n" +
-              "Content-Type: text/html; charset=UTF-8\r\n" +
-              "Date: Sun, 05 Nov 2017 22:24:03 GMT\r\n" +
-              "Server: Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips Communique/4.2.2\r\n" +
-              "Set-Cookie: AWSELB=AF7927F5100F4202119876ED2436B5005EE;PATH=/;MAX-AGE=900\r\n" +
-              "Vary: Host,Accept-Encoding,User-Agent\r\n" +
-              "X-Mod-Pagespeed: 1.12.34.2-0\r\n" +
-              "Connection: keep-alive\r\n\r\n"
-            };
-        Arrays.stream(basic).forEach(responses::add);
-
-        String[] foldingTemplate =
-           {  "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 9\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r'
-              " charset=UTF-8\r\n" +                // one preceding SP
-              "Connection: close\r\n\r\n" +
-              "XXYYZZAABBCCDDEE",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 19\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "   charset=UTF-8\r\n" +              // more than one preceding SP
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGG",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 999\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "\tcharset=UTF-8\r\n" +               // one preceding HT
-              "Connection: close\r\n\r\n" +
-              "XXYYZZAABBCCDDEE",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 54\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "\t\t\tcharset=UTF-8\r\n" +           // more than one preceding HT
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGG",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: -1\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              "\t \t \tcharset=UTF-8\r\n" +         // mix of preceding HT and SP
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGGHH",
-
-              "HTTP/1.1 200 OK\r\n" +
-              "Content-Length: 65\r\n" +
-              "Content-Type: text/html;$NEWLINE" +  // folding field-value with '\n'|'\r
-              " \t \t charset=UTF-8\r\n" +          // mix of preceding SP and HT
-              "Connection: keep-alive\r\n\r\n" +
-              "XXYYZZAABBCCDDEEFFGGHHII",
-
-              "HTTP/1.1 401 Unauthorized\r\n" +
-              "WWW-Authenticate: Digest realm=\"wally land\","
-                      +"$NEWLINE    domain=/,"
-                      +"$NEWLINE nonce=\"2B7F3A2B\","
-                      +"$NEWLINE\tqop=\"auth\"\r\n\r\n",
-
-           };
-        for (String newLineChar : new String[] { "\n", "\r", "\r\n" }) {
-            for (String template : foldingTemplate)
-                responses.add(template.replace("$NEWLINE", newLineChar));
-        }
-
-        String[] bad = // much of this is to retain parity with legacy MessageHeaders
-           { "HTTP/1.1 200 OK\r\n" +
-             "Connection:\r\n\r\n",   // empty value, no body
-
-             "HTTP/1.1 200 OK\r\n" +
-             "Connection:\r\n\r\n" +  // empty value, with body
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no header\r\n\r\n",  // no/empty header-name, no body, no following header
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no; header\r\n" +  // no/empty header-name, no body, following header
-             "Content-Length: 65\r\n\r\n",
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no header\r\n" +  // no/empty header-name
-             "Content-Length: 65\r\n\r\n" +
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             ": no header\r\n\r\n" +  // no/empty header-name, followed by header
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             "Conte\r" +
-             " nt-Length: 9\r\n" +    // fold/bad header name ???
-             "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
-             "XXXXX",
-
-             "HTTP/1.1 200 OK\r\n" +
-             "Conte\r" +
-             "nt-Length: 9\r\n" +    // fold/bad header name ??? without preceding space
-             "Content-Type: text/html; charset=UTF-8\r\n\r\n" +
-             "XXXXXYYZZ",
-
-             "HTTP/1.0 404 Not Found\r\n" +
-             "header-without-colon\r\n\r\n",
-
-             "HTTP/1.0 404 Not Found\r\n" +
-             "header-without-colon\r\n\r\n" +
-             "SOMEBODY",
-
-           };
-        Arrays.stream(bad).forEach(responses::add);
-
-        return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
-    }
-
-    @Test(dataProvider = "responses")
-    public void verifyHeaders(String respString) throws Exception {
-        byte[] bytes = respString.getBytes(US_ASCII);
-        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
-        MessageHeader m = new MessageHeader(bais);
-        Map<String,List<String>> messageHeaderMap = m.getHeaders();
-        int available = bais.available();
-
-        Http1HeaderParser decoder = new Http1HeaderParser();
-        ByteBuffer b = ByteBuffer.wrap(bytes);
-        decoder.parse(b);
-        Map<String,List<String>> decoderMap1 = decoder.headers().map();
-        assertEquals(available, b.remaining(),
-                     "stream available not equal to remaining");
-
-        // assert status-line
-        String statusLine1 = messageHeaderMap.get(null).get(0);
-        String statusLine2 = decoder.statusLine();
-        if (statusLine1.startsWith("HTTP")) {// skip the case where MH's messes up the status-line
-            assertEquals(statusLine1, statusLine2, "Status-line not equal");
-        } else {
-            assertTrue(statusLine2.startsWith("HTTP/1."), "Status-line not HTTP/1.");
-        }
-
-        // remove the null'th entry with is the status-line
-        Map<String,List<String>> map = new HashMap<>();
-        for (Map.Entry<String,List<String>> e : messageHeaderMap.entrySet()) {
-            if (e.getKey() != null) {
-                map.put(e.getKey(), e.getValue());
-            }
-        }
-        messageHeaderMap = map;
-
-        assertHeadersEqual(messageHeaderMap, decoderMap1,
-                          "messageHeaderMap not equal to decoderMap1");
-
-        // byte at a time
-        decoder = new Http1HeaderParser();
-        List<ByteBuffer> buffers = IntStream.range(0, bytes.length)
-                .mapToObj(i -> ByteBuffer.wrap(bytes, i, 1))
-                .collect(toList());
-        while (decoder.parse(buffers.remove(0)) != true);
-        Map<String,List<String>> decoderMap2 = decoder.headers().map();
-        assertEquals(available, buffers.size(),
-                     "stream available not equals to remaining buffers");
-        assertEquals(decoderMap1, decoderMap2, "decoder maps not equal");
-    }
-
-    @DataProvider(name = "errors")
-    public Object[][] errors() {
-        List<String> responses = new ArrayList<>();
-
-        // These responses are parsed, somewhat, by MessageHeaders but give
-        // nonsensible results. They, correctly, fail with the Http1HeaderParser.
-        String[] bad =
-           {// "HTTP/1.1 402 Payment Required\r\n" +
-            // "Content-Length: 65\r\n\r",   // missing trailing LF   //TODO: incomplete
-
-             "HTTP/1.1 402 Payment Required\r\n" +
-             "Content-Length: 65\r\n\rT\r\n\r\nGGGGGG",
-
-             "HTTP/1.1 200OK\r\n\rT",
-
-             "HTTP/1.1 200OK\rT",
-           };
-        Arrays.stream(bad).forEach(responses::add);
-
-        return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
-    }
-
-    @Test(dataProvider = "errors", expectedExceptions = ProtocolException.class)
-    public void errors(String respString) throws ProtocolException {
-        byte[] bytes = respString.getBytes(US_ASCII);
-        Http1HeaderParser decoder = new Http1HeaderParser();
-        ByteBuffer b = ByteBuffer.wrap(bytes);
-        decoder.parse(b);
-    }
-
-    void assertHeadersEqual(Map<String,List<String>> expected,
-                            Map<String,List<String>> actual,
-                            String msg) {
-
-        if (expected.equals(actual))
-            return;
-
-        assertEquals(expected.size(), actual.size(),
-                     format("%s. Expected size %d, actual size %s. %nexpected= %s,%n actual=%s.",
-                            msg, expected.size(), actual.size(), mapToString(expected), mapToString(actual)));
-
-        for (Map.Entry<String,List<String>> e : expected.entrySet()) {
-            String key = e.getKey();
-            List<String> values = e.getValue();
-
-            boolean found = false;
-            for (Map.Entry<String,List<String>> other: actual.entrySet()) {
-                if (key.equalsIgnoreCase(other.getKey())) {
-                    found = true;
-                    List<String> otherValues = other.getValue();
-                    assertEquals(values.size(), otherValues.size(),
-                                 format("%s. Expected list size %d, actual size %s",
-                                        msg, values.size(), otherValues.size()));
-                    if (!(values.containsAll(otherValues) && otherValues.containsAll(values)))
-                        assertTrue(false, format("Lists are unequal [%s] [%s]", values, otherValues));
-                    break;
-                }
-            }
-            assertTrue(found, format("header name, %s, not found in %s", key, actual));
-        }
-    }
-
-    static String mapToString(Map<String,List<String>> map) {
-        StringBuilder sb = new StringBuilder();
-        List<String> sortedKeys = new ArrayList(map.keySet());
-        Collections.sort(sortedKeys);
-        for (String key : sortedKeys) {
-            List<String> values = map.get(key);
-            sb.append("\n\t" + key + " | " + values);
-        }
-        return sb.toString();
-    }
-
-    // ---
-
-    /* Main entry point for standalone testing of the main functional test. */
-    public static void main(String... args) throws Exception  {
-        Http1HeaderParserTest test = new Http1HeaderParserTest();
-        int count = 0;
-        for (Object[] objs : test.responses()) {
-            out.println("Testing " + count++ + ", " + objs[0]);
-            test.verifyHeaders((String) objs[0]);
-        }
-        for (Object[] objs : test.errors()) {
-            out.println("Testing " + count++ + ", " + objs[0]);
-            try {
-                test.errors((String) objs[0]);
-                throw new RuntimeException("Expected ProtocolException for " + objs[0]);
-            } catch (ProtocolException expected) { /* Ok */ }
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/RawChannelTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,332 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.websocket.RawChannel;
-import jdk.incubator.http.internal.websocket.WebSocketRequest;
-import org.testng.annotations.Test;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UncheckedIOException;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.channels.SelectionKey;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-import static org.testng.Assert.assertEquals;
-
-/*
- * This test exercises mechanics of _independent_ reads and writes on the
- * RawChannel. It verifies that the underlying implementation can manage more
- * than a single type of notifications at the same time.
- */
-public class RawChannelTest {
-
-    private final AtomicLong clientWritten = new AtomicLong();
-    private final AtomicLong serverWritten = new AtomicLong();
-    private final AtomicLong clientRead = new AtomicLong();
-    private final AtomicLong serverRead = new AtomicLong();
-
-    /*
-     * Since at this level we don't have any control over the low level socket
-     * parameters, this latch ensures a write to the channel will stall at least
-     * once (socket's send buffer filled up).
-     */
-    private final CountDownLatch writeStall = new CountDownLatch(1);
-    private final CountDownLatch initialWriteStall = new CountDownLatch(1);
-
-    /*
-     * This one works similarly by providing means to ensure a read from the
-     * channel will stall at least once (no more data available on the socket).
-     */
-    private final CountDownLatch readStall = new CountDownLatch(1);
-    private final CountDownLatch initialReadStall = new CountDownLatch(1);
-
-    private final AtomicInteger writeHandles = new AtomicInteger();
-    private final AtomicInteger readHandles = new AtomicInteger();
-
-    private final CountDownLatch exit = new CountDownLatch(1);
-
-    @Test
-    public void test() throws Exception {
-        try (ServerSocket server = new ServerSocket(0)) {
-            int port = server.getLocalPort();
-            new TestServer(server).start();
-
-            final RawChannel chan = channelOf(port);
-            print("RawChannel is %s", String.valueOf(chan));
-            initialWriteStall.await();
-
-            // It's very important not to forget the initial bytes, possibly
-            // left from the HTTP thingy
-            int initialBytes = chan.initialByteBuffer().remaining();
-            print("RawChannel has %s initial bytes", initialBytes);
-            clientRead.addAndGet(initialBytes);
-
-            // tell the server we have read the initial bytes, so
-            // that it makes sure there is something for us to
-            // read next in case the initialBytes have already drained the
-            // channel dry.
-            initialReadStall.countDown();
-
-            chan.registerEvent(new RawChannel.RawEvent() {
-
-                private final ByteBuffer reusableBuffer = ByteBuffer.allocate(32768);
-
-                @Override
-                public int interestOps() {
-                    return SelectionKey.OP_WRITE;
-                }
-
-                @Override
-                public void handle() {
-                    int i = writeHandles.incrementAndGet();
-                    print("OP_WRITE #%s", i);
-                    if (i > 3) { // Fill up the send buffer not more than 3 times
-                        try {
-                            chan.shutdownOutput();
-                        } catch (IOException e) {
-                            e.printStackTrace();
-                        }
-                        return;
-                    }
-                    long total = 0;
-                    try {
-                        long n;
-                        do {
-                            ByteBuffer[] array = {reusableBuffer.slice()};
-                            n = chan.write(array, 0, 1);
-                            total += n;
-                        } while (n > 0);
-                        print("OP_WRITE clogged SNDBUF with %s bytes", total);
-                        clientWritten.addAndGet(total);
-                        chan.registerEvent(this);
-                        writeStall.countDown(); // signal send buffer is full
-                    } catch (IOException e) {
-                        throw new UncheckedIOException(e);
-                    }
-                }
-            });
-
-            chan.registerEvent(new RawChannel.RawEvent() {
-
-                @Override
-                public int interestOps() {
-                    return SelectionKey.OP_READ;
-                }
-
-                @Override
-                public void handle() {
-                    int i = readHandles.incrementAndGet();
-                    print("OP_READ #%s", i);
-                    ByteBuffer read = null;
-                    long total = 0;
-                    while (true) {
-                        try {
-                            read = chan.read();
-                        } catch (IOException e) {
-                            e.printStackTrace();
-                        }
-                        if (read == null) {
-                            print("OP_READ EOF");
-                            break;
-                        } else if (!read.hasRemaining()) {
-                            print("OP_READ stall");
-                            try {
-                                chan.registerEvent(this);
-                            } catch (IOException e) {
-                                e.printStackTrace();
-                            }
-                            readStall.countDown();
-                            break;
-                        }
-                        int r = read.remaining();
-                        total += r;
-                        clientRead.addAndGet(r);
-                    }
-                    print("OP_READ read %s bytes (%s total)", total, clientRead.get());
-                }
-            });
-            exit.await(); // All done, we need to compare results:
-            assertEquals(clientRead.get(), serverWritten.get());
-            assertEquals(serverRead.get(), clientWritten.get());
-        }
-    }
-
-    private static RawChannel channelOf(int port) throws Exception {
-        URI uri = URI.create("http://127.0.0.1:" + port + "/");
-        print("raw channel to %s", uri.toString());
-        HttpRequest req = HttpRequest.newBuilder(uri).build();
-        // Switch on isWebSocket flag to prevent the connection from
-        // being returned to the pool.
-        ((WebSocketRequest)req).isWebSocket(true);
-        HttpClient client = HttpClient.newHttpClient();
-        try {
-            HttpResponse<?> r = client.send(req, discard(null));
-            r.body();
-            return ((HttpResponseImpl) r).rawChannel();
-        } finally {
-           // Need to hold onto the client until the RawChannel is
-           // created. This would not be needed if we had created
-           // a WebSocket, but here we are fiddling directly
-           // with the internals of HttpResponseImpl!
-           java.lang.ref.Reference.reachabilityFence(client);
-        }
-    }
-
-    private class TestServer extends Thread { // Powered by Slowpokes
-
-        private final ServerSocket server;
-
-        TestServer(ServerSocket server) throws IOException {
-            this.server = server;
-        }
-
-        @Override
-        public void run() {
-            try (Socket s = server.accept()) {
-                InputStream is = s.getInputStream();
-                OutputStream os = s.getOutputStream();
-
-                processHttp(is, os);
-
-                Thread reader = new Thread(() -> {
-                    try {
-                        long n = readSlowly(is);
-                        print("Server read %s bytes", n);
-                        serverRead.addAndGet(n);
-                        s.shutdownInput();
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-                });
-
-                Thread writer = new Thread(() -> {
-                    try {
-                        long n = writeSlowly(os);
-                        print("Server written %s bytes", n);
-                        serverWritten.addAndGet(n);
-                        s.shutdownOutput();
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-                });
-
-                reader.start();
-                writer.start();
-
-                reader.join();
-                writer.join();
-            } catch (Exception e) {
-                e.printStackTrace();
-            } finally {
-                exit.countDown();
-            }
-        }
-
-        private void processHttp(InputStream is, OutputStream os)
-                throws IOException
-        {
-            os.write("HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n".getBytes());
-
-            // write some initial bytes
-            byte[] initial = byteArrayOfSize(1024);
-            os.write(initial);
-            os.flush();
-            serverWritten.addAndGet(initial.length);
-            initialWriteStall.countDown();
-
-            byte[] buf = new byte[1024];
-            String s = "";
-            while (true) {
-                int n = is.read(buf);
-                if (n <= 0) {
-                    throw new RuntimeException("Unexpected end of request");
-                }
-                s = s + new String(buf, 0, n);
-                if (s.contains("\r\n\r\n")) {
-                    break;
-                }
-            }
-        }
-
-        private long writeSlowly(OutputStream os) throws Exception {
-            byte[] first = byteArrayOfSize(1024);
-            long total = first.length;
-            os.write(first);
-            os.flush();
-
-            // wait until initial bytes were read
-            initialReadStall.await();
-
-            // make sure there is something to read, otherwise readStall
-            // will never be counted down.
-            first = byteArrayOfSize(1024);
-            os.write(first);
-            os.flush();
-            total += first.length;
-
-            // Let's wait for the signal from the raw channel that its read has
-            // stalled, and then continue sending a bit more stuff
-            readStall.await();
-            for (int i = 0; i < 32; i++) {
-                byte[] b = byteArrayOfSize(1024);
-                os.write(b);
-                os.flush();
-                total += b.length;
-                TimeUnit.MILLISECONDS.sleep(1);
-            }
-            return total;
-        }
-
-        private long readSlowly(InputStream is) throws Exception {
-            // Wait for the raw channel to fill up its send buffer
-            writeStall.await();
-            long overall = 0;
-            byte[] array = new byte[1024];
-            for (int n = 0; n != -1; n = is.read(array)) {
-                TimeUnit.MILLISECONDS.sleep(1);
-                overall += n;
-            }
-            return overall;
-        }
-    }
-
-    private static void print(String format, Object... args) {
-        System.out.println(Thread.currentThread() + ": " + String.format(format, args));
-    }
-
-    private static byte[] byteArrayOfSize(int bound) {
-        return new byte[new Random().nextInt(1 + bound)];
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/SSLEchoTubeTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,420 +0,0 @@
-/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import jdk.incubator.http.internal.common.Demand;
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SSLTube;
-import jdk.incubator.http.internal.common.SequentialScheduler;
-import jdk.incubator.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.deferOrSchedule(executor);
-        }
-
-        @Override
-        public void onError(Throwable throwable) {
-            System.out.println("EchoTube add " + throwable);
-            queue.add(throwable);
-            processingScheduler.deferOrSchedule(executor);
-        }
-
-        @Override
-        public void onComplete() {
-            System.out.println("EchoTube add EOF");
-            queue.add(EOF);
-            processingScheduler.deferOrSchedule(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.deferOrSchedule(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/jdk.incubator.httpclient/jdk/incubator/http/SSLTubeTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.http;
-
-import jdk.incubator.http.internal.common.FlowTube;
-import jdk.incubator.http.internal.common.SSLFlowDelegate;
-import jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/SelectorTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-/*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.incubator.http;
-
-import java.net.*;
-import java.io.*;
-import java.nio.channels.*;
-import java.nio.ByteBuffer;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-import static java.lang.System.out;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
-
-import org.testng.annotations.Test;
-import jdk.incubator.http.internal.websocket.RawChannel;
-
-/**
- * Whitebox test of selector mechanics. Currently only a simple test
- * setting one read and one write event is done. It checks that the
- * write event occurs first, followed by the read event and then no
- * further events occur despite the conditions actually still existing.
- */
-@Test
-public class SelectorTest {
-
-    AtomicInteger counter = new AtomicInteger();
-    volatile boolean error;
-    static final CountDownLatch finishingGate = new CountDownLatch(1);
-    static volatile HttpClient staticDefaultClient;
-
-    static HttpClient defaultClient() {
-        if (staticDefaultClient == null) {
-            synchronized (SelectorTest.class) {
-                staticDefaultClient = HttpClient.newHttpClient();
-            }
-        }
-        return staticDefaultClient;
-    }
-
-    String readSomeBytes(RawChannel chan) {
-        try {
-            ByteBuffer buf = chan.read();
-            if (buf == null) {
-                out.println("chan read returned null");
-                return null;
-            }
-            buf.flip();
-            byte[] bb = new byte[buf.remaining()];
-            buf.get(bb);
-            return new String(bb, US_ASCII);
-        } catch (IOException ioe) {
-            throw new UncheckedIOException(ioe);
-        }
-    }
-
-    @Test
-    public void test() throws Exception {
-
-        try (ServerSocket server = new ServerSocket(0)) {
-            int port = server.getLocalPort();
-
-            out.println("Listening on port " + server.getLocalPort());
-
-            TestServer t = new TestServer(server);
-            t.start();
-            out.println("Started server thread");
-
-            try (RawChannel chan = getARawChannel(port)) {
-
-                chan.registerEvent(new RawChannel.RawEvent() {
-                    @Override
-                    public int interestOps() {
-                        return SelectionKey.OP_READ;
-                    }
-
-                    @Override
-                    public void handle() {
-                        readSomeBytes(chan);
-                        out.printf("OP_READ\n");
-                        final int count = counter.get();
-                        if (count != 1) {
-                            out.printf("OP_READ error counter = %d\n", count);
-                            error = true;
-                        }
-                    }
-                });
-
-                chan.registerEvent(new RawChannel.RawEvent() {
-                    @Override
-                    public int interestOps() {
-                        return SelectionKey.OP_WRITE;
-                    }
-
-                    @Override
-                    public void handle() {
-                        out.printf("OP_WRITE\n");
-                        final int count = counter.get();
-                        if (count != 0) {
-                            out.printf("OP_WRITE error counter = %d\n", count);
-                            error = true;
-                        } else {
-                            ByteBuffer bb = ByteBuffer.wrap(TestServer.INPUT);
-                            counter.incrementAndGet();
-                            try {
-                                chan.write(new ByteBuffer[]{bb}, 0, 1);
-                            } catch (IOException e) {
-                                throw new UncheckedIOException(e);
-                            }
-                        }
-                    }
-
-                });
-                out.println("Events registered. Waiting");
-                finishingGate.await(30, SECONDS);
-                if (error)
-                    throw new RuntimeException("Error");
-                else
-                    out.println("No error");
-            }
-        }
-    }
-
-    static RawChannel getARawChannel(int port) throws Exception {
-        URI uri = URI.create("http://127.0.0.1:" + port + "/");
-        out.println("client connecting to " + uri.toString());
-        HttpRequest req = HttpRequest.newBuilder(uri).build();
-        // Otherwise HttpClient will think this is an ordinary connection and
-        // thus all ordinary procedures apply to it, e.g. it must be put into
-        // the cache
-        ((HttpRequestImpl) req).isWebSocket(true);
-        HttpResponse<?> r = defaultClient().send(req, discard(null));
-        r.body();
-        return ((HttpResponseImpl) r).rawChannel();
-    }
-
-    static class TestServer extends Thread {
-        static final byte[] INPUT = "Hello world".getBytes(US_ASCII);
-        static final byte[] OUTPUT = "Goodbye world".getBytes(US_ASCII);
-        static final String FIRST_RESPONSE = "HTTP/1.1 200 OK\r\nContent-length: 0\r\n\r\n";
-        final ServerSocket server;
-
-        TestServer(ServerSocket server) throws IOException {
-            this.server = server;
-        }
-
-        public void run() {
-            try (Socket s = server.accept();
-                 InputStream is = s.getInputStream();
-                 OutputStream os = s.getOutputStream()) {
-
-                out.println("Got connection");
-                readRequest(is);
-                os.write(FIRST_RESPONSE.getBytes());
-                read(is);
-                write(os);
-                Thread.sleep(1000);
-                // send some more data, and make sure WRITE op does not get called
-                write(os);
-                out.println("TestServer exiting");
-                SelectorTest.finishingGate.countDown();
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-
-        // consumes the HTTP request
-        static void readRequest(InputStream is) throws IOException {
-            out.println("starting readRequest");
-            byte[] buf = new byte[1024];
-            String s = "";
-            while (true) {
-                int n = is.read(buf);
-                if (n <= 0)
-                    throw new IOException("Error");
-                s = s + new String(buf, 0, n);
-                if (s.indexOf("\r\n\r\n") != -1)
-                    break;
-            }
-            out.println("returning from readRequest");
-        }
-
-        static void read(InputStream is) throws IOException {
-            out.println("starting read");
-            for (int i = 0; i < INPUT.length; i++) {
-                int c = is.read();
-                if (c == -1)
-                    throw new IOException("closed");
-                if (INPUT[i] != (byte) c)
-                    throw new IOException("Error. Expected:" + INPUT[i] + ", got:" + c);
-            }
-            out.println("returning from read");
-        }
-
-        static void write(OutputStream os) throws IOException {
-            out.println("doing write");
-            os.write(OUTPUT);
-        }
-    }
-}
--- a/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/WrapperTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/common/DemandTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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/jdk.incubator.httpclient/jdk/incubator/http/internal/frame/FramesDecoderTest.java	Tue Apr 17 15:39:20 2018 +0200
+++ /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 jdk.incubator.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);
-    }
-}
--- a/test/jdk/lib/testlibrary/jdk/testlibrary/SimpleSSLContext.java	Tue Apr 17 15:39:20 2018 +0200
+++ b/test/jdk/lib/testlibrary/jdk/testlibrary/SimpleSSLContext.java	Tue Apr 17 08:54:17 2018 -0700
@@ -53,27 +53,44 @@
      * source directory
      */
     public SimpleSSLContext() throws IOException {
-        String paths = System.getProperty("test.src.path");
-        StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
-        boolean securityExceptions = false;
-        while (st.hasMoreTokens()) {
-            String path = st.nextToken();
-            try {
-                File f = new File(path, "jdk/testlibrary/testkeys");
-                if (f.exists()) {
-                    try (FileInputStream fis = new FileInputStream(f)) {
-                        init(fis);
-                        return;
+        try {
+            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+                @Override
+                public Void run() throws Exception {
+                    String paths = System.getProperty("test.src.path");
+                    StringTokenizer st = new StringTokenizer(paths, File.pathSeparator);
+                    boolean securityExceptions = false;
+                    while (st.hasMoreTokens()) {
+                        String path = st.nextToken();
+                        try {
+                            File f = new File(path, "jdk/testlibrary/testkeys");
+                            if (f.exists()) {
+                                try (FileInputStream fis = new FileInputStream(f)) {
+                                    init(fis);
+                                    return null;
+                                }
+                            }
+                        } 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");
+                    }
+                    return null;
                 }
-            } 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");
+            });
+        } catch (PrivilegedActionException pae) {
+            Throwable t = pae.getCause() != null ? pae.getCause() : pae;
+            if (t instanceof IOException)
+                throw (IOException)t;
+            if (t instanceof RuntimeException)
+                throw (RuntimeException)t;
+            if (t instanceof Error)
+                throw (Error)t;
+            throw new RuntimeException(t);
         }
     }
 
Binary file test/jdk/lib/testlibrary/jdk/testlibrary/testkeys has changed