in lib/src/streams/stream_handler.dart [491:619]
void _processStreamFrameInternal(
ConnectionState connectionState, Frame frame) {
// If we initiated a close of the connection and the received frame belongs
// to a stream id which is higher than the last peer-initiated stream we
// processed, we'll ignore it.
// http/2 spec:
// After sending a GOAWAY frame, the sender can discard frames for
// streams initiated by the receiver with identifiers higher than the
// identified last stream. However, any frames that alter connection
// state cannot be completely ignored. For instance, HEADERS,
// PUSH_PROMISE, and CONTINUATION frames MUST be minimally processed to
// ensure the state maintained for header compression is consistent
// (see Section 4.3); similarly, DATA frames MUST be counted toward
// the connection flow-control window. Failure to process these
// frames can cause flow control or header compression state to become
// unsynchronized.
if (connectionState.activeFinishing &&
_isPeerInitiatedStream(frame.header.streamId) &&
frame.header.streamId > highestPeerInitiatedStream) {
// Even if the frame will be ignored, we still need to process it in a
// minimal way to ensure the connection window will be updated.
if (frame is DataFrame) {
incomingQueue.processIgnoredDataFrame(frame);
}
return null;
}
// TODO: Consider splitting this method into client/server handling.
return ensureNotTerminatedSync(() {
var stream = _openStreams[frame.header.streamId];
if (stream == null) {
bool frameBelongsToIdleStream() {
var streamId = frame.header.streamId;
var isServerStreamId = frame.header.streamId.isEven;
var isLocalStream = isServerStreamId == isServer;
var isIdleStream = isLocalStream
? streamId >= nextStreamId
: streamId > lastRemoteStreamId;
return isIdleStream;
}
if (_isPeerInitiatedStream(frame.header.streamId)) {
// Update highest stream id we received and processed (we update it
// before processing, so if it was an error, the client will not
// retry it).
_highestStreamIdReceived =
max(_highestStreamIdReceived, frame.header.streamId);
}
if (frame is HeadersFrame) {
if (isServer) {
var newStream = newRemoteStream(frame.header.streamId);
_changeState(newStream, StreamState.Open);
_handleHeadersFrame(newStream, frame);
_newStreamsC.add(newStream);
} else {
// A server cannot open new streams to the client. The only way
// for a server to start a new stream is via a PUSH_PROMISE_FRAME.
throw ProtocolException(
'HTTP/2 clients cannot receive HEADER_FRAMEs as a connection'
'attempt.');
}
} else if (frame is WindowUpdateFrame) {
if (frameBelongsToIdleStream()) {
// We treat this as a protocol error even though not enforced
// or specified by the HTTP/2 spec.
throw ProtocolException(
'Got a WINDOW_UPDATE_FRAME for an "idle" stream id.');
} else {
// We must be able to receive window update frames for streams that
// have been already closed. The specification does not mention
// what happens if the streamId is belonging to an "idle" / unused
// stream.
}
} else if (frame is RstStreamFrame) {
if (frameBelongsToIdleStream()) {
// [RstFrame]s for streams which haven't been established (known as
// idle streams) must be treated as a connection error.
throw ProtocolException(
'Got a RST_STREAM_FRAME for an "idle" stream id.');
} else {
// [RstFrame]s for already dead (known as "closed") streams should
// be ignored. (If the stream was in "HalfClosedRemote" and we did
// send an endStream=true, it will be removed from the stream set).
}
} else if (frame is PriorityFrame) {
// http/2 spec:
// The PRIORITY frame can be sent for a stream in the "idle" or
// "closed" states. This allows for the reprioritization of a
// group of dependent streams by altering the priority of an
// unused or closed parent stream.
//
// As long as we do not handle stream priorities, we can safely ignore
// such frames on idle streams.
//
// NOTE: Firefox for example sends [PriorityFrame]s even without
// opening any streams (e.g. streams 3,5,7,9,11 [PriorityFrame]s and
// stream 13 is the first real stream opened by a [HeadersFrame].
//
// TODO: When implementing priorities for HTTP/2 streams, these frames
// need to be taken into account.
} else if (frame is PushPromiseFrame) {
throw ProtocolException('Cannot push on a non-existent stream '
'(stream ${frame.header.streamId} does not exist)');
} else {
throw StreamClosedException(
frame.header.streamId,
'No open stream found and was not a headers frame opening a '
'new stream.');
}
} else {
if (frame is HeadersFrame) {
_handleHeadersFrame(stream, frame);
} else if (frame is DataFrame) {
_handleDataFrame(stream, frame);
} else if (frame is PushPromiseFrame) {
_handlePushPromiseFrame(stream, frame);
} else if (frame is WindowUpdateFrame) {
_handleWindowUpdate(stream, frame);
} else if (frame is RstStreamFrame) {
_handleRstFrame(stream, frame);
} else {
throw ProtocolException(
'Unsupported frame type ${frame.runtimeType}.');
}
}
});
}