Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
41171 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.internal.net.http;2627import java.io.EOFException;28import java.io.IOException;29import java.io.UncheckedIOException;30import java.net.InetSocketAddress;31import java.net.URI;32import java.nio.ByteBuffer;33import java.nio.charset.StandardCharsets;34import java.util.Iterator;35import java.util.List;36import java.util.Locale;37import java.util.Map;38import java.util.Set;39import java.util.concurrent.CompletableFuture;40import java.util.ArrayList;41import java.util.Objects;42import java.util.concurrent.ConcurrentMap;43import java.util.concurrent.ConcurrentHashMap;44import java.util.concurrent.ConcurrentLinkedQueue;45import java.util.concurrent.Flow;46import java.util.function.Function;47import java.util.function.Supplier;48import javax.net.ssl.SSLEngine;49import javax.net.ssl.SSLException;50import java.net.http.HttpClient;51import java.net.http.HttpHeaders;52import jdk.internal.net.http.HttpConnection.HttpPublisher;53import jdk.internal.net.http.common.FlowTube;54import jdk.internal.net.http.common.FlowTube.TubeSubscriber;55import jdk.internal.net.http.common.HttpHeadersBuilder;56import jdk.internal.net.http.common.Log;57import jdk.internal.net.http.common.Logger;58import jdk.internal.net.http.common.MinimalFuture;59import jdk.internal.net.http.common.SequentialScheduler;60import jdk.internal.net.http.common.Utils;61import jdk.internal.net.http.frame.ContinuationFrame;62import jdk.internal.net.http.frame.DataFrame;63import jdk.internal.net.http.frame.ErrorFrame;64import jdk.internal.net.http.frame.FramesDecoder;65import jdk.internal.net.http.frame.FramesEncoder;66import jdk.internal.net.http.frame.GoAwayFrame;67import jdk.internal.net.http.frame.HeaderFrame;68import jdk.internal.net.http.frame.HeadersFrame;69import jdk.internal.net.http.frame.Http2Frame;70import jdk.internal.net.http.frame.MalformedFrame;71import jdk.internal.net.http.frame.OutgoingHeaders;72import jdk.internal.net.http.frame.PingFrame;73import jdk.internal.net.http.frame.PushPromiseFrame;74import jdk.internal.net.http.frame.ResetFrame;75import jdk.internal.net.http.frame.SettingsFrame;76import jdk.internal.net.http.frame.WindowUpdateFrame;77import jdk.internal.net.http.hpack.Encoder;78import jdk.internal.net.http.hpack.Decoder;79import jdk.internal.net.http.hpack.DecodingCallback;80import static java.nio.charset.StandardCharsets.UTF_8;81import static jdk.internal.net.http.frame.SettingsFrame.*;8283/**84* An Http2Connection. Encapsulates the socket(channel) and any SSLEngine used85* over it. Contains an HttpConnection which hides the SocketChannel SSL stuff.86*87* Http2Connections belong to a Http2ClientImpl, (one of) which belongs88* to a HttpClientImpl.89*90* Creation cases:91* 1) upgraded HTTP/1.1 plain tcp connection92* 2) prior knowledge directly created plain tcp connection93* 3) directly created HTTP/2 SSL connection which uses ALPN.94*95* Sending is done by writing directly to underlying HttpConnection object which96* is operating in async mode. No flow control applies on output at this level97* and all writes are just executed as puts to an output Q belonging to HttpConnection98* Flow control is implemented by HTTP/2 protocol itself.99*100* Hpack header compression101* and outgoing stream creation is also done here, because these operations102* must be synchronized at the socket level. Stream objects send frames simply103* by placing them on the connection's output Queue. sendFrame() is called104* from a higher level (Stream) thread.105*106* asyncReceive(ByteBuffer) is always called from the selector thread. It assembles107* incoming Http2Frames, and directs them to the appropriate Stream.incoming()108* or handles them directly itself. This thread performs hpack decompression109* and incoming stream creation (Server push). Incoming frames destined for a110* stream are provided by calling Stream.incoming().111*/112class Http2Connection {113114final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);115final static Logger DEBUG_LOGGER =116Utils.getDebugLogger("Http2Connection"::toString, Utils.DEBUG);117private final Logger debugHpack =118Utils.getHpackLogger(this::dbgString, Utils.DEBUG_HPACK);119static final ByteBuffer EMPTY_TRIGGER = ByteBuffer.allocate(0);120121static private final int MAX_CLIENT_STREAM_ID = Integer.MAX_VALUE; // 2147483647122static private final int MAX_SERVER_STREAM_ID = Integer.MAX_VALUE - 1; // 2147483646123static private final int BUFFER = 8; // added as an upper bound124125/**126* Flag set when no more streams to be opened on this connection.127* Two cases where it is used.128*129* 1. Two connections to the same server were opened concurrently, in which130* case one of them will be put in the cache, and the second will expire131* when all its opened streams (which usually should be a single client132* stream + possibly some additional push-promise server streams) complete.133* 2. A cached connection reaches its maximum number of streams (~ 2^31-1)134* either server / or client allocated, in which case it will be taken135* out of the cache - allowing a new connection to replace it. It will136* expire when all its still open streams (which could be many) eventually137* complete.138*/139private boolean finalStream;140141/*142* ByteBuffer pooling strategy for HTTP/2 protocol.143*144* In general there are 4 points where ByteBuffers are used:145* - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing146* encrypted data in case of SSL connection.147*148* 1. Outgoing frames encoded to ByteBuffers.149*150* Outgoing ByteBuffers are created with required size and frequently151* small (except DataFrames, etc). At this place no pools at all. All152* outgoing buffers should eventually be collected by GC.153*154* 2. Incoming ByteBuffers (decoded to frames).155*156* Here, total elimination of BB pool is not a good idea.157* We don't know how many bytes we will receive through network.158*159* A possible future improvement ( currently not implemented ):160* Allocate buffers of reasonable size. The following life of the BB:161* - If all frames decoded from the BB are other than DataFrame and162* HeaderFrame (and HeaderFrame subclasses) BB is returned to pool,163* - If a DataFrame is decoded from the BB. In that case DataFrame refers164* to sub-buffer obtained by slice(). Such a BB is never returned to the165* pool and will eventually be GC'ed.166* - If a HeadersFrame is decoded from the BB. Then header decoding is167* performed inside processFrame method and the buffer could be release168* back to pool.169*170* 3. SSL encrypted buffers ( received ).171*172* The current implementation recycles encrypted buffers read from the173* channel. The pool of buffers has a maximum size of 3, SocketTube.MAX_BUFFERS,174* direct buffers which are shared by all connections on a given client.175* The pool is used by all SSL connections - whether HTTP/1.1 or HTTP/2,176* but only for SSL encrypted buffers that circulate between the SocketTube177* Publisher and the SSLFlowDelegate Reader. Limiting the pool to this178* particular segment allows the use of direct buffers, thus avoiding any179* additional copy in the NIO socket channel implementation. See180* HttpClientImpl.SSLDirectBufferSupplier, SocketTube.SSLDirectBufferSource,181* and SSLTube.recycler.182*/183184185// A small class that allows to control frames with respect to the state of186// the connection preface. Any data received before the connection187// preface is sent will be buffered.188private final class FramesController {189volatile boolean prefaceSent;190volatile List<ByteBuffer> pending;191192boolean processReceivedData(FramesDecoder decoder, ByteBuffer buf)193throws IOException194{195// if preface is not sent, buffers data in the pending list196if (!prefaceSent) {197if (debug.on())198debug.log("Preface not sent: buffering %d", buf.remaining());199synchronized (this) {200if (!prefaceSent) {201if (pending == null) pending = new ArrayList<>();202pending.add(buf);203if (debug.on())204debug.log("there are now %d bytes buffered waiting for preface to be sent"205+ Utils.remaining(pending)206);207return false;208}209}210}211212// Preface is sent. Checks for pending data and flush it.213// We rely on this method being called from within the Http2TubeSubscriber214// scheduler, so we know that no other thread could execute this method215// concurrently while we're here.216// This ensures that later incoming buffers will not217// be processed before we have flushed the pending queue.218// No additional synchronization is therefore necessary here.219List<ByteBuffer> pending = this.pending;220this.pending = null;221if (pending != null) {222// flush pending data223if (debug.on()) debug.log(() -> "Processing buffered data: "224+ Utils.remaining(pending));225for (ByteBuffer b : pending) {226decoder.decode(b);227}228}229// push the received buffer to the frames decoder.230if (buf != EMPTY_TRIGGER) {231if (debug.on()) debug.log("Processing %d", buf.remaining());232decoder.decode(buf);233}234return true;235}236237// Mark that the connection preface is sent238void markPrefaceSent() {239assert !prefaceSent;240synchronized (this) {241prefaceSent = true;242}243}244}245246volatile boolean closed;247248//-------------------------------------249final HttpConnection connection;250private final Http2ClientImpl client2;251private final ConcurrentMap<Integer,Stream<?>> streams = new ConcurrentHashMap<>();252private int nextstreamid;253private int nextPushStream = 2;254// actual stream ids are not allocated until the Headers frame is ready255// to be sent. The following two fields are updated as soon as a stream256// is created and assigned to a connection. They are checked before257// assigning a stream to a connection.258private int lastReservedClientStreamid = 1;259private int lastReservedServerStreamid = 0;260private int numReservedClientStreams = 0; // count of current streams261private int numReservedServerStreams = 0; // count of current streams262private final Encoder hpackOut;263private final Decoder hpackIn;264final SettingsFrame clientSettings;265private volatile SettingsFrame serverSettings;266private final String key; // for HttpClientImpl.connections map267private final FramesDecoder framesDecoder;268private final FramesEncoder framesEncoder = new FramesEncoder();269270/**271* Send Window controller for both connection and stream windows.272* Each of this connection's Streams MUST use this controller.273*/274private final WindowController windowController = new WindowController();275private final FramesController framesController = new FramesController();276private final Http2TubeSubscriber subscriber;277final ConnectionWindowUpdateSender windowUpdater;278private volatile Throwable cause;279private volatile Supplier<ByteBuffer> initial;280281static final int DEFAULT_FRAME_SIZE = 16 * 1024;282283284// TODO: need list of control frames from other threads285// that need to be sent286287private Http2Connection(HttpConnection connection,288Http2ClientImpl client2,289int nextstreamid,290String key) {291this.connection = connection;292this.client2 = client2;293this.subscriber = new Http2TubeSubscriber(client2.client());294this.nextstreamid = nextstreamid;295this.key = key;296this.clientSettings = this.client2.getClientSettings();297this.framesDecoder = new FramesDecoder(this::processFrame,298clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE));299// serverSettings will be updated by server300this.serverSettings = SettingsFrame.defaultRFCSettings();301this.hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE));302this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));303if (debugHpack.on()) {304debugHpack.log("For the record:" + super.toString());305debugHpack.log("Decoder created: %s", hpackIn);306debugHpack.log("Encoder created: %s", hpackOut);307}308this.windowUpdater = new ConnectionWindowUpdateSender(this,309client2.getConnectionWindowSize(clientSettings));310}311312/**313* Case 1) Create from upgraded HTTP/1.1 connection.314* Is ready to use. Can't be SSL. exchange is the Exchange315* that initiated the connection, whose response will be delivered316* on a Stream.317*/318private Http2Connection(HttpConnection connection,319Http2ClientImpl client2,320Exchange<?> exchange,321Supplier<ByteBuffer> initial)322throws IOException, InterruptedException323{324this(connection,325client2,3263, // stream 1 is registered during the upgrade327keyFor(connection));328reserveStream(true);329Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());330331Stream<?> initialStream = createStream(exchange);332boolean opened = initialStream.registerStream(1, true);333if (debug.on() && !opened) {334debug.log("Initial stream was cancelled - but connection is maintained: " +335"reset frame will need to be sent later");336}337windowController.registerStream(1, getInitialSendWindowSize());338initialStream.requestSent();339// Upgrading:340// set callbacks before sending preface - makes sure anything that341// might be sent by the server will come our way.342this.initial = initial;343connectFlows(connection);344sendConnectionPreface();345if (!opened) {346debug.log("ensure reset frame is sent to cancel initial stream");347initialStream.sendCancelStreamFrame();348}349350}351352// Used when upgrading an HTTP/1.1 connection to HTTP/2 after receiving353// agreement from the server. Async style but completes immediately, because354// the connection is already connected.355static CompletableFuture<Http2Connection> createAsync(HttpConnection connection,356Http2ClientImpl client2,357Exchange<?> exchange,358Supplier<ByteBuffer> initial)359{360return MinimalFuture.supply(() -> new Http2Connection(connection, client2, exchange, initial));361}362363// Requires TLS handshake. So, is really async364static CompletableFuture<Http2Connection> createAsync(HttpRequestImpl request,365Http2ClientImpl h2client,366Exchange<?> exchange) {367assert request.secure();368AbstractAsyncSSLConnection connection = (AbstractAsyncSSLConnection)369HttpConnection.getConnection(request.getAddress(),370h2client.client(),371request,372HttpClient.Version.HTTP_2);373374// Expose the underlying connection to the exchange's aborter so it can375// be closed if a timeout occurs.376exchange.connectionAborter.connection(connection);377378return connection.connectAsync(exchange)379.thenCompose(unused -> connection.finishConnect())380.thenCompose(unused -> checkSSLConfig(connection))381.thenCompose(notused-> {382CompletableFuture<Http2Connection> cf = new MinimalFuture<>();383try {384Http2Connection hc = new Http2Connection(request, h2client, connection);385cf.complete(hc);386} catch (IOException e) {387cf.completeExceptionally(e);388}389return cf; } );390}391392/**393* Cases 2) 3)394*395* request is request to be sent.396*/397private Http2Connection(HttpRequestImpl request,398Http2ClientImpl h2client,399HttpConnection connection)400throws IOException401{402this(connection,403h2client,4041,405keyFor(request.uri(), request.proxy()));406407Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());408409// safe to resume async reading now.410connectFlows(connection);411sendConnectionPreface();412}413414private void connectFlows(HttpConnection connection) {415FlowTube tube = connection.getConnectionFlow();416// Connect the flow to our Http2TubeSubscriber:417tube.connectFlows(connection.publisher(), subscriber);418}419420final HttpClientImpl client() {421return client2.client();422}423424// call these before assigning a request/stream to a connection425// if false returned then a new Http2Connection is required426// if true, the stream may be assigned to this connection427// for server push, if false returned, then the stream should be cancelled428synchronized boolean reserveStream(boolean clientInitiated) throws IOException {429if (finalStream) {430return false;431}432if (clientInitiated && (lastReservedClientStreamid + 2) >= MAX_CLIENT_STREAM_ID) {433setFinalStream();434client2.deleteConnection(this);435return false;436} else if (!clientInitiated && (lastReservedServerStreamid + 2) >= MAX_SERVER_STREAM_ID) {437setFinalStream();438client2.deleteConnection(this);439return false;440}441if (clientInitiated)442lastReservedClientStreamid+=2;443else444lastReservedServerStreamid+=2;445446assert numReservedClientStreams >= 0;447assert numReservedServerStreams >= 0;448if (clientInitiated &&numReservedClientStreams >= maxConcurrentClientInitiatedStreams()) {449throw new IOException("too many concurrent streams");450} else if (clientInitiated) {451numReservedClientStreams++;452}453if (!clientInitiated && numReservedServerStreams >= maxConcurrentServerInitiatedStreams()) {454return false;455} else if (!clientInitiated) {456numReservedServerStreams++;457}458return true;459}460461/**462* Throws an IOException if h2 was not negotiated463*/464private static CompletableFuture<?> checkSSLConfig(AbstractAsyncSSLConnection aconn) {465assert aconn.isSecure();466467Function<String, CompletableFuture<Void>> checkAlpnCF = (alpn) -> {468CompletableFuture<Void> cf = new MinimalFuture<>();469SSLEngine engine = aconn.getEngine();470String engineAlpn = engine.getApplicationProtocol();471assert Objects.equals(alpn, engineAlpn)472: "alpn: %s, engine: %s".formatted(alpn, engineAlpn);473474DEBUG_LOGGER.log("checkSSLConfig: alpn: %s", alpn );475476if (alpn == null || !alpn.equals("h2")) {477String msg;478if (alpn == null) {479Log.logSSL("ALPN not supported");480msg = "ALPN not supported";481} else {482switch (alpn) {483case "":484Log.logSSL(msg = "No ALPN negotiated");485break;486case "http/1.1":487Log.logSSL( msg = "HTTP/1.1 ALPN returned");488break;489default:490Log.logSSL(msg = "Unexpected ALPN: " + alpn);491cf.completeExceptionally(new IOException(msg));492}493}494cf.completeExceptionally(new ALPNException(msg, aconn));495return cf;496}497cf.complete(null);498return cf;499};500501return aconn.getALPN()502.whenComplete((r,t) -> {503if (t != null && t instanceof SSLException) {504// something went wrong during the initial handshake505// close the connection506aconn.close();507}508})509.thenCompose(checkAlpnCF);510}511512synchronized boolean finalStream() {513return finalStream;514}515516/**517* Mark this connection so no more streams created on it and it will close when518* all are complete.519*/520synchronized void setFinalStream() {521finalStream = true;522}523524static String keyFor(HttpConnection connection) {525boolean isProxy = connection.isProxied(); // tunnel or plain clear connection through proxy526boolean isSecure = connection.isSecure();527InetSocketAddress addr = connection.address();528InetSocketAddress proxyAddr = connection.proxy();529assert isProxy == (proxyAddr != null);530531return keyString(isSecure, proxyAddr, addr.getHostString(), addr.getPort());532}533534static String keyFor(URI uri, InetSocketAddress proxy) {535boolean isSecure = uri.getScheme().equalsIgnoreCase("https");536537String host = uri.getHost();538int port = uri.getPort();539return keyString(isSecure, proxy, host, port);540}541542543// Compute the key for an HttpConnection in the Http2ClientImpl pool:544// The key string follows one of the three forms below:545// {C,S}:H:host:port546// C:P:proxy-host:proxy-port547// S:T:H:host:port;P:proxy-host:proxy-port548// C indicates clear text connection "http"549// S indicates secure "https"550// H indicates host (direct) connection551// P indicates proxy552// T indicates a tunnel connection through a proxy553//554// The first form indicates a direct connection to a server:555// - direct clear connection to an HTTP host:556// e.g.: "C:H:foo.com:80"557// - direct secure connection to an HTTPS host:558// e.g.: "S:H:foo.com:443"559// The second form indicates a clear connection to an HTTP/1.1 proxy:560// e.g.: "C:P:myproxy:8080"561// The third form indicates a secure tunnel connection to an HTTPS562// host through an HTTP/1.1 proxy:563// e.g: "S:T:H:foo.com:80;P:myproxy:8080"564static String keyString(boolean secure, InetSocketAddress proxy, String host, int port) {565if (secure && port == -1)566port = 443;567else if (!secure && port == -1)568port = 80;569var key = (secure ? "S:" : "C:");570if (proxy != null && !secure) {571// clear connection through proxy572key = key + "P:" + proxy.getHostString() + ":" + proxy.getPort();573} else if (proxy == null) {574// direct connection to host575key = key + "H:" + host + ":" + port;576} else {577// tunnel connection through proxy578key = key + "T:H:" + host + ":" + port + ";P:" + proxy.getHostString() + ":" + proxy.getPort();579}580return key;581}582583String key() {584return this.key;585}586587boolean offerConnection() {588return client2.offerConnection(this);589}590591private HttpPublisher publisher() {592return connection.publisher();593}594595private void decodeHeaders(HeaderFrame frame, DecodingCallback decoder)596throws IOException597{598if (debugHpack.on()) debugHpack.log("decodeHeaders(%s)", decoder);599600boolean endOfHeaders = frame.getFlag(HeaderFrame.END_HEADERS);601602List<ByteBuffer> buffers = frame.getHeaderBlock();603int len = buffers.size();604for (int i = 0; i < len; i++) {605ByteBuffer b = buffers.get(i);606hpackIn.decode(b, endOfHeaders && (i == len - 1), decoder);607}608}609610final int getInitialSendWindowSize() {611return serverSettings.getParameter(INITIAL_WINDOW_SIZE);612}613614final int maxConcurrentClientInitiatedStreams() {615return serverSettings.getParameter(MAX_CONCURRENT_STREAMS);616}617618final int maxConcurrentServerInitiatedStreams() {619return clientSettings.getParameter(MAX_CONCURRENT_STREAMS);620}621622void close() {623Log.logTrace("Closing HTTP/2 connection: to {0}", connection.address());624GoAwayFrame f = new GoAwayFrame(0,625ErrorFrame.NO_ERROR,626"Requested by user".getBytes(UTF_8));627// TODO: set last stream. For now zero ok.628sendFrame(f);629}630631long count;632final void asyncReceive(ByteBuffer buffer) {633// We don't need to read anything and634// we don't want to send anything back to the server635// until the connection preface has been sent.636// Therefore we're going to wait if needed before reading637// (and thus replying) to anything.638// Starting to reply to something (e.g send an ACK to a639// SettingsFrame sent by the server) before the connection640// preface is fully sent might result in the server641// sending a GOAWAY frame with 'invalid_preface'.642//643// Note: asyncReceive is only called from the Http2TubeSubscriber644// sequential scheduler.645try {646Supplier<ByteBuffer> bs = initial;647// ensure that we always handle the initial buffer first,648// if any.649if (bs != null) {650initial = null;651ByteBuffer b = bs.get();652if (b.hasRemaining()) {653long c = ++count;654if (debug.on())655debug.log(() -> "H2 Receiving Initial(" + c +"): " + b.remaining());656framesController.processReceivedData(framesDecoder, b);657}658}659ByteBuffer b = buffer;660// the Http2TubeSubscriber scheduler ensures that the order of incoming661// buffers is preserved.662if (b == EMPTY_TRIGGER) {663if (debug.on()) debug.log("H2 Received EMPTY_TRIGGER");664boolean prefaceSent = framesController.prefaceSent;665assert prefaceSent;666// call framesController.processReceivedData to potentially667// trigger the processing of all the data buffered there.668framesController.processReceivedData(framesDecoder, buffer);669if (debug.on()) debug.log("H2 processed buffered data");670} else {671long c = ++count;672if (debug.on())673debug.log("H2 Receiving(%d): %d", c, b.remaining());674framesController.processReceivedData(framesDecoder, buffer);675if (debug.on()) debug.log("H2 processed(%d)", c);676}677} catch (Throwable e) {678String msg = Utils.stackTrace(e);679Log.logTrace(msg);680shutdown(e);681}682}683684Throwable getRecordedCause() {685return cause;686}687688void shutdown(Throwable t) {689if (debug.on()) debug.log(() -> "Shutting down h2c (closed="+closed+"): " + t);690if (closed == true) return;691synchronized (this) {692if (closed == true) return;693closed = true;694}695if (Log.errors()) {696if (!(t instanceof EOFException) || isActive()) {697Log.logError(t);698} else if (t != null) {699Log.logError("Shutting down connection: {0}", t.getMessage());700}701}702Throwable initialCause = this.cause;703if (initialCause == null) this.cause = t;704client2.deleteConnection(this);705for (Stream<?> s : streams.values()) {706try {707s.connectionClosing(t);708} catch (Throwable e) {709Log.logError("Failed to close stream {0}: {1}", s.streamid, e);710}711}712connection.close();713}714715/**716* Streams initiated by a client MUST use odd-numbered stream717* identifiers; those initiated by the server MUST use even-numbered718* stream identifiers.719*/720private static final boolean isServerInitiatedStream(int streamid) {721return (streamid & 0x1) == 0;722}723724/**725* Handles stream 0 (common) frames that apply to whole connection and passes726* other stream specific frames to that Stream object.727*728* Invokes Stream.incoming() which is expected to process frame without729* blocking.730*/731void processFrame(Http2Frame frame) throws IOException {732Log.logFrames(frame, "IN");733int streamid = frame.streamid();734if (frame instanceof MalformedFrame) {735Log.logError(((MalformedFrame) frame).getMessage());736if (streamid == 0) {737framesDecoder.close("Malformed frame on stream 0");738protocolError(((MalformedFrame) frame).getErrorCode(),739((MalformedFrame) frame).getMessage());740} else {741if (debug.on())742debug.log(() -> "Reset stream: " + ((MalformedFrame) frame).getMessage());743resetStream(streamid, ((MalformedFrame) frame).getErrorCode());744}745return;746}747if (streamid == 0) {748handleConnectionFrame(frame);749} else {750if (frame instanceof SettingsFrame) {751// The stream identifier for a SETTINGS frame MUST be zero752framesDecoder.close(753"The stream identifier for a SETTINGS frame MUST be zero");754protocolError(GoAwayFrame.PROTOCOL_ERROR);755return;756}757758Stream<?> stream = getStream(streamid);759if (stream == null) {760// Should never receive a frame with unknown stream id761762if (frame instanceof HeaderFrame) {763// always decode the headers as they may affect764// connection-level HPACK decoding state765DecodingCallback decoder = new ValidatingHeadersConsumer();766try {767decodeHeaders((HeaderFrame) frame, decoder);768} catch (UncheckedIOException e) {769protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());770return;771}772}773774if (!(frame instanceof ResetFrame)) {775if (frame instanceof DataFrame) {776dropDataFrame((DataFrame)frame);777}778if (isServerInitiatedStream(streamid)) {779if (streamid < nextPushStream) {780// trailing data on a cancelled push promise stream,781// reset will already have been sent, ignore782Log.logTrace("Ignoring cancelled push promise frame " + frame);783} else {784resetStream(streamid, ResetFrame.PROTOCOL_ERROR);785}786} else if (streamid >= nextstreamid) {787// otherwise the stream has already been reset/closed788resetStream(streamid, ResetFrame.PROTOCOL_ERROR);789}790}791return;792}793if (frame instanceof PushPromiseFrame) {794PushPromiseFrame pp = (PushPromiseFrame)frame;795try {796handlePushPromise(stream, pp);797} catch (UncheckedIOException e) {798protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());799return;800}801} else if (frame instanceof HeaderFrame) {802// decode headers (or continuation)803try {804decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer());805} catch (UncheckedIOException e) {806debug.log("Error decoding headers: " + e.getMessage(), e);807protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());808return;809}810stream.incoming(frame);811} else {812stream.incoming(frame);813}814}815}816817final void dropDataFrame(DataFrame df) {818if (closed) return;819if (debug.on()) {820debug.log("Dropping data frame for stream %d (%d payload bytes)",821df.streamid(), df.payloadLength());822}823ensureWindowUpdated(df);824}825826final void ensureWindowUpdated(DataFrame df) {827try {828if (closed) return;829int length = df.payloadLength();830if (length > 0) {831windowUpdater.update(length);832}833} catch(Throwable t) {834Log.logError("Unexpected exception while updating window: {0}", (Object)t);835}836}837838private <T> void handlePushPromise(Stream<T> parent, PushPromiseFrame pp)839throws IOException840{841// always decode the headers as they may affect connection-level HPACK842// decoding state843HeaderDecoder decoder = new HeaderDecoder();844decodeHeaders(pp, decoder);845846HttpRequestImpl parentReq = parent.request;847int promisedStreamid = pp.getPromisedStream();848if (promisedStreamid != nextPushStream) {849resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);850return;851} else if (!reserveStream(false)) {852resetStream(promisedStreamid, ResetFrame.REFUSED_STREAM);853return;854} else {855nextPushStream += 2;856}857858HttpHeaders headers = decoder.headers();859HttpRequestImpl pushReq = HttpRequestImpl.createPushRequest(parentReq, headers);860Exchange<T> pushExch = new Exchange<>(pushReq, parent.exchange.multi);861Stream.PushedStream<T> pushStream = createPushStream(parent, pushExch);862pushExch.exchImpl = pushStream;863pushStream.registerStream(promisedStreamid, true);864parent.incoming_pushPromise(pushReq, pushStream);865}866867private void handleConnectionFrame(Http2Frame frame)868throws IOException869{870switch (frame.type()) {871case SettingsFrame.TYPE -> handleSettings((SettingsFrame) frame);872case PingFrame.TYPE -> handlePing((PingFrame) frame);873case GoAwayFrame.TYPE -> handleGoAway((GoAwayFrame) frame);874case WindowUpdateFrame.TYPE -> handleWindowUpdate((WindowUpdateFrame) frame);875876default -> protocolError(ErrorFrame.PROTOCOL_ERROR);877}878}879880void resetStream(int streamid, int code) {881try {882if (connection.channel().isOpen()) {883// no need to try & send a reset frame if the884// connection channel is already closed.885Log.logError(886"Resetting stream {0,number,integer} with error code {1,number,integer}",887streamid, code);888markStream(streamid, code);889ResetFrame frame = new ResetFrame(streamid, code);890sendFrame(frame);891} else if (debug.on()) {892debug.log("Channel already closed, no need to reset stream %d",893streamid);894}895} finally {896decrementStreamsCount(streamid);897closeStream(streamid);898}899}900901private void markStream(int streamid, int code) {902Stream<?> s = streams.get(streamid);903if (s != null) s.markStream(code);904}905906// reduce count of streams by 1 if stream still exists907synchronized void decrementStreamsCount(int streamid) {908Stream<?> s = streams.get(streamid);909if (s == null || !s.deRegister())910return;911if (streamid % 2 == 1) {912numReservedClientStreams--;913assert numReservedClientStreams >= 0 :914"negative client stream count for stream=" + streamid;915} else {916numReservedServerStreams--;917assert numReservedServerStreams >= 0 :918"negative server stream count for stream=" + streamid;919}920}921922void closeStream(int streamid) {923if (debug.on()) debug.log("Closed stream %d", streamid);924boolean isClient = (streamid % 2) == 1;925Stream<?> s = streams.remove(streamid);926if (s != null) {927// decrement the reference count on the HttpClientImpl928// to allow the SelectorManager thread to exit if no929// other operation is pending and the facade is no930// longer referenced.931client().streamUnreference();932}933// ## Remove s != null. It is a hack for delayed cancellation,reset934if (s != null && !(s instanceof Stream.PushedStream)) {935// Since PushStreams have no request body, then they have no936// corresponding entry in the window controller.937windowController.removeStream(streamid);938}939if (finalStream() && streams.isEmpty()) {940// should be only 1 stream, but there might be more if server push941close();942}943}944945/**946* Increments this connection's send Window by the amount in the given frame.947*/948private void handleWindowUpdate(WindowUpdateFrame f)949throws IOException950{951int amount = f.getUpdate();952if (amount <= 0) {953// ## temporarily disable to workaround a bug in Jetty where it954// ## sends Window updates with a 0 update value.955//protocolError(ErrorFrame.PROTOCOL_ERROR);956} else {957boolean success = windowController.increaseConnectionWindow(amount);958if (!success) {959protocolError(ErrorFrame.FLOW_CONTROL_ERROR); // overflow960}961}962}963964private void protocolError(int errorCode)965throws IOException966{967protocolError(errorCode, null);968}969970private void protocolError(int errorCode, String msg)971throws IOException972{973GoAwayFrame frame = new GoAwayFrame(0, errorCode);974sendFrame(frame);975shutdown(new IOException("protocol error" + (msg == null?"":(": " + msg))));976}977978private void handleSettings(SettingsFrame frame)979throws IOException980{981assert frame.streamid() == 0;982if (!frame.getFlag(SettingsFrame.ACK)) {983int newWindowSize = frame.getParameter(INITIAL_WINDOW_SIZE);984if (newWindowSize != -1) {985int oldWindowSize = serverSettings.getParameter(INITIAL_WINDOW_SIZE);986int diff = newWindowSize - oldWindowSize;987if (diff != 0) {988windowController.adjustActiveStreams(diff);989}990}991992serverSettings.update(frame);993sendFrame(new SettingsFrame(SettingsFrame.ACK));994}995}996997private void handlePing(PingFrame frame)998throws IOException999{1000frame.setFlag(PingFrame.ACK);1001sendUnorderedFrame(frame);1002}10031004private void handleGoAway(GoAwayFrame frame)1005throws IOException1006{1007shutdown(new IOException(1008String.valueOf(connection.channel().getLocalAddress())1009+": GOAWAY received"));1010}10111012/**1013* Max frame size we are allowed to send1014*/1015public int getMaxSendFrameSize() {1016int param = serverSettings.getParameter(MAX_FRAME_SIZE);1017if (param == -1) {1018param = DEFAULT_FRAME_SIZE;1019}1020return param;1021}10221023/**1024* Max frame size we will receive1025*/1026public int getMaxReceiveFrameSize() {1027return clientSettings.getParameter(MAX_FRAME_SIZE);1028}10291030private static final String CLIENT_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";10311032private static final byte[] PREFACE_BYTES =1033CLIENT_PREFACE.getBytes(StandardCharsets.ISO_8859_1);10341035/**1036* Sends Connection preface and Settings frame with current preferred1037* values1038*/1039private void sendConnectionPreface() throws IOException {1040Log.logTrace("{0}: start sending connection preface to {1}",1041connection.channel().getLocalAddress(),1042connection.address());1043SettingsFrame sf = new SettingsFrame(clientSettings);1044ByteBuffer buf = framesEncoder.encodeConnectionPreface(PREFACE_BYTES, sf);1045Log.logFrames(sf, "OUT");1046// send preface bytes and SettingsFrame together1047HttpPublisher publisher = publisher();1048publisher.enqueueUnordered(List.of(buf));1049publisher.signalEnqueued();1050// mark preface sent.1051framesController.markPrefaceSent();1052Log.logTrace("PREFACE_BYTES sent");1053Log.logTrace("Settings Frame sent");10541055// send a Window update for the receive buffer we are using1056// minus the initial 64 K -1 specified in protocol:1057// RFC 7540, Section 6.9.2:1058// "[...] the connection flow-control window is set to the default1059// initial window size until a WINDOW_UPDATE frame is received."1060//1061// Note that the default initial window size, not to be confused1062// with the initial window size, is defined by RFC 7540 as1063// 64K -1.1064final int len = windowUpdater.initialWindowSize - DEFAULT_INITIAL_WINDOW_SIZE;1065if (len != 0) {1066if (Log.channel()) {1067Log.logChannel("Sending initial connection window update frame: {0} ({1} - {2})",1068len, windowUpdater.initialWindowSize, DEFAULT_INITIAL_WINDOW_SIZE);1069}1070windowUpdater.sendWindowUpdate(len);1071}1072// there will be an ACK to the windows update - which should1073// cause any pending data stored before the preface was sent to be1074// flushed (see PrefaceController).1075Log.logTrace("finished sending connection preface");1076if (debug.on())1077debug.log("Triggering processing of buffered data"1078+ " after sending connection preface");1079subscriber.onNext(List.of(EMPTY_TRIGGER));1080}10811082/**1083* Returns an existing Stream with given id, or null if doesn't exist1084*/1085@SuppressWarnings("unchecked")1086<T> Stream<T> getStream(int streamid) {1087return (Stream<T>)streams.get(streamid);1088}10891090/**1091* Creates Stream with given id.1092*/1093final <T> Stream<T> createStream(Exchange<T> exchange) {1094Stream<T> stream = new Stream<>(this, exchange, windowController);1095return stream;1096}10971098<T> Stream.PushedStream<T> createPushStream(Stream<T> parent, Exchange<T> pushEx) {1099PushGroup<T> pg = parent.exchange.getPushGroup();1100return new Stream.PushedStream<>(pg, this, pushEx);1101}11021103<T> void putStream(Stream<T> stream, int streamid) {1104// increment the reference count on the HttpClientImpl1105// to prevent the SelectorManager thread from exiting until1106// the stream is closed.1107client().streamReference();1108streams.put(streamid, stream);1109}11101111/**1112* Encode the headers into a List<ByteBuffer> and then create HEADERS1113* and CONTINUATION frames from the list and return the List<Http2Frame>.1114*/1115private List<HeaderFrame> encodeHeaders(OutgoingHeaders<Stream<?>> frame) {1116// max value of frame size is clamped by default frame size to avoid OOM1117int bufferSize = Math.min(Math.max(getMaxSendFrameSize(), 1024), DEFAULT_FRAME_SIZE);1118List<ByteBuffer> buffers = encodeHeadersImpl(1119bufferSize,1120frame.getAttachment().getRequestPseudoHeaders(),1121frame.getUserHeaders(),1122frame.getSystemHeaders());11231124List<HeaderFrame> frames = new ArrayList<>(buffers.size());1125Iterator<ByteBuffer> bufIterator = buffers.iterator();1126HeaderFrame oframe = new HeadersFrame(frame.streamid(), frame.getFlags(), bufIterator.next());1127frames.add(oframe);1128while(bufIterator.hasNext()) {1129oframe = new ContinuationFrame(frame.streamid(), bufIterator.next());1130frames.add(oframe);1131}1132oframe.setFlag(HeaderFrame.END_HEADERS);1133return frames;1134}11351136// Dedicated cache for headers encoding ByteBuffer.1137// There can be no concurrent access to this buffer as all access to this buffer1138// and its content happen within a single critical code block section protected1139// by the sendLock. / (see sendFrame())1140// private final ByteBufferPool headerEncodingPool = new ByteBufferPool();11411142private ByteBuffer getHeaderBuffer(int size) {1143ByteBuffer buf = ByteBuffer.allocate(size);1144buf.limit(size);1145return buf;1146}11471148/*1149* Encodes all the headers from the given HttpHeaders into the given List1150* of buffers.1151*1152* From https://tools.ietf.org/html/rfc7540#section-8.1.2 :1153*1154* ...Just as in HTTP/1.x, header field names are strings of ASCII1155* characters that are compared in a case-insensitive fashion. However,1156* header field names MUST be converted to lowercase prior to their1157* encoding in HTTP/2...1158*/1159private List<ByteBuffer> encodeHeadersImpl(int bufferSize, HttpHeaders... headers) {1160ByteBuffer buffer = getHeaderBuffer(bufferSize);1161List<ByteBuffer> buffers = new ArrayList<>();1162for(HttpHeaders header : headers) {1163for (Map.Entry<String, List<String>> e : header.map().entrySet()) {1164String lKey = e.getKey().toLowerCase(Locale.US);1165List<String> values = e.getValue();1166for (String value : values) {1167hpackOut.header(lKey, value);1168while (!hpackOut.encode(buffer)) {1169buffer.flip();1170buffers.add(buffer);1171buffer = getHeaderBuffer(bufferSize);1172}1173}1174}1175}1176buffer.flip();1177buffers.add(buffer);1178return buffers;1179}118011811182private List<ByteBuffer> encodeHeaders(OutgoingHeaders<Stream<?>> oh, Stream<?> stream) {1183oh.streamid(stream.streamid);1184if (Log.headers()) {1185StringBuilder sb = new StringBuilder("HEADERS FRAME (stream=");1186sb.append(stream.streamid).append(")\n");1187Log.dumpHeaders(sb, " ", oh.getAttachment().getRequestPseudoHeaders());1188Log.dumpHeaders(sb, " ", oh.getSystemHeaders());1189Log.dumpHeaders(sb, " ", oh.getUserHeaders());1190Log.logHeaders(sb.toString());1191}1192List<HeaderFrame> frames = encodeHeaders(oh);1193return encodeFrames(frames);1194}11951196private List<ByteBuffer> encodeFrames(List<HeaderFrame> frames) {1197if (Log.frames()) {1198frames.forEach(f -> Log.logFrames(f, "OUT"));1199}1200return framesEncoder.encodeFrames(frames);1201}12021203private Stream<?> registerNewStream(OutgoingHeaders<Stream<?>> oh) {1204Stream<?> stream = oh.getAttachment();1205assert stream.streamid == 0;1206int streamid = nextstreamid;1207if (stream.registerStream(streamid, false)) {1208// set outgoing window here. This allows thread sending1209// body to proceed.1210nextstreamid += 2;1211windowController.registerStream(streamid, getInitialSendWindowSize());1212return stream;1213} else {1214stream.cancelImpl(new IOException("Request cancelled"));1215if (finalStream() && streams.isEmpty()) {1216close();1217}1218return null;1219}1220}12211222private final Object sendlock = new Object();12231224void sendFrame(Http2Frame frame) {1225try {1226HttpPublisher publisher = publisher();1227synchronized (sendlock) {1228if (frame instanceof OutgoingHeaders) {1229@SuppressWarnings("unchecked")1230OutgoingHeaders<Stream<?>> oh = (OutgoingHeaders<Stream<?>>) frame;1231Stream<?> stream = registerNewStream(oh);1232// provide protection from inserting unordered frames between Headers and Continuation1233if (stream != null) {1234publisher.enqueue(encodeHeaders(oh, stream));1235}1236} else {1237publisher.enqueue(encodeFrame(frame));1238}1239}1240publisher.signalEnqueued();1241} catch (IOException e) {1242if (!closed) {1243Log.logError(e);1244shutdown(e);1245}1246}1247}12481249private List<ByteBuffer> encodeFrame(Http2Frame frame) {1250Log.logFrames(frame, "OUT");1251return framesEncoder.encodeFrame(frame);1252}12531254void sendDataFrame(DataFrame frame) {1255try {1256HttpPublisher publisher = publisher();1257publisher.enqueue(encodeFrame(frame));1258publisher.signalEnqueued();1259} catch (IOException e) {1260if (!closed) {1261Log.logError(e);1262shutdown(e);1263}1264}1265}12661267/*1268* Direct call of the method bypasses synchronization on "sendlock" and1269* allowed only of control frames: WindowUpdateFrame, PingFrame and etc.1270* prohibited for such frames as DataFrame, HeadersFrame, ContinuationFrame.1271*/1272void sendUnorderedFrame(Http2Frame frame) {1273try {1274HttpPublisher publisher = publisher();1275publisher.enqueueUnordered(encodeFrame(frame));1276publisher.signalEnqueued();1277} catch (IOException e) {1278if (!closed) {1279Log.logError(e);1280shutdown(e);1281}1282}1283}12841285/**1286* A simple tube subscriber for reading from the connection flow.1287*/1288final class Http2TubeSubscriber implements TubeSubscriber {1289private volatile Flow.Subscription subscription;1290private volatile boolean completed;1291private volatile boolean dropped;1292private volatile Throwable error;1293private final ConcurrentLinkedQueue<ByteBuffer> queue1294= new ConcurrentLinkedQueue<>();1295private final SequentialScheduler scheduler =1296SequentialScheduler.lockingScheduler(this::processQueue);1297private final HttpClientImpl client;12981299Http2TubeSubscriber(HttpClientImpl client) {1300this.client = Objects.requireNonNull(client);1301}13021303final void processQueue() {1304try {1305while (!queue.isEmpty() && !scheduler.isStopped()) {1306ByteBuffer buffer = queue.poll();1307if (debug.on())1308debug.log("sending %d to Http2Connection.asyncReceive",1309buffer.remaining());1310asyncReceive(buffer);1311}1312} catch (Throwable t) {1313Throwable x = error;1314if (x == null) error = t;1315} finally {1316Throwable x = error;1317if (x != null) {1318if (debug.on()) debug.log("Stopping scheduler", x);1319scheduler.stop();1320Http2Connection.this.shutdown(x);1321}1322}1323}13241325private final void runOrSchedule() {1326if (client.isSelectorThread()) {1327scheduler.runOrSchedule(client.theExecutor());1328} else scheduler.runOrSchedule();1329}13301331@Override1332public void onSubscribe(Flow.Subscription subscription) {1333// supports being called multiple time.1334// doesn't cancel the previous subscription, since that is1335// most probably the same as the new subscription.1336assert this.subscription == null || dropped == false;1337this.subscription = subscription;1338dropped = false;1339// TODO FIXME: request(1) should be done by the delegate.1340if (!completed) {1341if (debug.on())1342debug.log("onSubscribe: requesting Long.MAX_VALUE for reading");1343subscription.request(Long.MAX_VALUE);1344} else {1345if (debug.on()) debug.log("onSubscribe: already completed");1346}1347}13481349@Override1350public void onNext(List<ByteBuffer> item) {1351if (debug.on()) debug.log(() -> "onNext: got " + Utils.remaining(item)1352+ " bytes in " + item.size() + " buffers");1353queue.addAll(item);1354runOrSchedule();1355}13561357@Override1358public void onError(Throwable throwable) {1359if (debug.on()) debug.log(() -> "onError: " + throwable);1360error = throwable;1361completed = true;1362runOrSchedule();1363}13641365@Override1366public void onComplete() {1367String msg = isActive()1368? "EOF reached while reading"1369: "Idle connection closed by HTTP/2 peer";1370if (debug.on()) debug.log(msg);1371error = new EOFException(msg);1372completed = true;1373runOrSchedule();1374}13751376@Override1377public void dropSubscription() {1378if (debug.on()) debug.log("dropSubscription");1379// we could probably set subscription to null here...1380// then we might not need the 'dropped' boolean?1381dropped = true;1382}1383}13841385synchronized boolean isActive() {1386return numReservedClientStreams > 0 || numReservedServerStreams > 0;1387}13881389@Override1390public final String toString() {1391return dbgString();1392}13931394final String dbgString() {1395return "Http2Connection("1396+ connection.getConnectionFlow() + ")";1397}13981399static class HeaderDecoder extends ValidatingHeadersConsumer {14001401HttpHeadersBuilder headersBuilder;14021403HeaderDecoder() {1404this.headersBuilder = new HttpHeadersBuilder();1405}14061407@Override1408public void onDecoded(CharSequence name, CharSequence value) {1409String n = name.toString();1410String v = value.toString();1411super.onDecoded(n, v);1412headersBuilder.addHeader(n, v);1413}14141415HttpHeaders headers() {1416return headersBuilder.build();1417}1418}14191420/*1421* Checks RFC 7540 rules (relaxed) compliance regarding pseudo-headers.1422*/1423static class ValidatingHeadersConsumer implements DecodingCallback {14241425private static final Set<String> PSEUDO_HEADERS =1426Set.of(":authority", ":method", ":path", ":scheme", ":status");14271428/** Used to check that if there are pseudo-headers, they go first */1429private boolean pseudoHeadersEnded;14301431/**1432* Called when END_HEADERS was received. This consumer may be invoked1433* again after reset() is called, but for a whole new set of headers.1434*/1435void reset() {1436pseudoHeadersEnded = false;1437}14381439@Override1440public void onDecoded(CharSequence name, CharSequence value)1441throws UncheckedIOException1442{1443String n = name.toString();1444if (n.startsWith(":")) {1445if (pseudoHeadersEnded) {1446throw newException("Unexpected pseudo-header '%s'", n);1447} else if (!PSEUDO_HEADERS.contains(n)) {1448throw newException("Unknown pseudo-header '%s'", n);1449}1450} else {1451pseudoHeadersEnded = true;1452if (!Utils.isValidName(n)) {1453throw newException("Bad header name '%s'", n);1454}1455}1456String v = value.toString();1457if (!Utils.isValidValue(v)) {1458throw newException("Bad header value '%s'", v);1459}1460}14611462private UncheckedIOException newException(String message, String header)1463{1464return new UncheckedIOException(1465new IOException(String.format(message, header)));1466}1467}14681469static final class ConnectionWindowUpdateSender extends WindowUpdateSender {14701471final int initialWindowSize;1472public ConnectionWindowUpdateSender(Http2Connection connection,1473int initialWindowSize) {1474super(connection, initialWindowSize);1475this.initialWindowSize = initialWindowSize;1476}14771478@Override1479int getStreamId() {1480return 0;1481}1482}14831484/**1485* Thrown when https handshake negotiates http/1.1 alpn instead of h21486*/1487static final class ALPNException extends IOException {1488private static final long serialVersionUID = 0L;1489final transient AbstractAsyncSSLConnection connection;14901491ALPNException(String msg, AbstractAsyncSSLConnection connection) {1492super(msg);1493this.connection = connection;1494}14951496AbstractAsyncSSLConnection getConnection() {1497return connection;1498}1499}1500}150115021503