All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.protocols.http2.Http2Channel Maven / Gradle / Ivy

There is a newer version: 2.3.18.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.protocols.http2;

import io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.UndertowOptions;
import io.undertow.connector.ByteBufferPool;
import io.undertow.connector.PooledByteBuffer;
import io.undertow.server.protocol.ParseTimeoutUpdater;
import io.undertow.server.protocol.framed.AbstractFramedChannel;
import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel;
import io.undertow.server.protocol.framed.FrameHeaderData;
import io.undertow.server.protocol.http2.Http2OpenListener;
import io.undertow.util.Attachable;
import io.undertow.util.AttachmentKey;
import io.undertow.util.AttachmentList;
import io.undertow.util.HeaderMap;
import io.undertow.util.HttpString;
import org.xnio.Bits;
import org.xnio.ChannelExceptionHandler;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.IoUtils;
import org.xnio.OptionMap;
import org.xnio.StreamConnection;
import org.xnio.channels.StreamSinkChannel;
import org.xnio.ssl.SslConnection;

import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * HTTP2 channel.
 *
 * @author Stuart Douglas
 */
public class Http2Channel extends AbstractFramedChannel implements Attachable {

    // HTTP2 MAX HEADER SIZE constants, to be properly replaced by UndertowOptions
    /**
     * Name of the system property for configuring HTTP2 max header size: {@code "io.undertow.http2-max-header-size"}.
     * 

This system property will be replaced by an {@link UndertowOptions Undertow option}.

*/ public static final String HTTP2_MAX_HEADER_SIZE_PROPERTY = "io.undertow.http2-max-header-size"; /** * Default value of HTTP2 max header size. If the system property {@code "io.undertow.http2-max-header-size"} is left * undefined, this value will be used for the corresponding {@link #HTTP2_MAX_HEADER_SIZE system property configuration}. */ private static final int DEFAULT_HTTP2_MAX_HEADER_SIZE = 20000; /** * System property {@code "io.undertow.http2-max-header-size"} for configuring the max header size configuration for HTTP2 * messages. *

* The effective maximum size for headers to be used by this channel will be the minimum of the three: this system property, * {@link UndertowOptions#MAX_HEADER_SIZE}, and {@link UndertowOptions#HTTP2_SETTINGS_HEADER_TABLE_SIZE}. When calculating * the minimum, only positive values are taken into account, as values of -1 indicate the absence of a limit. *

* To be replaced by an {@link UndertowOptions Undertow option} in a future release. */ private static final int HTTP2_MAX_HEADER_SIZE = Integer.getInteger(HTTP2_MAX_HEADER_SIZE_PROPERTY, DEFAULT_HTTP2_MAX_HEADER_SIZE); public static final String CLEARTEXT_UPGRADE_STRING = "h2c"; public static final HttpString METHOD = new HttpString(":method"); public static final HttpString PATH = new HttpString(":path"); public static final HttpString SCHEME = new HttpString(":scheme"); public static final HttpString AUTHORITY = new HttpString(":authority"); public static final HttpString STATUS = new HttpString(":status"); static final int FRAME_TYPE_DATA = 0x00; static final int FRAME_TYPE_HEADERS = 0x01; static final int FRAME_TYPE_PRIORITY = 0x02; static final int FRAME_TYPE_RST_STREAM = 0x03; static final int FRAME_TYPE_SETTINGS = 0x04; static final int FRAME_TYPE_PUSH_PROMISE = 0x05; static final int FRAME_TYPE_PING = 0x06; static final int FRAME_TYPE_GOAWAY = 0x07; static final int FRAME_TYPE_WINDOW_UPDATE = 0x08; static final int FRAME_TYPE_CONTINUATION = 0x09; public static final int ERROR_NO_ERROR = 0x00; public static final int ERROR_PROTOCOL_ERROR = 0x01; public static final int ERROR_INTERNAL_ERROR = 0x02; public static final int ERROR_FLOW_CONTROL_ERROR = 0x03; public static final int ERROR_SETTINGS_TIMEOUT = 0x04; public static final int ERROR_STREAM_CLOSED = 0x05; public static final int ERROR_FRAME_SIZE_ERROR = 0x06; public static final int ERROR_REFUSED_STREAM = 0x07; public static final int ERROR_CANCEL = 0x08; public static final int ERROR_COMPRESSION_ERROR = 0x09; public static final int ERROR_CONNECT_ERROR = 0x0a; public static final int ERROR_ENHANCE_YOUR_CALM = 0x0b; public static final int ERROR_INADEQUATE_SECURITY = 0x0c; static final int DATA_FLAG_END_STREAM = 0x1; static final int DATA_FLAG_END_SEGMENT = 0x2; static final int DATA_FLAG_PADDED = 0x8; static final int PING_FRAME_LENGTH = 8; static final int PING_FLAG_ACK = 0x1; static final int HEADERS_FLAG_END_STREAM = 0x1; static final int HEADERS_FLAG_END_SEGMENT = 0x2; static final int HEADERS_FLAG_END_HEADERS = 0x4; static final int HEADERS_FLAG_PADDED = 0x8; static final int HEADERS_FLAG_PRIORITY = 0x20; static final int SETTINGS_FLAG_ACK = 0x1; static final int CONTINUATION_FLAG_END_HEADERS = 0x4; public static final int DEFAULT_INITIAL_WINDOW_SIZE = 65535; static final byte[] PREFACE_BYTES = { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a}; public static final int DEFAULT_MAX_FRAME_SIZE = 16384; public static final int MAX_FRAME_SIZE = 16777215; public static final int FLOW_CONTROL_MIN_WINDOW = 2; // the time a discarded stream is kept at the stream cache before actually being discarded // (used for handling responses to streams that were closed via rst) private static final int STREAM_CACHE_EVICTION_TIME_MS = 60000; private Http2FrameHeaderParser frameParser; private final Map currentStreams = new ConcurrentHashMap<>(); private final String protocol; //local private final int encoderHeaderTableSize; private volatile boolean pushEnabled; private volatile int sendMaxConcurrentStreams = -1; private final int receiveMaxConcurrentStreams; private volatile int sendConcurrentStreams = 0; private volatile int receiveConcurrentStreams = 0; private final int initialReceiveWindowSize; private volatile int sendMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; private final int receiveMaxFrameSize; private int unackedReceiveMaxFrameSize = DEFAULT_MAX_FRAME_SIZE; //the old max frame size, this gets updated when our setting frame is acked private final int maxHeaders; private final int maxHeaderListSize; // the max number of rst frames received per window private final int maxRstFramesPerWindow; // the time window for counting rst frames received private final long rstFramesTimeWindow; // the time in milliseconds the last rst frame was received private long lastRstFrameMillis = System.currentTimeMillis(); // the total number of received rst frames during current time windows private int receivedRstFramesPerWindow; private static final AtomicIntegerFieldUpdater sendConcurrentStreamsAtomicUpdater = AtomicIntegerFieldUpdater.newUpdater( Http2Channel.class, "sendConcurrentStreams"); private static final AtomicIntegerFieldUpdater receiveConcurrentStreamsAtomicUpdater = AtomicIntegerFieldUpdater.newUpdater( Http2Channel.class, "receiveConcurrentStreams"); private boolean thisGoneAway = false; private boolean peerGoneAway = false; private boolean lastDataRead = false; private int streamIdCounter; private int lastGoodStreamId; private int lastAssignedStreamOtherSide; private final HpackDecoder decoder; private final HpackEncoder encoder; private final int maxPadding; private final Random paddingRandom; private int prefaceCount; private boolean initialSettingsReceived; //settings frame must be the first frame we relieve private Http2HeadersParser continuationParser = null; //state for continuation frames /** * We send the settings frame lazily, which is basically a big hack to work around broken IE support for push (it * dies if you send a settings frame with push enabled). * * Once IE is no longer broken this should be removed. */ private boolean initialSettingsSent = false; private final Map, Object> attachments = Collections.synchronizedMap(new HashMap, Object>()); private final ParseTimeoutUpdater parseTimeoutUpdater; private final Object flowControlLock = new Object(); /** * The initial window size for newly created channels, guarded by {@link #flowControlLock} */ private volatile int initialSendWindowSize = DEFAULT_INITIAL_WINDOW_SIZE; /** * How much data we can send to the remote endpoint, at the connection level, guarded by {@link #flowControlLock} */ private volatile long sendWindowSize = initialSendWindowSize; /** * How much data we have told the remote endpoint we are prepared to accept, guarded by {@link #flowControlLock} */ private volatile int receiveWindowSize; private final StreamCache resetStreamTracker = new StreamCache(); public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, OptionMap settings) { this(connectedStreamChannel, protocol, bufferPool, data, clientSide, fromUpgrade, true, null, settings); } public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, boolean prefaceRequired, OptionMap settings) { this(connectedStreamChannel, protocol, bufferPool, data, clientSide, fromUpgrade, prefaceRequired, null, settings); } public Http2Channel(StreamConnection connectedStreamChannel, String protocol, ByteBufferPool bufferPool, PooledByteBuffer data, boolean clientSide, boolean fromUpgrade, boolean prefaceRequired, ByteBuffer initialOtherSideSettings, OptionMap settings) { super(connectedStreamChannel, bufferPool, new Http2FramePriority(clientSide ? (fromUpgrade ? 3 : 1) : 2), data, settings); streamIdCounter = clientSide ? (fromUpgrade ? 3 : 1) : 2; pushEnabled = settings.get(UndertowOptions.HTTP2_SETTINGS_ENABLE_PUSH, true); this.initialReceiveWindowSize = settings.get(UndertowOptions.HTTP2_SETTINGS_INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE); this.receiveWindowSize = initialReceiveWindowSize; this.receiveMaxConcurrentStreams = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, UndertowOptions.DEFAULT_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); this.protocol = protocol == null ? Http2OpenListener.HTTP2 : protocol; this.maxHeaders = settings.get(UndertowOptions.MAX_HEADERS, clientSide ? -1 : UndertowOptions.DEFAULT_MAX_HEADERS); encoderHeaderTableSize = settings.get(UndertowOptions.HTTP2_SETTINGS_HEADER_TABLE_SIZE, Hpack.DEFAULT_TABLE_SIZE); receiveMaxFrameSize = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_FRAME_SIZE, DEFAULT_MAX_FRAME_SIZE); maxPadding = settings.get(UndertowOptions.HTTP2_PADDING_SIZE, 0); maxHeaderListSize = getMaxHeaderSize(settings); if(maxPadding > 0) { paddingRandom = new SecureRandom(); } else { paddingRandom = null; } maxRstFramesPerWindow = settings.get(UndertowOptions.MAX_RST_FRAMES_PER_WINDOW, settings.get(UndertowOptions.MAX_RST_FRAMES_PER_WINDOW, UndertowOptions.DEFAULT_MAX_RST_FRAMES_PER_WINDOW)); rstFramesTimeWindow = settings.get(UndertowOptions.RST_FRAMES_TIME_WINDOW, settings.get(UndertowOptions.RST_FRAMES_TIME_WINDOW, UndertowOptions.DEFAULT_RST_FRAMES_TIME_WINDOW)); this.decoder = new HpackDecoder(encoderHeaderTableSize); this.encoder = new HpackEncoder(encoderHeaderTableSize); if(!prefaceRequired) { prefaceCount = PREFACE_BYTES.length; } if (clientSide) { sendPreface(); prefaceCount = PREFACE_BYTES.length; sendSettings(); initialSettingsSent = true; if(fromUpgrade) { StreamHolder streamHolder = new StreamHolder((Http2StreamSinkChannel) null); streamHolder.sinkClosed = true; sendConcurrentStreamsAtomicUpdater.getAndIncrement(this); currentStreams.put(1, streamHolder); } } else if(fromUpgrade) { sendSettings(); initialSettingsSent = true; } if (initialOtherSideSettings != null) { Http2SettingsParser parser = new Http2SettingsParser(initialOtherSideSettings.remaining()); try { final Http2FrameHeaderParser headerParser = new Http2FrameHeaderParser(this, null); headerParser.length = initialOtherSideSettings.remaining(); parser.parse(initialOtherSideSettings, headerParser); updateSettings(parser.getSettings()); } catch (Throwable e) { IoUtils.safeClose(connectedStreamChannel); //should never happen throw new RuntimeException(e); } } int requestParseTimeout = settings.get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1); int requestIdleTimeout = settings.get(UndertowOptions.NO_REQUEST_TIMEOUT, -1); if(requestIdleTimeout < 0 && requestParseTimeout < 0) { this.parseTimeoutUpdater = null; } else { this.parseTimeoutUpdater = new ParseTimeoutUpdater(this, requestParseTimeout, requestIdleTimeout, new Runnable() { @Override public void run() { sendGoAway(ERROR_NO_ERROR); //just to make sure the connection is actually closed we give it 2 seconds //then we forcibly kill the connection getIoThread().executeAfter(new Runnable() { @Override public void run() { IoUtils.safeClose(Http2Channel.this); } }, 2, TimeUnit.SECONDS); } }); this.addCloseTask(new ChannelListener() { @Override public void handleEvent(Http2Channel channel) { parseTimeoutUpdater.close(); } }); } } private static int getMaxHeaderSize(OptionMap settings) { final int maxHeaderSizeConfig = settings.get(UndertowOptions.MAX_HEADER_SIZE, HTTP2_MAX_HEADER_SIZE); // soon to be removed http2 settings max header final int http2SettingsMaxHeaderListSize = settings.get(UndertowOptions.HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, HTTP2_MAX_HEADER_SIZE); if (HTTP2_MAX_HEADER_SIZE > 0) { if (maxHeaderSizeConfig > 0) { if (http2SettingsMaxHeaderListSize > 0) { return Math.min(Math.min(HTTP2_MAX_HEADER_SIZE, maxHeaderSizeConfig), http2SettingsMaxHeaderListSize); } else { return Math.min(HTTP2_MAX_HEADER_SIZE, maxHeaderSizeConfig); } } else { if (http2SettingsMaxHeaderListSize > 0) { return Math.min(HTTP2_MAX_HEADER_SIZE, http2SettingsMaxHeaderListSize); } else { return HTTP2_MAX_HEADER_SIZE; } } } else { if (maxHeaderSizeConfig > 0) { if (http2SettingsMaxHeaderListSize > 0) { return Math.min(maxHeaderSizeConfig, http2SettingsMaxHeaderListSize); } else { return maxHeaderSizeConfig; } } else { if (http2SettingsMaxHeaderListSize > 0) { return http2SettingsMaxHeaderListSize; } else { // replace any value <= 0 by -1 return -1; } } } } private void sendSettings() { List settings = new ArrayList<>(); settings.add(new Http2Setting(Http2Setting.SETTINGS_HEADER_TABLE_SIZE, encoderHeaderTableSize)); if(isClient()) { settings.add(new Http2Setting(Http2Setting.SETTINGS_ENABLE_PUSH, pushEnabled ? 1 : 0)); } settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_FRAME_SIZE, receiveMaxFrameSize)); settings.add(new Http2Setting(Http2Setting.SETTINGS_INITIAL_WINDOW_SIZE, initialReceiveWindowSize)); if(maxHeaderListSize > 0) { settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_HEADER_LIST_SIZE, maxHeaderListSize)); } if(receiveMaxConcurrentStreams > 0) { settings.add(new Http2Setting(Http2Setting.SETTINGS_MAX_CONCURRENT_STREAMS, receiveMaxConcurrentStreams)); } Http2SettingsStreamSinkChannel stream = new Http2SettingsStreamSinkChannel(this, settings); flushChannelIgnoreFailure(stream); } private void sendSettingsAck() { if(!initialSettingsSent) { sendSettings(); initialSettingsSent = true; } Http2SettingsStreamSinkChannel stream = new Http2SettingsStreamSinkChannel(this); flushChannelIgnoreFailure(stream); } private void flushChannelIgnoreFailure(StreamSinkChannel stream) { try { flushChannel(stream); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.ioException(e); } catch (Throwable t) { UndertowLogger.REQUEST_IO_LOGGER.handleUnexpectedFailure(t); } } private void flushChannel(StreamSinkChannel stream) throws IOException { stream.shutdownWrites(); if (!stream.flush()) { stream.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, writeExceptionHandler())); stream.resumeWrites(); } } private void sendPreface() { Http2PrefaceStreamSinkChannel preface = new Http2PrefaceStreamSinkChannel(this); flushChannelIgnoreFailure(preface); } @Override protected AbstractHttp2StreamSourceChannel createChannel(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException { AbstractHttp2StreamSourceChannel channel = createChannelImpl(frameHeaderData, frameData); if(channel instanceof Http2StreamSourceChannel) { if (parseTimeoutUpdater != null) { if (channel != null) { parseTimeoutUpdater.requestStarted(); } else if (currentStreams.isEmpty()) { parseTimeoutUpdater.failedParse(); } } } return channel; } protected AbstractHttp2StreamSourceChannel createChannelImpl(FrameHeaderData frameHeaderData, PooledByteBuffer frameData) throws IOException { Http2FrameHeaderParser frameParser = (Http2FrameHeaderParser) frameHeaderData; AbstractHttp2StreamSourceChannel channel; if (frameParser.type == FRAME_TYPE_DATA) { //DATA frames must be already associated with a connection. If it gets here then something is wrong //spec explicitly calls this out as a connection error sendGoAway(ERROR_PROTOCOL_ERROR); UndertowLogger.REQUEST_LOGGER.tracef("Dropping Frame of length %s for stream %s", frameParser.getFrameLength(), frameParser.streamId); return null; } //note that not all frame types are covered here, as some are only relevant to already active streams //if which case they are handled by the existing channel support switch (frameParser.type) { case FRAME_TYPE_CONTINUATION: case FRAME_TYPE_PUSH_PROMISE: { //this is some 'clever' code to deal with both types continuation (push_promise and headers) //if the continuation is not a push promise it falls through to the headers code if(frameParser.parser instanceof Http2PushPromiseParser) { if(!isClient()) { sendGoAway(ERROR_PROTOCOL_ERROR); throw UndertowMessages.MESSAGES.serverReceivedPushPromise(); } Http2PushPromiseParser pushPromiseParser = (Http2PushPromiseParser) frameParser.parser; channel = new Http2PushPromiseStreamSourceChannel(this, frameData, frameParser.getFrameLength(), pushPromiseParser.getHeaderMap(), pushPromiseParser.getPromisedStreamId(), frameParser.streamId); break; } //fall through } case FRAME_TYPE_HEADERS: { if(!isIdle(frameParser.streamId)) { //this is an existing stream //make sure it exists StreamHolder existing = currentStreams.get(frameParser.streamId); if (existing == null) { existing = resetStreamTracker.find(frameParser.streamId); } if(existing == null || existing.sourceClosed) { if (existing != null || resetStreamTracker.find(frameParser.streamId) == null) { sendGoAway(ERROR_PROTOCOL_ERROR); } frameData.close(); return null; } else if (existing.sourceChannel != null ){ //if exists //make sure it has END_STREAM set if(!Bits.allAreSet(frameParser.flags, HEADERS_FLAG_END_STREAM)) { sendGoAway(ERROR_PROTOCOL_ERROR); frameData.close(); return null; } } } else { if(frameParser.streamId < getLastAssignedStreamOtherSide()) { sendGoAway(ERROR_PROTOCOL_ERROR); frameData.close(); return null; } if(frameParser.streamId % 2 == (isClient() ? 1 : 0)) { sendGoAway(ERROR_PROTOCOL_ERROR); frameData.close(); return null; } } Http2HeadersParser parser = (Http2HeadersParser) frameParser.parser; channel = new Http2StreamSourceChannel(this, frameData, frameHeaderData.getFrameLength(), parser.getHeaderMap(), frameParser.streamId); updateStreamIdsCountersInHeaders(frameParser.streamId); StreamHolder holder = currentStreams.get(frameParser.streamId); if(holder == null) { holder = resetStreamTracker.find(frameParser.streamId); if (holder != null) { holder.sourceChannel = (Http2StreamSourceChannel) channel; } else { receiveConcurrentStreamsAtomicUpdater.getAndIncrement(this); currentStreams.put(frameParser.streamId, holder = new StreamHolder((Http2StreamSourceChannel) channel)); } } else { holder.sourceChannel = (Http2StreamSourceChannel) channel; } if (parser.isHeadersEndStream() && Bits.allAreSet(frameParser.flags, HEADERS_FLAG_END_HEADERS)) { channel.lastFrame(); holder.sourceChannel = null; //this is yuck if(!isClient() || !"100".equals(parser.getHeaderMap().getFirst(STATUS))) { holder.sourceClosed = true; if(holder.sinkClosed) { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); currentStreams.remove(frameParser.streamId); } } } if(parser.isInvalid()) { channel.rstStream(ERROR_PROTOCOL_ERROR); sendRstStream(frameParser.streamId, Http2Channel.ERROR_PROTOCOL_ERROR); channel = null; } if(parser.getDependentStreamId() == frameParser.streamId) { sendRstStream(frameParser.streamId, ERROR_PROTOCOL_ERROR); frameData.close(); return null; } break; } case FRAME_TYPE_RST_STREAM: { Http2RstStreamParser parser = (Http2RstStreamParser) frameParser.parser; if (frameParser.streamId == 0) { if(frameData != null) { frameData.close(); } throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(FRAME_TYPE_RST_STREAM)); } channel = new Http2RstStreamStreamSourceChannel(this, frameData, parser.getErrorCode(), frameParser.streamId); handleRstStream(frameParser.streamId, true); if(isIdle(frameParser.streamId)) { sendGoAway(ERROR_PROTOCOL_ERROR); } break; } case FRAME_TYPE_SETTINGS: { if (!Bits.anyAreSet(frameParser.flags, SETTINGS_FLAG_ACK)) { if(updateSettings(((Http2SettingsParser) frameParser.parser).getSettings())) { sendSettingsAck(); } } else if (frameHeaderData.getFrameLength() != 0) { sendGoAway(ERROR_FRAME_SIZE_ERROR); frameData.close(); return null; } channel = new Http2SettingsStreamSourceChannel(this, frameData, frameParser.getFrameLength(), ((Http2SettingsParser) frameParser.parser).getSettings()); unackedReceiveMaxFrameSize = receiveMaxFrameSize; break; } case FRAME_TYPE_PING: { Http2PingParser pingParser = (Http2PingParser) frameParser.parser; frameData.close(); boolean ack = Bits.anyAreSet(frameParser.flags, PING_FLAG_ACK); channel = new Http2PingStreamSourceChannel(this, pingParser.getData(), ack); if(!ack) { //not an ack from one of our pings, so send it back sendPing(pingParser.getData(), new Http2ControlMessageExceptionHandler(), true); } break; } case FRAME_TYPE_GOAWAY: { Http2GoAwayParser http2GoAwayParser = (Http2GoAwayParser) frameParser.parser; channel = new Http2GoAwayStreamSourceChannel(this, frameData, frameParser.getFrameLength(), http2GoAwayParser.getStatusCode(), http2GoAwayParser.getLastGoodStreamId()); peerGoneAway = true; //the peer is going away //everything is broken for(StreamHolder holder : currentStreams.values()) { if(holder.sourceChannel != null) { holder.sourceChannel.rstStream(); } if(holder.sinkChannel != null) { holder.sinkChannel.rstStream(); } } frameData.close(); sendGoAway(ERROR_NO_ERROR); break; } case FRAME_TYPE_WINDOW_UPDATE: { Http2WindowUpdateParser parser = (Http2WindowUpdateParser) frameParser.parser; handleWindowUpdate(frameParser.streamId, parser.getDeltaWindowSize()); frameData.close(); //we don't return window update notifications, they are handled internally return null; } case FRAME_TYPE_PRIORITY: { Http2PriorityParser parser = (Http2PriorityParser) frameParser.parser; if(parser.getStreamDependency() == frameParser.streamId) { //according to the spec this is a stream error sendRstStream(frameParser.streamId, ERROR_PROTOCOL_ERROR); return null; } frameData.close(); //we don't return priority notifications, they are handled internally return null; } default: { UndertowLogger.REQUEST_LOGGER.tracef("Dropping frame of length %s and type %s for stream %s as we do not understand this type of frame", frameParser.getFrameLength(), frameParser.type, frameParser.streamId); frameData.close(); return null; } } return channel; } @Override protected FrameHeaderData parseFrame(ByteBuffer data) throws IOException { Http2FrameHeaderParser frameParser; do { frameParser = parseFrameNoContinuation(data); // if the frame requires continuation and there is remaining data in the buffer // it should be consumed cos spec ensures the next frame is the continuation } while(frameParser != null && frameParser.getContinuationParser() != null && data.hasRemaining()); return frameParser; } private Http2FrameHeaderParser parseFrameNoContinuation(ByteBuffer data) throws IOException { if (prefaceCount < PREFACE_BYTES.length) { while (data.hasRemaining() && prefaceCount < PREFACE_BYTES.length) { if (data.get() != PREFACE_BYTES[prefaceCount]) { IoUtils.safeClose(getUnderlyingConnection()); throw UndertowMessages.MESSAGES.incorrectHttp2Preface(); } prefaceCount++; } } Http2FrameHeaderParser frameParser = this.frameParser; if (frameParser == null) { this.frameParser = frameParser = new Http2FrameHeaderParser(this, continuationParser); this.continuationParser = null; } if (!frameParser.handle(data)) { return null; } if (!initialSettingsReceived) { if (frameParser.type != FRAME_TYPE_SETTINGS) { UndertowLogger.REQUEST_IO_LOGGER.remoteEndpointFailedToSendInitialSettings(frameParser.type); //StringBuilder sb = new StringBuilder(); //while (data.hasRemaining()) { // sb.append((char)data.get()); // sb.append(" "); //} markReadsBroken(new IOException()); } else { initialSettingsReceived = true; } } this.frameParser = null; if (frameParser.getActualLength() > receiveMaxFrameSize && frameParser.getActualLength() > unackedReceiveMaxFrameSize) { sendGoAway(ERROR_FRAME_SIZE_ERROR); throw UndertowMessages.MESSAGES.http2FrameTooLarge(); } if (frameParser.getContinuationParser() != null) { this.continuationParser = frameParser.getContinuationParser(); } return frameParser; } protected void lastDataRead() { lastDataRead = true; if(!peerGoneAway) { //we just close the connection, as the peer has performed an unclean close IoUtils.safeClose(this); } else { peerGoneAway = true; if(!thisGoneAway) { //we send a goaway message, and then close sendGoAway(ERROR_CONNECT_ERROR); } } } @Override protected boolean isLastFrameReceived() { return lastDataRead; } @Override protected boolean isLastFrameSent() { return thisGoneAway; } @Override protected void handleBrokenSourceChannel(Throwable e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Closing HTTP2 channel to %s due to broken read side", getPeerAddress()); if (e instanceof ConnectionErrorException) { sendGoAway(((ConnectionErrorException) e).getCode(), new Http2ControlMessageExceptionHandler()); } else { sendGoAway(e instanceof ClosedChannelException ? Http2Channel.ERROR_CONNECT_ERROR : Http2Channel.ERROR_PROTOCOL_ERROR, new Http2ControlMessageExceptionHandler()); } } @Override protected void handleBrokenSinkChannel(Throwable e) { UndertowLogger.REQUEST_LOGGER.debugf(e, "Closing HTTP2 channel to %s due to broken write side", getPeerAddress()); //the write side is broken, so we can't even send GO_AWAY //just tear down the TCP connection IoUtils.safeClose(this); } @Override protected void closeSubChannels() { closeSubChannels(currentStreams); closeSubChannels(resetStreamTracker.getStreamHolders()); } private void closeSubChannels(Map streams) { for (Map.Entry e : streams.entrySet()) { StreamHolder holder = e.getValue(); AbstractHttp2StreamSourceChannel receiver = holder.sourceChannel; if(receiver != null) { receiver.markStreamBroken(); } Http2StreamSinkChannel sink = holder.sinkChannel; if(sink != null) { if (sink.isWritesShutdown()) { ChannelListeners.invokeChannelListener(sink.getIoThread(), sink, ((ChannelListener.SimpleSetter) sink.getWriteSetter()).get()); } IoUtils.safeClose(sink); } } } @Override protected Collection> getReceivers() { List> channels = new ArrayList<>(currentStreams.size()); for(Map.Entry entry : currentStreams.entrySet()) { if(!entry.getValue().sourceClosed) { final Http2StreamSourceChannel sourceChannel = entry.getValue().sourceChannel; if (sourceChannel != null) { channels.add(sourceChannel); } } } return channels; } /** * Setting have been received from the client * * @param settings */ boolean updateSettings(List settings) { for (Http2Setting setting : settings) { if (setting.getId() == Http2Setting.SETTINGS_INITIAL_WINDOW_SIZE) { synchronized (flowControlLock) { if (setting.getValue() > Integer.MAX_VALUE) { sendGoAway(ERROR_FLOW_CONTROL_ERROR); return false; } initialSendWindowSize = (int) setting.getValue(); } } else if (setting.getId() == Http2Setting.SETTINGS_MAX_FRAME_SIZE) { if(setting.getValue() > MAX_FRAME_SIZE || setting.getValue() < DEFAULT_MAX_FRAME_SIZE) { UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid value received for SETTINGS_MAX_FRAME_SIZE " + setting.getValue()); sendGoAway(ERROR_PROTOCOL_ERROR); return false; } sendMaxFrameSize = (int) setting.getValue(); } else if (setting.getId() == Http2Setting.SETTINGS_HEADER_TABLE_SIZE) { synchronized (this) { encoder.setMaxTableSize((int) setting.getValue()); } } else if (setting.getId() == Http2Setting.SETTINGS_ENABLE_PUSH) { int result = (int) setting.getValue(); //we allow the remote endpoint to disable push //but not enable it if it has been explictly disabled on this side if(result == 0) { pushEnabled = false; } else if(result != 1) { //invalid value UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid value received for SETTINGS_ENABLE_PUSH " + result); sendGoAway(ERROR_PROTOCOL_ERROR); return false; } } else if (setting.getId() == Http2Setting.SETTINGS_MAX_CONCURRENT_STREAMS) { sendMaxConcurrentStreams = (int) setting.getValue(); } //ignore the rest for now } return true; } public int getHttp2Version() { return 3; } public int getInitialSendWindowSize() { return initialSendWindowSize; } public int getInitialReceiveWindowSize() { return initialReceiveWindowSize; } public int getSendMaxConcurrentStreams() { return sendMaxConcurrentStreams; } public void setSendMaxConcurrentStreams(int sendMaxConcurrentStreams) { this.sendMaxConcurrentStreams = sendMaxConcurrentStreams; sendSettings(); } public int getReceiveMaxConcurrentStreams() { return receiveMaxConcurrentStreams; } public void handleWindowUpdate(int streamId, int deltaWindowSize) throws IOException { if (streamId == 0) { if (deltaWindowSize == 0) { UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid flow-control window increment of 0 received with WINDOW_UPDATE frame for connection"); sendGoAway(ERROR_PROTOCOL_ERROR); return; } synchronized (flowControlLock) { boolean exhausted = sendWindowSize <= FLOW_CONTROL_MIN_WINDOW; // sendWindowSize += deltaWindowSize; if (exhausted) { notifyFlowControlAllowed(); } if (sendWindowSize > Integer.MAX_VALUE) { sendGoAway(ERROR_FLOW_CONTROL_ERROR); } } } else { if (deltaWindowSize == 0) { UndertowLogger.REQUEST_IO_LOGGER.debug("Invalid flow-control window increment of 0 received with WINDOW_UPDATE frame for stream " + streamId); sendRstStream(streamId, ERROR_PROTOCOL_ERROR); return; } StreamHolder holder = currentStreams.get(streamId); Http2StreamSinkChannel stream = holder != null ? holder.sinkChannel : null; if (stream == null) { if (resetStreamTracker.find(streamId) == null && isIdle(streamId)) { sendGoAway(ERROR_PROTOCOL_ERROR); } } else { stream.updateFlowControlWindow(deltaWindowSize); } } } synchronized void notifyFlowControlAllowed() throws IOException { super.recalculateHeldFrames(); } public void sendPing(byte[] data) { sendPing(data, new Http2ControlMessageExceptionHandler()); } public void sendPing(byte[] data, final ChannelExceptionHandler exceptionHandler) { sendPing(data, exceptionHandler, false); } void sendPing(byte[] data, final ChannelExceptionHandler exceptionHandler, boolean ack) { Http2PingStreamSinkChannel ping = new Http2PingStreamSinkChannel(this, data, ack); try { ping.shutdownWrites(); if (!ping.flush()) { ping.getWriteSetter().set(ChannelListeners.flushingChannelListener(null, exceptionHandler)); ping.resumeWrites(); } } catch (IOException e) { if(exceptionHandler != null) { exceptionHandler.handleException(ping, e); } else { UndertowLogger.REQUEST_LOGGER.debug("Failed to send ping and no exception handler set", e); } } catch (Throwable t) { if(exceptionHandler != null) { exceptionHandler.handleException(ping, new IOException(t)); } else { UndertowLogger.REQUEST_LOGGER.debug("Failed to send ping and no exception handler set", t); } } } public void sendGoAway(int status) { sendGoAway(status, new Http2ControlMessageExceptionHandler()); } public void sendGoAway(int status, final ChannelExceptionHandler exceptionHandler) { if (thisGoneAway) { return; } thisGoneAway = true; if(UndertowLogger.REQUEST_IO_LOGGER.isTraceEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.tracef(new ClosedChannelException(), "Sending goaway on channel %s", this); } Http2GoAwayStreamSinkChannel goAway = new Http2GoAwayStreamSinkChannel(this, status, getLastGoodStreamId()); try { goAway.shutdownWrites(); if (!goAway.flush()) { goAway.getWriteSetter().set(ChannelListeners.flushingChannelListener(new ChannelListener() { @Override public void handleEvent(Channel channel) { IoUtils.safeClose(Http2Channel.this); } }, exceptionHandler)); goAway.resumeWrites(); } else { IoUtils.safeClose(this); } } catch (IOException e) { exceptionHandler.handleException(goAway, e); } catch (Throwable t) { exceptionHandler.handleException(goAway, new IOException(t)); } } public void sendUpdateWindowSize(int streamId, int delta) throws IOException { Http2WindowUpdateStreamSinkChannel windowUpdateStreamSinkChannel = new Http2WindowUpdateStreamSinkChannel(this, streamId, delta); flushChannel(windowUpdateStreamSinkChannel); } public SSLSession getSslSession() { StreamConnection con = getUnderlyingConnection(); if (con instanceof SslConnection) { return ((SslConnection) con).getSslSession(); } return null; } public void updateReceiveFlowControlWindow(int read) throws IOException { if (read <= 0) { return; } int delta = -1; synchronized (flowControlLock) { receiveWindowSize -= read; //TODO: make this configurable, we should be able to set the policy that is used to determine when to update the window size int initialWindowSize = this.initialReceiveWindowSize; if (receiveWindowSize < (initialWindowSize / 2)) { delta = initialWindowSize - receiveWindowSize; receiveWindowSize += delta; } } if(delta > 0) { sendUpdateWindowSize(0, delta); } } /** * Creates a strema using a HEADERS frame * * @param requestHeaders * @return * @throws IOException */ public synchronized Http2HeadersStreamSinkChannel createStream(HeaderMap requestHeaders) throws IOException { if (!isClient()) { throw UndertowMessages.MESSAGES.headersStreamCanOnlyBeCreatedByClient(); } if (!isOpen()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } sendConcurrentStreamsAtomicUpdater.incrementAndGet(this); if(sendMaxConcurrentStreams > 0 && sendConcurrentStreams > sendMaxConcurrentStreams) { throw UndertowMessages.MESSAGES.streamLimitExceeded(); } int streamId = streamIdCounter; streamIdCounter += 2; Http2HeadersStreamSinkChannel http2SynStreamStreamSinkChannel = new Http2HeadersStreamSinkChannel(this, streamId, requestHeaders); currentStreams.put(streamId, new StreamHolder(http2SynStreamStreamSinkChannel)); return http2SynStreamStreamSinkChannel; } public String getProtocolRequestId() { return Integer.toString(streamIdCounter); } /** * Adds a received pushed stream into the current streams for a client. The * stream is added into the currentStream and lastAssignedStreamOtherSide is incremented. * * @param pushedStreamId The pushed stream returned by the server * @return true if pushedStreamId can be added, false if invalid * @throws IOException General error like not being a client or odd stream id */ public synchronized boolean addPushPromiseStream(int pushedStreamId) throws IOException { if (!isClient() || pushedStreamId % 2 != 0) { throw UndertowMessages.MESSAGES.pushPromiseCanOnlyBeCreatedByServer(); } if (!isOpen()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (!isIdle(pushedStreamId)) { UndertowLogger.REQUEST_IO_LOGGER.debugf("Non idle streamId %d received from the server as a pushed stream.", pushedStreamId); return false; } StreamHolder holder = new StreamHolder((Http2HeadersStreamSinkChannel) null); holder.sinkClosed = true; lastAssignedStreamOtherSide = Math.max(lastAssignedStreamOtherSide, pushedStreamId); currentStreams.put(pushedStreamId, holder); return true; } private synchronized int getLastAssignedStreamOtherSide() { return lastAssignedStreamOtherSide; } private synchronized int getLastGoodStreamId() { return lastGoodStreamId; } /** * Updates the lastGoodStreamId (last request ID to send in goaway frames), * and lastAssignedStreamOtherSide (the last received streamId from the other * side to check if it's idle). The lastAssignedStreamOtherSide in a server * is the same as lastGoodStreamId but in a client push promises can be * received and check for idle is different. * * @param streamNo The received streamId for the client or the server */ private synchronized void updateStreamIdsCountersInHeaders(int streamNo) { if (streamNo % 2 != 0) { // the last good stream is always the last client ID sent by the client or received by the server lastGoodStreamId = Math.max(lastGoodStreamId, streamNo); if (!isClient()) { // server received client request ID => update the last assigned for the server lastAssignedStreamOtherSide = lastGoodStreamId; } } else if (isClient()) { // client received push promise => update the last assigned for the client lastAssignedStreamOtherSide = Math.max(lastAssignedStreamOtherSide, streamNo); } } public synchronized Http2HeadersStreamSinkChannel sendPushPromise(int associatedStreamId, HeaderMap requestHeaders, HeaderMap responseHeaders) throws IOException { if (!isOpen()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (isClient()) { throw UndertowMessages.MESSAGES.pushPromiseCanOnlyBeCreatedByServer(); } sendConcurrentStreamsAtomicUpdater.incrementAndGet(this); if(sendMaxConcurrentStreams > 0 && sendConcurrentStreams > sendMaxConcurrentStreams) { throw UndertowMessages.MESSAGES.streamLimitExceeded(); } int streamId = streamIdCounter; streamIdCounter += 2; Http2PushPromiseStreamSinkChannel pushPromise = new Http2PushPromiseStreamSinkChannel(this, requestHeaders, associatedStreamId, streamId); flushChannel(pushPromise); Http2HeadersStreamSinkChannel http2SynStreamStreamSinkChannel = new Http2HeadersStreamSinkChannel(this, streamId, responseHeaders); currentStreams.put(streamId, new StreamHolder(http2SynStreamStreamSinkChannel)); return http2SynStreamStreamSinkChannel; } /** * Try and decrement the send window by the given amount of bytes. * * @param bytesToGrab The amount of bytes the sender is trying to send * @return The actual amount of bytes the sender can send */ int grabFlowControlBytes(int bytesToGrab) { if(bytesToGrab <= 0) { return 0; } int min; synchronized (flowControlLock) { min = (int) Math.min(bytesToGrab, sendWindowSize); if (bytesToGrab > FLOW_CONTROL_MIN_WINDOW && min <= FLOW_CONTROL_MIN_WINDOW) { //this can cause problems with padding, so we just return 0 return 0; } min = Math.min(sendMaxFrameSize, min); sendWindowSize -= min; } return min; } void registerStreamSink(Http2HeadersStreamSinkChannel synResponse) { StreamHolder existing = currentStreams.get(synResponse.getStreamId()); if(existing == null) { throw UndertowMessages.MESSAGES.streamNotRegistered(); } existing.sinkChannel = synResponse; } void removeStreamSink(int streamId) { StreamHolder existing = currentStreams.get(streamId); if(existing == null) { return; } existing.sinkClosed = true; existing.sinkChannel = null; if(existing.sourceClosed) { if(streamId % 2 == (isClient() ? 1 : 0)) { sendConcurrentStreamsAtomicUpdater.getAndDecrement(this); } else { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); } currentStreams.remove(streamId); } if(isLastFrameReceived() && currentStreams.isEmpty()) { sendGoAway(ERROR_NO_ERROR); } else if(parseTimeoutUpdater != null && currentStreams.isEmpty()) { parseTimeoutUpdater.connectionIdle(); } } public boolean isClient() { return streamIdCounter % 2 == 1; } HpackEncoder getEncoder() { return encoder; } HpackDecoder getDecoder() { return decoder; } int getMaxHeaders() { return maxHeaders; } int getPaddingBytes() { if(paddingRandom == null) { return 0; } return paddingRandom.nextInt(maxPadding); } @Override public T getAttachment(AttachmentKey key) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } return (T) attachments.get(key); } @Override public List getAttachmentList(AttachmentKey> key) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } Object o = attachments.get(key); if (o == null) { return Collections.emptyList(); } return (List) o; } @Override public T putAttachment(AttachmentKey key, T value) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } return key.cast(attachments.put(key, key.cast(value))); } @Override public T removeAttachment(AttachmentKey key) { return key.cast(attachments.remove(key)); } @Override public void addToAttachmentList(AttachmentKey> key, T value) { if (key == null) { throw UndertowMessages.MESSAGES.argumentCannotBeNull("key"); } final Map, Object> attachments = this.attachments; synchronized (attachments) { final List list = key.cast(attachments.get(key)); if (list == null) { final AttachmentList newList = new AttachmentList<>((Class) Object.class); attachments.put(key, newList); newList.add(value); } else { list.add(value); } } } public void sendRstStream(int streamId, int statusCode) { if(!isOpen()) { //no point sending if the channel is closed return; } handleRstStream(streamId, false); if(UndertowLogger.REQUEST_IO_LOGGER.isDebugEnabled()) { UndertowLogger.REQUEST_IO_LOGGER.debugf(new ClosedChannelException(), "Sending rststream on channel %s stream %s", this, streamId); } Http2RstStreamSinkChannel channel = new Http2RstStreamSinkChannel(this, streamId, statusCode); flushChannelIgnoreFailure(channel); } private StreamHolder handleRstStream(int streamId, boolean receivedRst) { final StreamHolder holder = currentStreams.remove(streamId); if(holder != null) { resetStreamTracker.store(streamId, holder); if(streamId % 2 == (isClient() ? 1 : 0)) { sendConcurrentStreamsAtomicUpdater.getAndDecrement(this); } else { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); } if (holder.sinkChannel != null) { holder.sinkChannel.rstStream(); } if (holder.sourceChannel != null) { holder.sourceChannel.rstStream(); } if (receivedRst) { if (holder.sinkChannel != null && holder.sourceChannel == null) { //Server side originated, no input from client other than RST //this can happen on page refresh when push happens, but client //still has valid cache entry holder.resetByPeer = receivedRst; } else { handleRstWindow(); } } } else if(receivedRst){ final StreamHolder resetStream = resetStreamTracker.find(streamId); if(resetStream != null && resetStream.resetByPeer) { //This means other side reset stream at some point. //depending on peer or network latency our frames might be late and //cause other end to flare up with RST, this RST can be safely ignored. //TODO: do we need to check error code? } else { handleRstWindow(); } } return holder; } private void handleRstWindow() { long currentTimeMillis = System.currentTimeMillis(); // reset the window tracking if (currentTimeMillis - lastRstFrameMillis >= rstFramesTimeWindow) { lastRstFrameMillis = currentTimeMillis; receivedRstFramesPerWindow = 1; } else { receivedRstFramesPerWindow++; if (receivedRstFramesPerWindow > maxRstFramesPerWindow) { sendGoAway(Http2Channel.ERROR_ENHANCE_YOUR_CALM); UndertowLogger.REQUEST_IO_LOGGER.debugf( "Reached maximum number of rst frames %s during %s ms, sending GO_AWAY 11", maxRstFramesPerWindow, rstFramesTimeWindow); } } } /** * Creates a response stream to respond to the initial HTTP upgrade * * @return */ public synchronized Http2HeadersStreamSinkChannel createInitialUpgradeResponseStream() { if (lastGoodStreamId != 0) { throw new IllegalStateException(); } updateStreamIdsCountersInHeaders(1); Http2HeadersStreamSinkChannel stream = new Http2HeadersStreamSinkChannel(this, 1); StreamHolder streamHolder = new StreamHolder(stream); streamHolder.sourceClosed = true; currentStreams.put(1, streamHolder); receiveConcurrentStreamsAtomicUpdater.getAndIncrement(this); return stream; } public boolean isPushEnabled() { return pushEnabled; } public boolean isPeerGoneAway() { return peerGoneAway; } public boolean isThisGoneAway() { return thisGoneAway; } Http2StreamSourceChannel removeStreamSource(int streamId) { StreamHolder existing = currentStreams.get(streamId); if (existing == null) { existing = resetStreamTracker.find(streamId); return existing == null? null : existing.sourceChannel; } existing.sourceClosed = true; Http2StreamSourceChannel ret = existing.sourceChannel; existing.sourceChannel = null; if(existing.sinkClosed) { if(streamId % 2 == (isClient() ? 1 : 0)) { sendConcurrentStreamsAtomicUpdater.getAndDecrement(this); } else { receiveConcurrentStreamsAtomicUpdater.getAndDecrement(this); } currentStreams.remove(streamId); } return ret; } Http2StreamSourceChannel getIncomingStream(int streamId) { StreamHolder existing = currentStreams.get(streamId); if(existing == null){ existing = resetStreamTracker.find(streamId); if (existing == null) { return null; } } return existing.sourceChannel; } private class Http2ControlMessageExceptionHandler implements ChannelExceptionHandler { @Override public void handleException(AbstractHttp2StreamSinkChannel channel, IOException exception) { IoUtils.safeClose(channel); handleBrokenSinkChannel(exception); } } public int getReceiveMaxFrameSize() { return receiveMaxFrameSize; } public int getSendMaxFrameSize() { return sendMaxFrameSize; } public String getProtocol() { return protocol; } private synchronized boolean isIdle(int streamNo) { if(streamNo % 2 == streamIdCounter % 2) { // our side is controlled by us in the generated streamIdCounter return streamNo >= streamIdCounter; } else { // the other side should increase lastAssignedStreamOtherSide all the time return streamNo > lastAssignedStreamOtherSide; } } int getMaxHeaderListSize() { return maxHeaderListSize; } private static final class StreamHolder { boolean sourceClosed = false; boolean sinkClosed = false; /** * This flag is set only in case of short lived server push that was reset by remote end. */ boolean resetByPeer = false; Http2StreamSourceChannel sourceChannel; Http2StreamSinkChannel sinkChannel; StreamHolder(Http2StreamSourceChannel sourceChannel) { this.sourceChannel = sourceChannel; } StreamHolder(Http2StreamSinkChannel sinkChannel) { this.sinkChannel = sinkChannel; } } // cache that keeps track of streams until they can be evicted @see Http2Channel#RST_STREAM_EVICATION_TIME private static final class StreamCache { private Map streamHolders = new ConcurrentHashMap<>(); // entries are sorted per creation time private Queue entries = new ConcurrentLinkedQueue<>(); private void store(int streamId, StreamHolder streamHolder) { if (streamHolder == null) { return; } streamHolders.put(streamId, streamHolder); entries.add(new StreamCacheEntry(streamId)); } private StreamHolder find(int streamId) { for (Iterator iterator = entries.iterator(); iterator.hasNext();) { StreamCacheEntry entry = iterator.next(); if (entry.shouldEvict()) { iterator.remove(); StreamHolder holder = streamHolders.remove(entry.streamId); AbstractHttp2StreamSourceChannel receiver = holder.sourceChannel; if(receiver != null) { IoUtils.safeClose(receiver); } Http2StreamSinkChannel sink = holder.sinkChannel; if(sink != null) { if (sink.isWritesShutdown()) { ChannelListeners.invokeChannelListener(sink.getIoThread(), sink, ((ChannelListener.SimpleSetter) sink.getWriteSetter()).get()); } IoUtils.safeClose(sink); } } else break; } return streamHolders.get(streamId); } private Map getStreamHolders() { return streamHolders; } } private static class StreamCacheEntry { int streamId; long time; StreamCacheEntry(int streamId) { this.streamId = streamId; this.time = System.currentTimeMillis(); } public boolean shouldEvict() { return System.currentTimeMillis() - time > STREAM_CACHE_EVICTION_TIME_MS; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy