package org.glassfish.grizzly.http2;
import static org.glassfish.grizzly.http2.Http2BaseFilter.PRI_PAYLOAD;
import static org.glassfish.grizzly.http2.frames.SettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE;
import static org.glassfish.grizzly.http2.frames.SettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS;
import static org.glassfish.grizzly.http2.frames.SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.CloseListener;
import org.glassfish.grizzly.CloseType;
import org.glassfish.grizzly.Closeable;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.Context;
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.IOEvent;
import org.glassfish.grizzly.IOEventLifeCycleListener;
import org.glassfish.grizzly.ProcessorExecutor;
import org.glassfish.grizzly.WriteResult;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.http.HttpContent;
import org.glassfish.grizzly.http.HttpContext;
import org.glassfish.grizzly.http.HttpHeader;
import org.glassfish.grizzly.http.HttpPacket;
import org.glassfish.grizzly.http.HttpRequestPacket;
import org.glassfish.grizzly.http.HttpResponsePacket;
import org.glassfish.grizzly.http.Method;
import org.glassfish.grizzly.http.Protocol;
import org.glassfish.grizzly.http.util.MimeHeaders;
import org.glassfish.grizzly.http2.frames.ContinuationFrame;
import org.glassfish.grizzly.http2.frames.DataFrame;
import org.glassfish.grizzly.http2.frames.ErrorCode;
import org.glassfish.grizzly.http2.frames.GoAwayFrame;
import org.glassfish.grizzly.http2.frames.HeaderBlockFragment;
import org.glassfish.grizzly.http2.frames.HeadersFrame;
import org.glassfish.grizzly.http2.frames.Http2Frame;
import org.glassfish.grizzly.http2.frames.PingFrame;
import org.glassfish.grizzly.http2.frames.PriorityFrame;
import org.glassfish.grizzly.http2.frames.PushPromiseFrame;
import org.glassfish.grizzly.http2.frames.RstStreamFrame;
import org.glassfish.grizzly.http2.frames.SettingsFrame;
import org.glassfish.grizzly.http2.frames.SettingsFrame.SettingsFrameBuilder;
import org.glassfish.grizzly.http2.frames.UnknownFrame;
import org.glassfish.grizzly.http2.frames.WindowUpdateFrame;
import org.glassfish.grizzly.impl.FutureImpl;
import org.glassfish.grizzly.memory.Buffers;
import org.glassfish.grizzly.memory.MemoryManager;
import org.glassfish.grizzly.ssl.SSLBaseFilter;
import org.glassfish.grizzly.utils.Futures;
import org.glassfish.grizzly.utils.Holder;
import org.glassfish.grizzly.utils.NullaryFunction;
public class Http2Session {
private static final Logger LOGGER = Grizzly.logger(Http2Session.class);
private final boolean isServer;
private final Connection<?> connection;
Http2State http2State;
private HeadersDecoder ;
private HeadersEncoder ;
private final ReentrantLock deflaterLock = new ReentrantLock();
int lastPeerStreamId;
private int lastLocalStreamId;
private boolean pushEnabled = true;
private final ReentrantLock newClientStreamLock = new ReentrantLock();
private volatile FilterChain http2StreamChain;
private volatile FilterChain htt2SessionChain;
private final AtomicInteger concurrentStreamsCount = new AtomicInteger(0);
private final TreeMap<Integer, Http2Stream> streamsMap = new TreeMap<>();
final List<Http2Stream> streamsToFlushInput = new ArrayList<>();
protected final List<Http2Frame> = new ArrayList<>(2);
private final Object sessionLock = new Object();
private volatile CloseType closeFlag;
private int peerStreamWindowSize = getDefaultStreamWindowSize();
private volatile int localStreamWindowSize = getDefaultStreamWindowSize();
private volatile int localConnectionWindowSize = getDefaultConnectionWindowSize();
private volatile int ;
private volatile int localMaxConcurrentStreams = getDefaultMaxConcurrentStreams();
private int peerMaxConcurrentStreams = getDefaultMaxConcurrentStreams();
private final Http2SessionOutputSink outputSink;
private final Http2Configuration http2Configuration;
private volatile int streamsHighWaterMark;
private int checkCount;
private int goingAwayLastStreamId = Integer.MIN_VALUE;
private FutureImpl<Http2Session> sessionClosed;
private volatile boolean isPrefaceReceived;
private volatile boolean isPrefaceSent;
public static Http2Session get(final Connection connection) {
final Http2State http2State = Http2State.get(connection);
return http2State != null ? http2State.getHttp2Session() : null;
}
static void bind(final Connection connection, final Http2Session http2Session) {
Http2State.obtain(connection).setHttp2Session(http2Session);
}
private final Holder<?> addressHolder;
final Http2BaseFilter handlerFilter;
private final int localMaxFramePayloadSize;
private int peerMaxFramePayloadSize = getSpecDefaultFramePayloadSize();
private boolean isFirstInFrame = true;
private volatile SSLBaseFilter sslFilter;
private final AtomicInteger unackedReadBytes = new AtomicInteger();
public Http2Session(final Connection<?> connection, final boolean isServer, final Http2BaseFilter handlerFilter) {
this.connection = connection;
final FilterChain chain = (FilterChain) connection.getProcessor();
final int sslIdx = chain.indexOfType(SSLBaseFilter.class);
if (sslIdx != -1) {
sslFilter = (SSLBaseFilter) chain.get(sslIdx);
}
this.isServer = isServer;
this.handlerFilter = handlerFilter;
this.http2Configuration = handlerFilter.getConfiguration();
if (this.http2Configuration.getMaxConcurrentStreams() != -1) {
this.setLocalMaxConcurrentStreams(this.http2Configuration.getMaxConcurrentStreams());
} else {
this.setLocalMaxConcurrentStreams(this.getDefaultMaxConcurrentStreams());
}
if (this.http2Configuration.getInitialWindowSize() != -1) {
this.localStreamWindowSize = this.http2Configuration.getInitialWindowSize();
}
final int customMaxFramePayloadSz = handlerFilter.getLocalMaxFramePayloadSize() > 0 ? handlerFilter.getLocalMaxFramePayloadSize() : -1;
this.localMaxFramePayloadSize = customMaxFramePayloadSz >= getSpecMinFramePayloadSize() && customMaxFramePayloadSz <= getSpecMaxFramePayloadSize()
? customMaxFramePayloadSz
: getSpecDefaultFramePayloadSize();
this.maxHeaderListSize = handlerFilter.getConfiguration().getMaxHeaderListSize();
if (isServer) {
this.lastLocalStreamId = 0;
this.lastPeerStreamId = -1;
} else {
this.lastLocalStreamId = -1;
this.lastPeerStreamId = 0;
}
this.addressHolder = Holder.lazyHolder((NullaryFunction<Object>) () -> connection.getPeerAddress());
connection.addCloseListener(new ConnectionCloseListener());
this.outputSink = newOutputSink();
NetLogger.logOpen(this);
}
protected Http2SessionOutputSink newOutputSink() {
return new Http2SessionOutputSink(this);
}
protected int getSpecDefaultFramePayloadSize() {
return 16384;
}
protected int getSpecMinFramePayloadSize() {
return 16384;
}
protected int getSpecMaxFramePayloadSize() {
return 0xffffff;
}
public int getDefaultConnectionWindowSize() {
return 65535;
}
public int getDefaultStreamWindowSize() {
return 65535;
}
public int getDefaultMaxConcurrentStreams() {
return 100;
}
public int () {
return maxHeaderListSize;
}
@SuppressWarnings("unused")
public void (int maxHeaderListSize) {
this.maxHeaderListSize = maxHeaderListSize;
}
protected int getFrameSize(final Buffer buffer) {
return buffer.remaining() < 4
? -1
: (buffer.getInt(buffer.position()) >>> 8) + Http2Frame.FRAME_HEADER_SIZE;
}
public Http2Frame (final Buffer buffer) throws Http2SessionException {
final int len = getFrameSize(buffer);
if (buffer.remaining() != len) {
throw new Http2SessionException(ErrorCode.FRAME_SIZE_ERROR);
}
final int i1 = buffer.getInt();
final int type = i1 & 0xff;
final int flags = buffer.get() & 0xff;
final int streamId = buffer.getInt() & 0x7fffffff;
switch (type) {
case DataFrame.TYPE:
return DataFrame.fromBuffer(flags, streamId, buffer);
case HeadersFrame.TYPE:
return HeadersFrame.fromBuffer(flags, streamId, buffer);
case PriorityFrame.TYPE:
return PriorityFrame.fromBuffer(streamId, buffer);
case RstStreamFrame.TYPE:
return RstStreamFrame.fromBuffer(flags, streamId, buffer);
case SettingsFrame.TYPE:
return SettingsFrame.fromBuffer(flags, streamId, buffer);
case PushPromiseFrame.TYPE:
return PushPromiseFrame.fromBuffer(flags, streamId, buffer);
case PingFrame.TYPE:
return PingFrame.fromBuffer(flags, streamId, buffer);
case GoAwayFrame.TYPE:
return GoAwayFrame.fromBuffer(streamId, buffer);
case WindowUpdateFrame.TYPE:
return WindowUpdateFrame.fromBuffer(flags, streamId, buffer);
case ContinuationFrame.TYPE:
return ContinuationFrame.fromBuffer(flags, streamId, buffer);
default:
return new UnknownFrame(type, len);
}
}
protected Http2Stream newStream(final HttpRequestPacket request, final int streamId, final int refStreamId, final boolean exclusive, final int priority) {
return new Http2Stream(this, request, streamId, refStreamId, exclusive, priority);
}
protected Http2Stream newUpgradeStream(final HttpRequestPacket request, final int priority) {
return new Http2Stream(this, request, priority);
}
protected void checkFrameSequenceSemantics(final Http2Frame frame) throws Http2SessionException {
final int frameType = frame.getType();
if (isFirstInFrame) {
if (frameType != SettingsFrame.TYPE) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "First in frame should be a SettingsFrame (preface)", frame);
}
throw new Http2SessionException(ErrorCode.PROTOCOL_ERROR);
}
isPrefaceReceived = true;
handlerFilter.onPrefaceReceived(this);
Http2State.get(connection).setOpen();
isFirstInFrame = false;
}
if (isParsingHeaders()) {
if (frameType != ContinuationFrame.TYPE) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "ContinuationFrame is expected, but {0} came", frame);
}
throw new Http2SessionException(ErrorCode.PROTOCOL_ERROR);
}
} else if (frameType == ContinuationFrame.TYPE) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "ContinuationFrame is not expected");
}
throw new Http2SessionException(ErrorCode.PROTOCOL_ERROR);
}
}
protected void onOversizedFrame(final Buffer buffer) throws Http2SessionException {
final int oldPos = buffer.position();
try {
throw new Http2SessionException(ErrorCode.FRAME_SIZE_ERROR);
} finally {
buffer.position(oldPos);
}
}
boolean () {
return headersDecoder != null && headersDecoder.isProcessingHeaders();
}
public final int getLocalMaxFramePayloadSize() {
return localMaxFramePayloadSize;
}
public int getPeerMaxFramePayloadSize() {
return peerMaxFramePayloadSize;
}
protected void setPeerMaxFramePayloadSize(final int peerMaxFramePayloadSize) throws Http2SessionException {
if (peerMaxFramePayloadSize < getSpecMinFramePayloadSize() || peerMaxFramePayloadSize > getSpecMaxFramePayloadSize()) {
throw new Http2SessionException(ErrorCode.FRAME_SIZE_ERROR);
}
this.peerMaxFramePayloadSize = peerMaxFramePayloadSize;
}
public int getLocalStreamWindowSize() {
return localStreamWindowSize;
}
public void setLocalStreamWindowSize(int localStreamWindowSize) {
this.localStreamWindowSize = localStreamWindowSize;
}
public int getPeerStreamWindowSize() {
return peerStreamWindowSize;
}
void setPeerStreamWindowSize(final int peerStreamWindowSize) throws Http2StreamException {
synchronized (sessionLock) {
final int delta = peerStreamWindowSize - this.peerStreamWindowSize;
this.peerStreamWindowSize = peerStreamWindowSize;
if (!streamsMap.isEmpty()) {
for (final Http2Stream stream : streamsMap.values()) {
if (stream.isClosed()) {
continue;
}
stream.getOutputSink().onPeerWindowUpdate(delta);
}
}
}
}
public int getLocalConnectionWindowSize() {
return localConnectionWindowSize;
}
public void setLocalConnectionWindowSize(final int localConnectionWindowSize) {
this.localConnectionWindowSize = localConnectionWindowSize;
}
public int getAvailablePeerConnectionWindowSize() {
return outputSink.getAvailablePeerConnectionWindowSize();
}
public int getLocalMaxConcurrentStreams() {
return localMaxConcurrentStreams;
}
public void setLocalMaxConcurrentStreams(int localMaxConcurrentStreams) {
this.localMaxConcurrentStreams = localMaxConcurrentStreams;
this.streamsHighWaterMark = Float.valueOf(this.localMaxConcurrentStreams * this.http2Configuration.getStreamsHighWaterMark()).intValue();
}
@SuppressWarnings("unused")
public int getPeerMaxConcurrentStreams() {
return peerMaxConcurrentStreams;
}
void setPeerMaxConcurrentStreams(int peerMaxConcurrentStreams) {
this.peerMaxConcurrentStreams = peerMaxConcurrentStreams;
}
public boolean isPushEnabled() {
return pushEnabled && http2Configuration.isPushEnabled();
}
public void setPushEnabled(final boolean pushEnabled) {
if (isGoingAway()) {
return;
}
this.pushEnabled = pushEnabled;
}
public int getNextLocalStreamId() {
lastLocalStreamId += 2;
return lastLocalStreamId;
}
public Connection getConnection() {
return connection;
}
public MemoryManager getMemoryManager() {
return connection.getMemoryManager();
}
public boolean isServer() {
return isServer;
}
public boolean isLocallyInitiatedStream(final int streamId) {
assert streamId > 0;
return isServer() ^ streamId % 2 != 0;
}
Http2State getHttp2State() {
return http2State;
}
boolean isHttp2InputEnabled() {
return isPrefaceReceived;
}
boolean isHttp2OutputEnabled() {
return isPrefaceSent;
}
public Http2Stream getStream(final int streamId) {
return streamsMap.get(streamId);
}
protected Http2SessionOutputSink getOutputSink() {
return outputSink;
}
FutureImpl<Http2Session> terminateGracefully() {
if (!isServer) {
throw new IllegalStateException("Illegal use of graceful termination on client.");
}
final GoAwayFrame frame = setGoAwayLocally(ErrorCode.NO_ERROR, "Shutting Down", true);
if (frame != null) {
sessionClosed = Futures.createSafeFuture();
outputSink.writeDownStream(frame);
}
return sessionClosed;
}
void terminate(final ErrorCode errorCode, final String detail) {
sendGoAwayAndClose(setGoAwayLocally(errorCode, detail, false));
}
private void sendGoAwayAndClose(final Http2Frame frame) {
if (frame != null) {
outputSink.writeDownStream(frame, new EmptyCompletionHandler<WriteResult>() {
private void close() {
connection.closeSilently();
outputSink.close();
}
@Override
public void failed(final Throwable throwable) {
LOGGER.log(Level.WARNING, "Unable to write GOAWAY. Terminating session.", throwable);
close();
}
@Override
public void completed(final WriteResult result) {
close();
}
@Override
public void cancelled() {
LOGGER.log(Level.FINE, "GOAWAY write cancelled. Terminating session.");
close();
}
}, null);
}
}
private GoAwayFrame setGoAwayLocally(final ErrorCode errorCode, final String detail, final boolean graceful) {
synchronized (sessionLock) {
if (goingAwayLastStreamId == Integer.MIN_VALUE || goingAwayLastStreamId == Integer.MAX_VALUE && !graceful) {
closeFlag = CloseType.LOCALLY;
goingAwayLastStreamId = graceful ? Integer.MAX_VALUE : lastPeerStreamId > 0 ? lastPeerStreamId : 0;
if (goingAwayLastStreamId != Integer.MAX_VALUE) {
if (concurrentStreamsCount.get() > 0) {
pruneStreams();
}
}
return GoAwayFrame.builder().lastStreamId(goingAwayLastStreamId)
.additionalDebugData(detail != null ? Buffers.wrap(getMemoryManager(), detail) : null).errorCode(errorCode).build();
}
return null;
}
}
private void pruneStreams() {
LOGGER.log(Level.FINE, "pruneStreams()");
Map<Integer, Http2Stream> invalidStreams = streamsMap.subMap(goingAwayLastStreamId, false, Integer.MAX_VALUE, true);
if (!invalidStreams.isEmpty()) {
List<Http2Stream> closedStreams = new ArrayList<>(invalidStreams.values());
for (final Http2Stream stream : closedStreams) {
stream.closedRemotely();
deregisterStream();
}
}
}
void setGoAwayByPeer(final int lastStreamId) {
synchronized (sessionLock) {
pushEnabled = false;
goingAwayLastStreamId = lastStreamId;
closeFlag = CloseType.REMOTELY;
pruneStreams();
if (isServer || lastStreamId != Integer.MAX_VALUE) {
sendGoAwayAndClose(GoAwayFrame.builder().lastStreamId(goingAwayLastStreamId)
.additionalDebugData(Buffers.wrap(getMemoryManager(), "Peer Requested.")).errorCode(ErrorCode.NO_ERROR).build());
}
}
}
boolean isGoingAway() {
return closeFlag != null;
}
public int getGoingAwayLastStreamId() {
return goingAwayLastStreamId;
}
protected void sendWindowUpdate(final int streamId, final int delta) {
final WindowUpdateFrame f = WindowUpdateFrame.builder().streamId(streamId).windowSizeIncrement(delta).build();
NetLogger.log(NetLogger.Context.TX, this, f);
outputSink.writeDownStream(f);
}
void sendPreface() {
if (!isPrefaceSent) {
synchronized (sessionLock) {
if (!isPrefaceSent) {
if (isServer) {
sendServerPreface();
} else {
sendClientPreface();
}
isPrefaceSent = true;
if (!isServer) {
ackConsumedData(getStream(0), 0);
}
}
}
}
}
protected void sendServerPreface() {
final SettingsFrame settingsFrame = prepareSettings().build();
NetLogger.log(NetLogger.Context.TX, this, settingsFrame);
connection.write(settingsFrame.toBuffer(getMemoryManager()), sslFilter != null ? new EmptyCompletionHandler() {
@Override
public void completed(Object result) {
sslFilter.setRenegotiationDisabled(true);
}
} : null);
}
protected void sendClientPreface() {
final HttpRequestPacket request = HttpRequestPacket.builder().method(Method.PRI).uri("*").protocol(Protocol.HTTP_2_0).build();
final Buffer priPayload = Buffers.wrap(connection.getMemoryManager(), PRI_PAYLOAD);
final SettingsFrame settingsFrame = prepareSettings().build();
final Buffer settingsBuffer = settingsFrame.toBuffer(getMemoryManager());
final Buffer payload = Buffers.appendBuffers(connection.getMemoryManager(), priPayload, settingsBuffer);
final HttpContent content = HttpContent.builder(request).content(payload).build();
NetLogger.log(NetLogger.Context.TX, this, settingsFrame);
connection.write(content);
}
HeadersDecoder () {
if (headersDecoder == null) {
headersDecoder = new HeadersDecoder(getMemoryManager(), getMaxHeaderListSize(), 4096);
}
return headersDecoder;
}
ReentrantLock getDeflaterLock() {
return deflaterLock;
}
HeadersEncoder () {
if (headersEncoder == null) {
headersEncoder = new HeadersEncoder(getMemoryManager(), 4096);
}
return headersEncoder;
}
@SuppressWarnings("SameParameterValue")
protected List<Http2Frame> (final FilterChainContext ctx, final HttpHeader httpHeader, final int streamId,
final boolean isLast, final List<Http2Frame> toList, final Map<String, String> capture) throws IOException {
final Buffer compressedHeaders = !httpHeader.isRequest() ? EncoderUtils.encodeResponseHeaders(this, (HttpResponsePacket) httpHeader, capture)
: EncoderUtils.encodeRequestHeaders(this, (HttpRequestPacket) httpHeader, capture);
final List<Http2Frame> headerFrames = bufferToHeaderFrames(streamId, compressedHeaders, isLast, toList);
handlerFilter.onHttpHeadersEncoded(httpHeader, ctx);
return headerFrames;
}
protected List<Http2Frame> (final int streamId, final List<Http2Frame> toList, final MimeHeaders trailerHeaders,
final Map<String, String> capture) throws IOException {
final Buffer compressedHeaders = EncoderUtils.encodeTrailerHeaders(this, trailerHeaders, capture);
return bufferToHeaderFrames(streamId, compressedHeaders, true, toList);
}
@SuppressWarnings("SameParameterValue")
protected List<Http2Frame> encodeHttpRequestAsPushPromiseFrames(final FilterChainContext ctx, final HttpRequestPacket httpRequest, final int streamId,
final int promisedStreamId, final List<Http2Frame> toList, final Map<String, String> capture) throws IOException {
final List<Http2Frame> headerFrames = bufferToPushPromiseFrames(streamId, promisedStreamId,
EncoderUtils.encodeRequestHeaders(this, httpRequest, capture), toList);
handlerFilter.onHttpHeadersEncoded(httpRequest, ctx);
return headerFrames;
}
private List<Http2Frame> (final int streamId, final Buffer compressedHeaders, final boolean isEos, final List<Http2Frame> toList) {
final HeadersFrame.HeadersFrameBuilder builder = HeadersFrame.builder().streamId(streamId).endStream(isEos);
return completeHeadersProviderFrameSerialization(builder, streamId, compressedHeaders, toList);
}
private List<Http2Frame> bufferToPushPromiseFrames(final int streamId, final int promisedStreamId, final Buffer compressedHeaders,
final List<Http2Frame> toList) {
final PushPromiseFrame.PushPromiseFrameBuilder builder = PushPromiseFrame.builder().streamId(streamId).promisedStreamId(promisedStreamId);
return completeHeadersProviderFrameSerialization(builder, streamId, compressedHeaders, toList);
}
private List<Http2Frame> (final HeaderBlockFragment.HeaderBlockFragmentBuilder builder, final int streamId,
final Buffer compressedHeaders, List<Http2Frame> toList) {
assert getDeflaterLock().isHeldByCurrentThread();
if (toList == null) {
toList = tmpHeaderFramesList;
}
if (compressedHeaders.remaining() <= peerMaxFramePayloadSize) {
toList.add(builder.endHeaders(true).compressedHeaders(compressedHeaders).build());
return toList;
}
Buffer remainder = compressedHeaders.split(compressedHeaders.position() + peerMaxFramePayloadSize);
toList.add(builder.endHeaders(false).compressedHeaders(compressedHeaders).build());
assert remainder != null;
do {
final Buffer buffer = remainder;
remainder = buffer.remaining() <= peerMaxFramePayloadSize ? null : buffer.split(buffer.position() + peerMaxFramePayloadSize);
toList.add(ContinuationFrame.builder().streamId(streamId).endHeaders(remainder == null).compressedHeaders(buffer).build());
} while (remainder != null);
return toList;
}
public ReentrantLock getNewClientStreamLock() {
return newClientStreamLock;
}
@SuppressWarnings("SameParameterValue")
Http2Stream acceptStream(final HttpRequestPacket request, final int streamId, final int parentStreamId, final boolean exclusive, final int priority)
throws Http2SessionException {
final Http2Stream stream = newStream(request, streamId, parentStreamId, exclusive, priority);
synchronized (sessionLock) {
if (isClosed()) {
return null;
}
if (concurrentStreamsCount.get() >= getLocalMaxConcurrentStreams()) {
throw new Http2SessionException(ErrorCode.REFUSED_STREAM);
}
if (isServer()) {
if (streamId > 0 && (streamId & 1) == 0) {
throw new Http2SessionException(ErrorCode.PROTOCOL_ERROR);
}
} else {
if (streamId > 0 && (streamId & 1) != 0) {
throw new Http2SessionException(ErrorCode.PROTOCOL_ERROR);
}
}
if (streamId < lastPeerStreamId) {
throw new Http2SessionException(ErrorCode.PROTOCOL_ERROR);
}
registerStream(streamId, stream);
lastPeerStreamId = streamId;
}
return stream;
}
public Http2Stream openStream(final HttpRequestPacket request, final int streamId, final int parentStreamId, final boolean exclusive, final int priority)
throws Http2StreamException {
final Http2Stream stream = newStream(request, streamId, parentStreamId, exclusive, priority);
synchronized (sessionLock) {
if (isClosed()) {
throw new Http2StreamException(streamId, ErrorCode.REFUSED_STREAM, "Session is closed");
}
if (concurrentStreamsCount.get() >= getLocalMaxConcurrentStreams()) {
throw new Http2StreamException(streamId, ErrorCode.REFUSED_STREAM);
}
if (parentStreamId > 0) {
final Http2Stream mainStream = getStream(parentStreamId);
if (mainStream == null) {
throw new Http2StreamException(streamId, ErrorCode.REFUSED_STREAM, "The parent stream does not exist");
}
}
registerStream(streamId, stream);
lastLocalStreamId = streamId;
}
return stream;
}
public Http2Stream acceptUpgradeStream(final HttpRequestPacket request, final int priority, final boolean fin) throws Http2StreamException {
request.setExpectContent(!fin);
final Http2Stream stream = newUpgradeStream(request, priority);
registerUpgradeStream(stream);
return stream;
}
public Http2Stream openUpgradeStream(final HttpRequestPacket request, final int priority) throws Http2StreamException {
final Http2Stream stream = newUpgradeStream(request, priority);
registerUpgradeStream(stream);
return stream;
}
void setupFilterChains(final FilterChainContext context, final boolean isUpStream) {
if (htt2SessionChain == null) {
synchronized (this) {
if (htt2SessionChain == null) {
if (isUpStream) {
http2StreamChain = (FilterChain) context.getFilterChain().subList(context.getFilterIdx(), context.getEndIdx());
htt2SessionChain = (FilterChain) context.getFilterChain().subList(context.getStartIdx(), context.getFilterIdx());
} else {
http2StreamChain = (FilterChain) context.getFilterChain().subList(context.getFilterIdx(), context.getFilterChain().size());
htt2SessionChain = (FilterChain) context.getFilterChain().subList(context.getEndIdx() + 1, context.getFilterIdx());
}
}
}
}
}
FilterChain getHttp2SessionChain() {
return htt2SessionChain;
}
void deregisterStream() {
LOGGER.fine("deregisterStream()");
final boolean isCloseSession;
synchronized (sessionLock) {
decStreamCount();
isCloseSession = isGoingAway() && concurrentStreamsCount.get() <= 0;
if (!isCloseSession) {
if (checkCount++ > http2Configuration.getCleanFrequencyCheck() && streamsMap.size() > streamsHighWaterMark) {
checkCount = 0;
for (final Iterator<Map.Entry<Integer, Http2Stream>> streamIds = streamsMap.entrySet().iterator(); streamIds.hasNext(); ) {
final Map.Entry<Integer, Http2Stream> entry = streamIds.next();
if (entry.getValue().isClosed()) {
streamIds.remove();
}
}
}
}
}
if (isCloseSession) {
if (sessionClosed != null) {
sessionClosed.result(this);
} else {
terminate(ErrorCode.NO_ERROR, "Session closed");
}
}
}
private boolean isClosed() {
return closeFlag != null;
}
void sendMessageUpstreamWithParseNotify(final Http2Stream stream, final HttpContent httpContent) {
final FilterChainContext upstreamContext = http2StreamChain.obtainFilterChainContext(connection, stream);
final HttpContext httpContext = httpContent.getHttpHeader().getProcessingState().getHttpContext();
httpContext.attach(upstreamContext);
handlerFilter.onHttpContentParsed(httpContent, upstreamContext);
final HttpHeader header = httpContent.getHttpHeader();
if (httpContent.isLast()) {
handlerFilter.onHttpPacketParsed(header, upstreamContext);
}
if (header.isSkipRemainder()) {
return;
}
sendMessageUpstream(stream, httpContent, upstreamContext);
}
void sendMessageUpstream(final Http2Stream stream, final HttpPacket message) {
final FilterChainContext upstreamContext = http2StreamChain.obtainFilterChainContext(connection, stream);
final HttpContext httpContext = message.getHttpHeader().getProcessingState().getHttpContext();
httpContext.attach(upstreamContext);
sendMessageUpstream(stream, message, upstreamContext);
}
private void sendMessageUpstream(final Http2Stream stream, final HttpPacket message, final FilterChainContext upstreamContext) {
upstreamContext.getInternalContext().setIoEvent(IOEvent.READ);
upstreamContext.getInternalContext().addLifeCycleListener(new IOEventLifeCycleListener.Adapter() {
@Override
public void onReregister(final Context context) throws IOException {
stream.inputBuffer.onReadEventComplete();
}
@Override
public void onComplete(Context context, Object data) throws IOException {
stream.inputBuffer.onReadEventComplete();
}
});
upstreamContext.setMessage(message);
upstreamContext.setAddressHolder(addressHolder);
ProcessorExecutor.execute(upstreamContext.getInternalContext());
}
protected SettingsFrameBuilder prepareSettings() {
final SettingsFrameBuilder builder = SettingsFrame.builder();
if (getLocalMaxConcurrentStreams() != getDefaultMaxConcurrentStreams()) {
builder.setting(SETTINGS_MAX_CONCURRENT_STREAMS, getLocalMaxConcurrentStreams());
}
if (getLocalStreamWindowSize() != getDefaultStreamWindowSize()) {
builder.setting(SETTINGS_INITIAL_WINDOW_SIZE, getLocalStreamWindowSize());
}
builder.setting(SETTINGS_MAX_HEADER_LIST_SIZE, getMaxHeaderListSize());
return builder;
}
void ackConsumedData(final int sz) {
ackConsumedData(null, sz);
}
void ackConsumedData(final Http2Stream stream, final int sz) {
final int currentUnackedBytes = unackedReadBytes.addAndGet(sz);
if (isPrefaceSent) {
final int windowSize = getLocalConnectionWindowSize();
if (currentUnackedBytes > windowSize / 3 && unackedReadBytes.compareAndSet(currentUnackedBytes, 0)) {
sendWindowUpdate(0, currentUnackedBytes);
}
if (stream != null) {
final int streamUnackedBytes = Http2Stream.unackedReadBytesUpdater.addAndGet(stream, sz);
final int streamWindowSize = stream.getLocalWindowSize();
if (streamUnackedBytes > 0 && streamUnackedBytes > streamWindowSize / 2
&& Http2Stream.unackedReadBytesUpdater.compareAndSet(stream, streamUnackedBytes, 0)) {
sendWindowUpdate(stream.getId(), streamUnackedBytes);
}
}
}
}
void registerStream(final int streamId, final Http2Stream stream) {
if (streamId < 1) {
throw new IllegalArgumentException("Invalid stream ID");
}
if (stream == null) {
throw new NullPointerException("Attempt to register null stream");
}
streamsMap.put(streamId, stream);
incStreamCount();
}
private void registerUpgradeStream(final Http2Stream stream) throws Http2StreamException {
synchronized (sessionLock) {
if (isClosed()) {
throw new Http2StreamException(Http2Stream.UPGRADE_STREAM_ID, ErrorCode.REFUSED_STREAM, "Session is closed");
}
registerStream(Http2Stream.UPGRADE_STREAM_ID, stream);
if (!isServer()) {
lastLocalStreamId = Http2Stream.UPGRADE_STREAM_ID;
}
}
}
private void incStreamCount() {
concurrentStreamsCount.incrementAndGet();
}
private void decStreamCount() {
concurrentStreamsCount.decrementAndGet();
}
private final class ConnectionCloseListener implements CloseListener<Closeable, CloseType> {
@Override
public void onClosed(final Closeable closeable, final CloseType type) throws IOException {
NetLogger.logClose(Http2Session.this);
final boolean isClosing;
synchronized (sessionLock) {
isClosing = !isClosed();
if (isClosing) {
closeFlag = type;
}
}
if (isClosing) {
for (Http2Stream stream : streamsMap.values()) {
stream.closedRemotely();
}
}
}
}
}