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

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

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

The 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); holder.sent = true; } 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 //NOTE: this is specific case when its set. holder.resetByPeer = receivedRst; } else { handleRstWindow(); } } } else if(receivedRst){ final StreamHolder resetStream = resetStreamTracker.find(streamId, true); 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; /** * flag indicate whether our side originated. This is done for caching purposes, handlng differs. */ boolean sent = false; Http2StreamSourceChannel sourceChannel; Http2StreamSinkChannel sinkChannel; StreamHolder(Http2StreamSourceChannel sourceChannel) { this.sourceChannel = sourceChannel; } StreamHolder(Http2StreamSinkChannel sinkChannel) { this.sinkChannel = sinkChannel; } @Override public String toString() { return "StreamHolder [sourceClosed=" + sourceClosed + ", sinkClosed=" + sinkClosed + ", resetByPeer=" + resetByPeer + ", sent=" + sent + ", sourceChannel=" + sourceChannel + ", 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)); } /** * Method will return only sent * @param streamId * @return */ private StreamHolder find(final int streamId) { return find(streamId, false); } private StreamHolder find(final int streamId, final boolean all) { for (Iterator iterator = entries.iterator(); iterator.hasNext();) { StreamCacheEntry entry = iterator.next(); if (entry.shouldEvict()) { iterator.remove(); StreamHolder holder = streamHolders.remove(entry.streamId); if(!holder.sent || holder.resetByPeer) { //if its not our end of chain, its just cached, so we only cache for purpose of // handling eager RST continue; } 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; } final StreamHolder holder = streamHolders.get(streamId); if(holder != null && (!all && !holder.sent)) { return null; } else { return holder; } } 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