Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.glassfish.grizzly.http2.Http2Connection Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2016 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.grizzly.http2;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
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.CloseType;
import org.glassfish.grizzly.Closeable;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.Context;
import org.glassfish.grizzly.GenericCloseListener;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.IOEvent;
import org.glassfish.grizzly.IOEventLifeCycleListener;
import org.glassfish.grizzly.ProcessorExecutor;
import org.glassfish.grizzly.WriteHandler;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.http2.frames.DataFrame;
import org.glassfish.grizzly.http2.frames.PingFrame;
import org.glassfish.grizzly.http2.frames.PriorityFrame;
import org.glassfish.grizzly.memory.Buffers;
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.Header;
import org.glassfish.grizzly.http2.frames.ContinuationFrame;
import org.glassfish.grizzly.http2.frames.ErrorCode;
import org.glassfish.grizzly.http2.frames.GoAwayFrame;
import org.glassfish.grizzly.http2.frames.HeadersFrame;
import org.glassfish.grizzly.http2.frames.Http2Frame;
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.memory.MemoryManager;
import org.glassfish.grizzly.utils.DataStructures;
import org.glassfish.grizzly.utils.Holder;
import org.glassfish.grizzly.utils.NullaryFunction;
import static org.glassfish.grizzly.http2.frames.SettingsFrame.*;
import static org.glassfish.grizzly.http2.Constants.*;
import static org.glassfish.grizzly.http2.Http2BaseFilter.PRI_PAYLOAD;
import org.glassfish.grizzly.http2.frames.HeaderBlockFragment;
import org.glassfish.grizzly.http2.frames.WindowUpdateFrame;
/**
* The HTTP2 connection abstraction.
*
* @author Alexey Stashok
*/
public class Http2Connection {
private static final Logger LOGGER = Grizzly.logger(Http2Connection.class);
private static final Level LOGGER_LEVEL = Level.INFO;
private final boolean isServer;
private final Connection> connection;
Http2State http2State;
private HeadersDecoder headersDecoder;
private HeadersEncoder headersEncoder;
private final ReentrantLock deflaterLock = new ReentrantLock();
private int lastPeerStreamId;
private int lastLocalStreamId;
private final ReentrantLock newClientStreamLock = new ReentrantLock();
private volatile FilterChain http2StreamChain;
private volatile FilterChain http2ConnectionChain;
volatile Boolean cipherSuiteOkay;
private final Map streamsMap =
DataStructures.getConcurrentMap();
// (Optimization) We may read several DataFrames belonging to the same
// SpdyStream, so in order to not process every DataFrame separately -
// we buffer them and only then passing for processing.
final List streamsToFlushInput = new ArrayList<>();
// The List object used to store header frames. Could be used by
// Http2Connection streams, when they write headers
protected final List tmpHeaderFramesList =
new ArrayList<>(2);
private final Object sessionLock = new Object();
private CloseType closeFlag;
private int peerStreamWindowSize = getDefaultStreamWindowSize();
private volatile int localStreamWindowSize = getDefaultStreamWindowSize();
private volatile int localConnectionWindowSize = getDefaultConnectionWindowSize();
private volatile int localMaxConcurrentStreams = getDefaultMaxConcurrentStreams();
private int peerMaxConcurrentStreams = getDefaultMaxConcurrentStreams();
private final StreamBuilder streamBuilder = new StreamBuilder();
private final Http2ConnectionOutputSink outputSink;
// true, if this connection is ready to accept frames or false if the first
// HTTP/1.1 Upgrade is still in progress
private volatile boolean isPrefaceReceived;
private volatile boolean isPrefaceSent;
public static Http2Connection get(final Connection connection) {
final Http2State http2State = Http2State.get(connection);
return http2State != null
? http2State.getHttp2Connection()
: null;
}
static void bind(final Connection connection,
final Http2Connection http2Connection) {
Http2State.obtain(connection).setHttp2Connection(http2Connection);
}
private final Holder> addressHolder;
final Http2BaseFilter handlerFilter;
private final int localMaxFramePayloadSize;
private int peerMaxFramePayloadSize = getSpecDefaultFramePayloadSize();
private boolean isFirstInFrame = true;
private final AtomicInteger unackedReadBytes = new AtomicInteger();
public Http2Connection(final Connection> connection,
final boolean isServer,
final Http2BaseFilter handlerFilter) {
this.connection = connection;
this.isServer = isServer;
this.handlerFilter = handlerFilter;
final int customMaxFramePayloadSz
= handlerFilter.getLocalMaxFramePayloadSize() > 0
? handlerFilter.getLocalMaxFramePayloadSize()
: -1;
// apply custom max frame value only if it's in [getSpecMinFramePayloadSize(); getSpecMaxFramePayloadSize()] range
localMaxFramePayloadSize =
customMaxFramePayloadSz >= getSpecMinFramePayloadSize() &&
customMaxFramePayloadSz <= getSpecMaxFramePayloadSize()
? customMaxFramePayloadSz
: getSpecDefaultFramePayloadSize();
if (isServer) {
lastLocalStreamId = 0;
lastPeerStreamId = -1;
} else {
lastLocalStreamId = -1;
lastPeerStreamId = 0;
}
addressHolder = Holder.lazyHolder(new NullaryFunction() {
@Override
public Object evaluate() {
return connection.getPeerAddress();
}
});
connection.addCloseListener(new ConnectionCloseListener());
this.outputSink = newOutputSink();
}
protected Http2ConnectionOutputSink newOutputSink() {
return new Http2ConnectionOutputSink(this);
}
public int getFrameHeaderSize() {
return 9;
}
protected int getSpecDefaultFramePayloadSize() {
return 16384; //2^14
}
protected int getSpecMinFramePayloadSize() {
return 16384; //2^14
}
protected int getSpecMaxFramePayloadSize() {
return 0xffffff; // 2^24-1 (16,777,215)
}
public int getDefaultConnectionWindowSize() {
return 65535;
}
public int getDefaultStreamWindowSize() {
return 65535;
}
public int getDefaultMaxConcurrentStreams() {
return 100;
}
protected boolean isFrameReady(final Buffer buffer) {
final int frameLen = getFrameSize(buffer);
return frameLen > 0 && buffer.remaining() >= frameLen;
}
/**
* Returns the total frame size (including header size), or -1
* if the buffer doesn't contain enough bytes to read the size.
*
* @param buffer
* @return the total frame size (including header size), or -1
* if the buffer doesn't contain enough bytes to read the size
*/
protected int getFrameSize(final Buffer buffer) {
return buffer.remaining() < 4 // even though we need just 3 bytes - we require 4 for simplicity
? -1
: (buffer.getInt(buffer.position()) >>> 8) + getFrameHeaderSize();
}
public Http2Frame parseHttp2FrameHeader(final Buffer buffer)
throws Http2ConnectionException {
// we assume the passed buffer represents only this frame, no remainders allowed
assert buffer.remaining() == getFrameSize(buffer);
final int i1 = buffer.getInt();
final int length = (i1 >>> 8) & 0xffffff;
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(length, flags, streamId, buffer)
.normalize(); // remove padding
case HeadersFrame.TYPE:
return HeadersFrame.fromBuffer(length, flags, streamId, buffer)
.normalize(); // remove padding
case PriorityFrame.TYPE:
return PriorityFrame.fromBuffer(streamId, buffer);
case RstStreamFrame.TYPE:
return RstStreamFrame.fromBuffer(flags, streamId, buffer);
case SettingsFrame.TYPE:
return SettingsFrame.fromBuffer(length, flags, buffer);
case PushPromiseFrame.TYPE:
return PushPromiseFrame.fromBuffer(length, flags, streamId, buffer);
case PingFrame.TYPE:
return PingFrame.fromBuffer(flags, buffer);
case GoAwayFrame.TYPE:
return GoAwayFrame.fromBuffer(length, buffer);
case WindowUpdateFrame.TYPE:
return WindowUpdateFrame.fromBuffer(flags, streamId, buffer);
case ContinuationFrame.TYPE:
return ContinuationFrame.fromBuffer(length, flags, streamId, buffer);
default:
throw new Http2ConnectionException(ErrorCode.PROTOCOL_ERROR,
"Unknown frame type: " + type);
}
}
public void serializeHttp2FrameHeader(final Http2Frame frame,
final Buffer buffer) {
assert buffer.remaining() >= getFrameHeaderSize();
buffer.putInt(
((frame.getLength() & 0xffffff) << 8) |
frame.getType());
buffer.put((byte) frame.getFlags());
buffer.putInt(frame.getStreamId());
}
protected Http2Stream newStream(final HttpRequestPacket request,
final int streamId, final int refStreamId,
final int priority, final Http2StreamState initialState) {
return new Http2Stream(this, request, streamId, refStreamId,
priority, initialState);
}
protected Http2Stream newUpgradeStream(final HttpRequestPacket request,
final int priority, final Http2StreamState initialState) {
return new Http2Stream(this, request, priority, initialState);
}
protected void checkFrameSequenceSemantics(final Http2Frame frame)
throws Http2ConnectionException {
final int frameType = frame.getType();
if (isFirstInFrame) {
if (frameType != SettingsFrame.TYPE) {
if (LOGGER.isLoggable(LOGGER_LEVEL)) {
LOGGER.log(LOGGER_LEVEL, "First in frame should be a SettingsFrame (preface)", frame);
}
throw new Http2ConnectionException(ErrorCode.PROTOCOL_ERROR);
}
isPrefaceReceived = true;
handlerFilter.onPrefaceReceived(this);
// Preface received - change the HTTP2 connection state
Http2State.get(connection).setOpen();
isFirstInFrame = false;
}
// 1) make sure the header frame sequence comes without interleaving
if (isParsingHeaders()) {
if (frameType != ContinuationFrame.TYPE) {
if (LOGGER.isLoggable(LOGGER_LEVEL)) {
LOGGER.log(LOGGER_LEVEL, "ContinuationFrame is expected, but {0} came", frame);
}
throw new Http2ConnectionException(ErrorCode.PROTOCOL_ERROR);
}
} else if (frameType == ContinuationFrame.TYPE) {
// isParsing == false, so no ContinuationFrame expected
if (LOGGER.isLoggable(LOGGER_LEVEL)) {
LOGGER.log(LOGGER_LEVEL, "ContinuationFrame is not expected");
}
throw new Http2ConnectionException(ErrorCode.PROTOCOL_ERROR);
}
}
protected void onOversizedFrame(final Buffer buffer)
throws Http2ConnectionException {
final int oldPos = buffer.position();
try {
final Http2Frame oversizedFrame = parseHttp2FrameHeader(buffer);
final int streamId = oversizedFrame.getStreamId();
if (streamId > 0) {
final Http2Stream stream = getStream(streamId);
if (stream != null) {
// Known Http2Stream
stream.inputBuffer.close(FRAME_TOO_LARGE_TERMINATION);
sendRstFrame(ErrorCode.FRAME_SIZE_ERROR, streamId);
} else {
sendRstFrame(ErrorCode.STREAM_CLOSED, streamId);
}
} else {
throw new Http2ConnectionException(ErrorCode.FRAME_SIZE_ERROR);
}
} finally {
buffer.position(oldPos);
}
}
boolean isParsingHeaders() {
return headersDecoder != null && headersDecoder.isProcessingHeaders();
}
/**
* @return The max payload size to be accepted by this side
*/
public final int getLocalMaxFramePayloadSize() {
return localMaxFramePayloadSize;
}
/**
* @return The max payload size to be accepted by the peer
*/
public int getPeerMaxFramePayloadSize() {
return peerMaxFramePayloadSize;
}
/**
* Sets the max payload size to be accepted by the peer.
* The method is called during the {@link SettingsFrame} processing.
*
* @param peerMaxFramePayloadSize
* @throws Http2ConnectionException if the peerMaxFramePayloadSize violates the limits
*/
protected void setPeerMaxFramePayloadSize(final int peerMaxFramePayloadSize)
throws Http2ConnectionException {
if (peerMaxFramePayloadSize < getSpecMinFramePayloadSize() ||
peerMaxFramePayloadSize > getSpecMaxFramePayloadSize()) {
throw new Http2ConnectionException(ErrorCode.FRAME_SIZE_ERROR);
}
this.peerMaxFramePayloadSize = peerMaxFramePayloadSize;
}
boolean canWrite() {
return outputSink.canWrite();
}
void notifyCanWrite(final WriteHandler writeHandler) {
outputSink.notifyCanWrite(writeHandler);
}
public int getLocalStreamWindowSize() {
return localStreamWindowSize;
}
public void setLocalStreamWindowSize(int localStreamWindowSize) {
this.localStreamWindowSize = localStreamWindowSize;
}
public int getPeerStreamWindowSize() {
return peerStreamWindowSize;
}
void setPeerStreamWindowSize(final int peerStreamWindowSize) {
synchronized (sessionLock) {
final int delta = peerStreamWindowSize - this.peerStreamWindowSize;
this.peerStreamWindowSize = peerStreamWindowSize;
for (Http2Stream stream : streamsMap.values()) {
try {
stream.getOutputSink().onPeerWindowUpdate(delta);
} catch (Http2StreamException e) {
if (LOGGER.isLoggable(LOGGER_LEVEL)) {
LOGGER.log(LOGGER_LEVEL, "SpdyStreamException occurred on stream="
+ stream + " during stream window update", e);
}
sendRstFrame(e.getErrorCode(), e.getStreamId());
}
}
}
}
public int getLocalConnectionWindowSize() {
return localConnectionWindowSize;
}
public void setLocalConnectionWindowSize(final int localConnectionWindowSize) {
this.localConnectionWindowSize = localConnectionWindowSize;
}
public int getAvailablePeerConnectionWindowSize() {
return outputSink.getAvailablePeerConnectionWindowSize();
}
/**
* @return the maximum number of concurrent streams allowed for this session by our side.
*/
public int getLocalMaxConcurrentStreams() {
return localMaxConcurrentStreams;
}
/**
* Sets the default maximum number of concurrent streams allowed for this session by our side.
* @param localMaxConcurrentStreams
*/
public void setLocalMaxConcurrentStreams(int localMaxConcurrentStreams) {
this.localMaxConcurrentStreams = localMaxConcurrentStreams;
}
/**
* @return the maximum number of concurrent streams allowed for this session by peer.
*/
public int getPeerMaxConcurrentStreams() {
return peerMaxConcurrentStreams;
}
/**
* Sets the default maximum number of concurrent streams allowed for this session by peer.
*/
void setPeerMaxConcurrentStreams(int peerMaxConcurrentStreams) {
this.peerMaxConcurrentStreams = peerMaxConcurrentStreams;
}
public int getNextLocalStreamId() {
lastLocalStreamId += 2;
return lastLocalStreamId;
}
public StreamBuilder getStreamBuilder() {
return streamBuilder;
}
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);
// Same as
// return isServer() ?
// (streamId % 2) == 0 :
// (streamId % 2) == 1;
}
Http2State getHttp2State() {
return http2State;
}
boolean isHttp2InputEnabled() {
return isPrefaceReceived;
}
boolean isHttp2OutputEnabled() {
return isPrefaceSent;
}
public Http2Stream getStream(final int streamId) {
return streamsMap.get(streamId);
}
protected Http2ConnectionOutputSink getOutputSink() {
return outputSink;
}
/**
* If the session is still open - closes it and sends GOAWAY frame to a peer,
* otherwise if the session was already closed - does nothing.
*
* @param errorCode GOAWAY status code.
*/
public void goAway(final ErrorCode errorCode) {
final Http2Frame goAwayFrame = setGoAwayLocally(errorCode);
if (goAwayFrame != null) {
outputSink.writeDownStream(goAwayFrame);
}
}
GoAwayFrame setGoAwayLocally(final ErrorCode errorCode) {
final int lastPeerStreamIdLocal = close();
if (lastPeerStreamIdLocal == -1) {
return null; // SpdySession is already in go-away state
}
return GoAwayFrame.builder()
.lastStreamId(lastPeerStreamIdLocal)
.errorCode(errorCode)
.build();
}
protected void sendWindowUpdate(final int streamId, final int delta) {
outputSink.writeDownStream(
WindowUpdateFrame.builder()
.streamId(streamId)
.windowSizeIncrement(delta)
.build());
}
boolean sendPreface() {
if (!isPrefaceSent) {
synchronized (sessionLock) {
if (!isPrefaceSent) {
if (isServer) {
sendServerPreface();
} else {
sendClientPreface();
}
isPrefaceSent = true;
if (!isServer) {
// If it's HTTP2 client, which uses HTTP/1.1 upgrade mechanism -
// it can have unacked user data sent from the server.
// So it's right time to ack this data and let the server send
// more data if needed.
ackConsumedData(getStream(0), 0);
}
return true;
}
}
}
return false;
}
protected void sendServerPreface() {
final SettingsFrame settingsFrame = prepareSettings().build();
if (LOGGER.isLoggable(LOGGER_LEVEL)) {
LOGGER.log(LOGGER_LEVEL, "Tx: server preface (the settings frame)."
+ "Connection={0}, settingsFrame={1}",
new Object[]{connection, settingsFrame});
}
// server preface
connection.write(settingsFrame.toBuffer(this));
}
protected void sendClientPreface() {
// send preface (PRI * ....)
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(this);
final Buffer payload = Buffers.appendBuffers(
connection.getMemoryManager(), priPayload, settingsBuffer);
final HttpContent content = HttpContent.builder(request)
.content(payload)
.build();
if (LOGGER.isLoggable(LOGGER_LEVEL)) {
LOGGER.log(LOGGER_LEVEL, "Tx: client preface including the settings "
+ "frame. Connection={0}, settingsFrame={1}",
new Object[]{connection, settingsFrame});
}
connection.write(content);
}
protected void sendRstFrame(final ErrorCode errorCode, final int streamId) {
outputSink.writeDownStream(
RstStreamFrame.builder()
.errorCode(errorCode)
.streamId(streamId)
.build());
}
HeadersDecoder getHeadersDecoder() {
if (headersDecoder == null) {
headersDecoder = new HeadersDecoder(getMemoryManager(), 4096, 4096);
}
return headersDecoder;
}
ReentrantLock getDeflaterLock() {
return deflaterLock;
}
HeadersEncoder getHeadersEncoder() {
if (headersEncoder == null) {
headersEncoder = new HeadersEncoder(getMemoryManager(), 4096);
}
return headersEncoder;
}
/**
* Encodes the {@link HttpHeader} and locks the compression lock.
*
* @param ctx
* @param httpHeader
* @param streamId
* @param isLast
* @param toList the target {@link List}, to which the frames will be serialized
*
* @return the HTTP2 header frames sequence
* @throws IOException
*/
protected List encodeHttpHeaderAsHeaderFrames(
final FilterChainContext ctx,
final HttpHeader httpHeader,
final int streamId,
final boolean isLast,
final List toList)
throws IOException {
final Buffer compressedHeaders = !httpHeader.isRequest()
? EncoderUtils.encodeResponseHeaders(
this, (HttpResponsePacket) httpHeader)
: EncoderUtils.encodeRequestHeaders(
this, (HttpRequestPacket) httpHeader);
final List headerFrames =
bufferToHeaderFrames(streamId, compressedHeaders, isLast, toList);
handlerFilter.onHttpHeadersEncoded(httpHeader, ctx);
return headerFrames;
}
/**
* Encodes the {@link HttpRequestPacket} as a {@link PushPromiseFrame}
* and locks the compression lock.
*
* @param ctx
* @param httpRequest
* @param streamId
* @param promisedStreamId
* @param toList the target {@link List}, to which the frames will be serialized
* @return the HTTP2 push promise frames sequence
*
* @throws IOException
*/
protected List encodeHttpRequestAsPushPromiseFrames(
final FilterChainContext ctx,
final HttpRequestPacket httpRequest,
final int streamId,
final int promisedStreamId,
final List toList)
throws IOException {
final List headerFrames =
bufferToPushPromiseFrames(
streamId,
promisedStreamId,
EncoderUtils.encodeRequestHeaders(this, httpRequest),
toList);
handlerFilter.onHttpHeadersEncoded(httpRequest, ctx);
return headerFrames;
}
/**
* Encodes a compressed header buffer as a {@link HeadersFrame} and
* a sequence of 0 or more {@link ContinuationFrame}s.
*
* @param streamId
* @param compressedHeaders
* @param isEos
* @param toList the {@link List} to which {@link Http2Frame}s will be added
* @return the result {@link List} with the frames
*/
private List bufferToHeaderFrames(final int streamId,
final Buffer compressedHeaders, final boolean isEos,
final List toList) {
final HeadersFrame.HeadersFrameBuilder builder =
HeadersFrame.builder()
.streamId(streamId)
.endStream(isEos);
return completeHeadersProviderFrameSerialization(builder,
streamId, compressedHeaders, toList);
}
/**
* Encodes a compressed header buffer as a {@link PushPromiseFrame} and
* a sequence of 0 or more {@link ContinuationFrame}s.
*
* @param streamId
* @param promisedStreamId
* @param compressedHeaders
* @param toList the {@link List} to which {@link Http2Frame}s will be added
* @return the result {@link List} with the frames
*/
private List bufferToPushPromiseFrames(final int streamId,
final int promisedStreamId, final Buffer compressedHeaders,
final List toList) {
final PushPromiseFrame.PushPromiseFrameBuilder builder =
PushPromiseFrame.builder()
.streamId(streamId)
.promisedStreamId(promisedStreamId);
return completeHeadersProviderFrameSerialization(builder,
streamId, compressedHeaders, toList);
}
/**
* Completes the {@link HeaderBlockFragment} sequence serialization.
*
* @param streamId
* @param compressedHeaders
* @param toList the {@link List} to which {@link Http2Frame}s will be added
* @return the result {@link List} with the frames
*/
private List completeHeadersProviderFrameSerialization(
final HeaderBlockFragment.HeaderBlockFragmentBuilder builder,
final int streamId,
final Buffer compressedHeaders,
List toList) {
// we assume deflaterLock is acquired and held by this thread
assert deflaterLock.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;
}
/**
* The {@link ReentrantLock}, which assures that requests assigned to newly
* allocated stream IDs will be sent to the server in their order.
* So that request associated with the stream ID '5' won't be sent before
* the request associated with the stream ID '3' etc.
*
* @return the {@link ReentrantLock}
*/
public ReentrantLock getNewClientStreamLock() {
return newClientStreamLock;
}
Http2Stream acceptStream(final HttpRequestPacket request,
final int streamId, final int refStreamId,
final int priority, final Http2StreamState initState)
throws Http2ConnectionException {
final Http2Stream stream = newStream(request,
streamId, refStreamId, priority, initState);
synchronized(sessionLock) {
if (isClosed()) {
return null; // if the session is closed is set - return null to ignore stream creation
}
if (streamsMap.size() >= getLocalMaxConcurrentStreams()) {
// throw Session level exception because headers were not decompressed,
// so compression context is lost
throw new Http2ConnectionException(ErrorCode.REFUSED_STREAM);
}
streamsMap.put(streamId, stream);
lastPeerStreamId = streamId;
}
return stream;
}
/**
* Method is not thread-safe, it is expected that it will be called
* within {@link #getNewClientStreamLock()} lock scope.
* The caller code is responsible for obtaining and releasing the mentioned
* {@link #getNewClientStreamLock()} lock.
* @param request
* @param streamId
* @param refStreamId
* @param priority
* @param initState
* @return
* @throws org.glassfish.grizzly.http2.Http2StreamException
*/
public Http2Stream openStream(final HttpRequestPacket request,
final int streamId, final int refStreamId,
final int priority,
final Http2StreamState initState)
throws Http2StreamException {
final Http2Stream stream = newStream(request,
streamId, refStreamId,
priority, initState);
synchronized(sessionLock) {
if (isClosed()) {
throw new Http2StreamException(streamId,
ErrorCode.REFUSED_STREAM, "Session is closed");
}
if (streamsMap.size() >= getLocalMaxConcurrentStreams()) {
throw new Http2StreamException(streamId, ErrorCode.REFUSED_STREAM);
}
if (refStreamId > 0) {
final Http2Stream mainStream = getStream(refStreamId);
if (mainStream == null) {
throw new Http2StreamException(streamId, ErrorCode.REFUSED_STREAM,
"The parent stream does not exist");
}
}
streamsMap.put(streamId, stream);
lastLocalStreamId = streamId;
}
return stream;
}
/**
* The method is called to create an {@link Http2Stream} initiated via
* HTTP/1.1 Upgrade mechanism.
*
* @param request
* @param priority
* @param fin
* @return
* @throws org.glassfish.grizzly.http2.Http2StreamException
*/
public Http2Stream acceptUpgradeStream(final HttpRequestPacket request,
final int priority, final boolean fin)
throws Http2StreamException {
request.setExpectContent(!fin);
final Http2Stream stream = newUpgradeStream(request, priority,
Http2StreamState.IDLE);
stream.onRcvHeaders(fin);
synchronized (sessionLock) {
if (isClosed()) {
throw new Http2StreamException(Http2Stream.UPGRADE_STREAM_ID,
ErrorCode.REFUSED_STREAM, "Session is closed");
}
streamsMap.put(Http2Stream.UPGRADE_STREAM_ID, stream);
lastLocalStreamId = Http2Stream.UPGRADE_STREAM_ID;
}
return stream;
}
/**
* The method is called on the client side, when the server confirms
* HTTP/1.1 -> HTTP/2.0 upgrade with '101' response.
*
* @param request
* @param priority
* @return
* @throws org.glassfish.grizzly.http2.Http2StreamException
*/
public Http2Stream openUpgradeStream(final HttpRequestPacket request,
final int priority)
throws Http2StreamException {
// we already sent headers - so the initial state is OPEN
final Http2Stream stream = newUpgradeStream(request, priority,
Http2StreamState.OPEN);
synchronized(sessionLock) {
if (isClosed()) {
throw new Http2StreamException(Http2Stream.UPGRADE_STREAM_ID,
ErrorCode.REFUSED_STREAM, "Session is closed");
}
streamsMap.put(Http2Stream.UPGRADE_STREAM_ID, stream);
lastLocalStreamId = Http2Stream.UPGRADE_STREAM_ID;
}
return stream;
}
/**
* Initializes HTTP2 communication (if not initialized before) by forming
* HTTP2 connection and stream {@link FilterChain}s.
*
* @param context {@link FilterChainContext}
* @param isUpStream
*/
boolean setupFilterChains(final FilterChainContext context,
final boolean isUpStream) {
if (http2ConnectionChain == null) {
synchronized(this) {
if (http2ConnectionChain == null) {
if (isUpStream) {
http2StreamChain = (FilterChain) context.getFilterChain().subList(
context.getFilterIdx(), context.getEndIdx());
http2ConnectionChain = (FilterChain) context.getFilterChain().subList(
context.getStartIdx(), context.getFilterIdx());
} else {
http2StreamChain = (FilterChain) context.getFilterChain().subList(
context.getFilterIdx(), context.getFilterChain().size());
http2ConnectionChain = (FilterChain) context.getFilterChain().subList(
context.getEndIdx() + 1, context.getFilterIdx());
}
return true;
}
}
}
return false;
}
FilterChain getHttp2StreamChain() {
return http2StreamChain;
}
FilterChain getHttp2ConnectionChain() {
return http2ConnectionChain;
}
/**
* Method is called, when the session closing is initiated locally.
*/
private int close() {
synchronized (sessionLock) {
if (isClosed()) {
return -1;
}
closeFlag = CloseType.LOCALLY;
return lastPeerStreamId > 0 ? lastPeerStreamId : 0;
}
}
/**
* Method is called, when GOAWAY is initiated by peer
*/
void setGoAwayByPeer(final int lastGoodStreamId) {
synchronized (sessionLock) {
// @TODO Notify pending SYNC_STREAMS if streams were aborted
closeFlag = CloseType.REMOTELY;
}
}
Object getSessionLock() {
return sessionLock;
}
/**
* Called from {@link Http2Stream} once stream is completely closed.
*/
void deregisterStream(final Http2Stream spdyStream) {
streamsMap.remove(spdyStream.getId());
final boolean isCloseSession;
synchronized (sessionLock) {
// If we're in GOAWAY state and there are no streams left - close this session
isCloseSession = isClosed() && streamsMap.isEmpty();
}
if (isCloseSession) {
closeSession();
}
}
/**
* Close the session
*/
private void closeSession() {
connection.closeSilently();
outputSink.close();
}
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() {
return prepareSettings(null);
}
protected SettingsFrameBuilder prepareSettings(
SettingsFrameBuilder builder) {
if (builder == null) {
builder = SettingsFrame.builder();
}
if (getLocalMaxConcurrentStreams() != getDefaultMaxConcurrentStreams()) {
builder.setting(SETTINGS_MAX_CONCURRENT_STREAMS, getLocalMaxConcurrentStreams());
}
if (getLocalStreamWindowSize() != getDefaultStreamWindowSize()) {
builder.setting(SETTINGS_INITIAL_WINDOW_SIZE, getLocalStreamWindowSize());
}
return builder;
}
/**
* Acknowledge that certain amount of data has been read.
* Depending on the total amount of un-acknowledge data the HTTP2 connection
* can decide to send a window_update message to the peer.
*
* @param sz
*/
void ackConsumedData(final int sz) {
ackConsumedData(null, sz);
}
/**
* Acknowledge that certain amount of data has been read.
* Depending on the total amount of un-acknowledge data the HTTP2 connection
* can decide to send a window_update message to the peer.
* Unlike the {@link #ackConsumedData(int)}, this method also requests an
* HTTP2 stream to acknowledge consumed data to the peer.
*
* @param stream
* @param sz
*/
void ackConsumedData(final Http2Stream stream, final int sz) {
final int currentUnackedBytes
= unackedReadBytes.addAndGet(sz);
if (isPrefaceSent) {
// ACK HTTP2 connection flow control
final int windowSize = getLocalConnectionWindowSize();
// if not forced - send update window message only in case currentUnackedBytes > windowSize / 2
if (currentUnackedBytes > (windowSize / 3)
&& unackedReadBytes.compareAndSet(currentUnackedBytes, 0)) {
sendWindowUpdate(0, currentUnackedBytes);
}
if (stream != null) {
// ACK HTTP2 stream flow control
final int streamUnackedBytes
= Http2Stream.unackedReadBytesUpdater.addAndGet(stream, sz);
final int streamWindowSize = stream.getLocalWindowSize();
// send update window message only in case currentUnackedBytes > windowSize / 2
if (streamUnackedBytes > 0
&& (streamUnackedBytes > (streamWindowSize / 2))
&& Http2Stream.unackedReadBytesUpdater.compareAndSet(stream, streamUnackedBytes, 0)) {
sendWindowUpdate(stream.getId(), streamUnackedBytes);
}
}
}
}
public final class StreamBuilder {
private StreamBuilder() {
}
public RegularStreamBuilder regular() {
return new RegularStreamBuilder();
}
public PushStreamBuilder push() {
return new PushStreamBuilder();
}
}
public final class PushStreamBuilder extends HttpHeader.Builder {
private int associatedToStreamId;
private int priority;
private boolean isFin;
private String uri;
private String query;
/**
* Set the request URI.
*
* @param uri the request URI.
* @return the current Builder
*/
public PushStreamBuilder uri(final String uri) {
this.uri = uri;
return this;
}
/**
* Set the query
portion of the request URI.
*
* @param query the query String
*
* @return the current Builder
*/
public PushStreamBuilder query(final String query) {
this.query = query;
return this;
}
/**
* Set the associatedToStreamId
parameter of a {@link Http2Stream}.
*
* @param associatedToStreamId the associatedToStreamId
*
* @return the current Builder
*/
public PushStreamBuilder associatedToStreamId(final int associatedToStreamId) {
this.associatedToStreamId = associatedToStreamId;
return this;
}
/**
* Set the priority
parameter of a {@link Http2Stream}.
*
* @param priority the priority
*
* @return the current Builder
*/
public PushStreamBuilder priority(final int priority) {
this.priority = priority;
return this;
}
/**
* Sets the fin
flag of a {@link Http2Stream}.
*
* @param fin
*
* @return the current Builder
*/
public PushStreamBuilder fin(final boolean fin) {
this.isFin = fin;
return this;
}
/**
* Build the HttpRequestPacket message.
*
* @return HttpRequestPacket
* @throws org.glassfish.grizzly.http2.Http2StreamException
*/
@SuppressWarnings("unchecked")
public final Http2Stream open() throws Http2StreamException {
final HttpRequestPacket request = build();
newClientStreamLock.lock();
try {
final Http2Stream stream = openStream(
request,
getNextLocalStreamId(),
associatedToStreamId, priority,
Http2StreamState.IDLE);
connection.write(request.getResponse());
return stream;
} finally {
newClientStreamLock.unlock();
}
}
@Override
public HttpRequestPacket build() {
Http2Request request = (Http2Request) super.build();
if (uri != null) {
request.setRequestURI(uri);
}
if (query != null) {
request.setQueryString(query);
}
request.setExpectContent(!isFin);
return request;
}
@Override
protected HttpHeader create() {
Http2Request request = Http2Request.create();
HttpResponsePacket packet = request.getResponse();
packet.setSecure(true);
return request;
}
}
public final class RegularStreamBuilder extends HttpHeader.Builder {
private int priority;
private boolean isFin;
private Method method;
private String methodString;
private String uri;
private String query;
private String host;
/**
* Set the HTTP request method.
* @param method the HTTP request method..
* @return the current Builder
*/
public RegularStreamBuilder method(final Method method) {
this.method = method;
methodString = null;
return this;
}
/**
* Set the HTTP request method.
* @param methodString the HTTP request method. Format is "GET|POST...".
* @return the current Builder
*/
public RegularStreamBuilder method(final String methodString) {
this.methodString = methodString;
method = null;
return this;
}
/**
* Set the request URI.
*
* @param uri the request URI.
* @return the current Builder
*/
public RegularStreamBuilder uri(final String uri) {
this.uri = uri;
return this;
}
/**
* Set the query
portion of the request URI.
*
* @param query the query String
* @return the current Builder
*/
public RegularStreamBuilder query(final String query) {
this.query = query;
return this;
}
/**
* Set the value of the Host header.
*
* @param host the value of the Host header.
* @return the current Builder
*/
public RegularStreamBuilder host(final String host) {
this.host = host;
return this;
}
/**
* Set the priority
parameter of a {@link Http2Stream}.
*
* @param priority the priority
* @return the current Builder
*/
public RegularStreamBuilder priority(final int priority) {
this.priority = priority;
return this;
}
/**
* Sets the fin
flag of a {@link Http2Stream}.
*
* @param fin
*
* @return the current Builder
*/
public RegularStreamBuilder fin(final boolean fin) {
this.isFin = fin;
return this;
}
/**
* Build the HttpRequestPacket message.
*
* @return HttpRequestPacket
* @throws org.glassfish.grizzly.http2.Http2StreamException
*/
@SuppressWarnings("unchecked")
public final Http2Stream open() throws Http2StreamException {
HttpRequestPacket request = build();
newClientStreamLock.lock();
try {
final Http2Stream stream = openStream(
request,
getNextLocalStreamId(),
0, priority, Http2StreamState.IDLE);
connection.write(request);
return stream;
} finally {
newClientStreamLock.unlock();
}
}
@Override
public HttpRequestPacket build() {
Http2Request request = (Http2Request) super.build();
if (method != null) {
request.setMethod(method);
}
if (methodString != null) {
request.setMethod(methodString);
}
if (uri != null) {
request.setRequestURI(uri);
}
if (query != null) {
request.setQueryString(query);
}
if (host != null) {
request.addHeader(Header.Host, host);
}
request.setExpectContent(!isFin);
return request;
}
@Override
protected HttpHeader create() {
Http2Request request = Http2Request.create();
request.setSecure(true);
return request;
}
}
private final class ConnectionCloseListener implements GenericCloseListener {
@Override
public void onClosed(final Closeable closeable, final CloseType type)
throws IOException {
final boolean isClosing;
synchronized (sessionLock) {
isClosing = !isClosed();
if (isClosing) {
closeFlag = type;
}
}
if (isClosing) {
for (Http2Stream stream : streamsMap.values()) {
stream.closedRemotely();
}
}
}
}
}