public void WriteHeadersFrame(HeadersList headers, bool isEndStream, bool isEndHeaders) { if (headers == null) { throw new ArgumentNullException("headers is null"); } if (Closed) { return; } var frame = new HeadersFrame(_id, true) { IsEndHeaders = isEndHeaders, IsEndStream = isEndStream, Headers = headers, }; _writeQueue.WriteFrame(frame); if (frame.IsEndStream) { HalfClosedLocal = true; } else if (ReservedLocal) { HalfClosedRemote = true; } if (OnFrameSent != null) { OnFrameSent(this, new FrameSentEventArgs(frame)); } }
//Outgoing internal Http2Stream(int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, int priority = Constants.DefaultStreamPriority) { if (id <= 0) { throw new ArgumentOutOfRangeException("invalid id for stream"); } if (priority < 0 || priority > Constants.MaxPriority) { throw new ArgumentOutOfRangeException("priority out of range"); } _id = id; Priority = priority; _writeQueue = writeQueue; _flowCrtlManager = flowCrtlManager; _unshippedFrames = new Queue <DataFrame>(16); Headers = new HeadersList(); SentDataAmount = 0; ReceivedDataAmount = 0; IsFlowControlBlocked = false; IsFlowControlEnabled = _flowCrtlManager.IsFlowControlEnabled; WindowSize = _flowCrtlManager.StreamsInitialWindowSize; _flowCrtlManager.NewStreamOpenedHandler(this); OnFrameSent += (sender, args) => FramesSent++; }
/// <summary> /// Creates stream. /// </summary> /// <param name="headers"></param> /// <param name="streamId"></param> /// <param name="priority"></param> /// <returns></returns> public Http2Stream CreateStream(HeadersList headers, int streamId, int priority = -1) { if (headers == null) { throw new ArgumentNullException("pairs is null"); } if (priority == -1) { priority = Constants.DefaultStreamPriority; } if (priority < 0 || priority > Constants.MaxPriority) { throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); } if (StreamDictionary.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var streamSequence = new HeadersSequence(streamId, (new HeadersFrame(streamId, true) { Headers = headers })); _headersSequences.Add(streamSequence); var stream = StreamDictionary[streamId]; stream.OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) { return; } var streamSeq = _headersSequences.Find(stream.Id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; stream.OnClose += (o, args) => { var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) { _headersSequences.Remove(streamSeq); } }; stream.Priority = priority; stream.Headers = headers; stream.Opened = true; return(stream); }
/// <summary> /// Sends the headers with request headers. /// </summary> /// <param name="pairs">The header pairs.</param> /// <param name="priority">The stream priority.</param> /// <param name="isEndStream">True if initial headers+priority is also the final frame from endpoint.</param> public void SendRequest(HeadersList pairs, int priority, bool isEndStream) { if (_ourEnd == ConnectionEnd.Server) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Server should not initiate request"); } if (pairs == null) { throw new ArgumentNullException("pairs is null"); } if (priority < 0 || priority > Constants.MaxPriority) { throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); } var path = pairs.GetValue(CommonHeaders.Path); if (path == null) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Invalid request ex"); } //09 -> 8.2.2. Push Responses //Once a client receives a PUSH_PROMISE frame and chooses to accept the //pushed resource, the client SHOULD NOT issue any requests for the //promised resource until after the promised stream has closed. if (_promisedResources.ContainsValue(path)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Resource has been promised. Client should not request it."); } var stream = CreateStream(priority); stream.WriteHeadersFrame(pairs, isEndStream, true); var streamSequence = _headersSequences.Find(stream.Id); streamSequence.AddHeaders(new HeadersFrame(stream.Id, true) { Headers = pairs }); if (OnRequestSent != null) { OnRequestSent(this, new RequestSentEventArgs(stream)); } }
//TODO Think about: writing push_promise is available in any time now. Need to handle it. public void WritePushPromise(IDictionary <string, string[]> pairs, Int32 promisedId) { if (Id % 2 != 0 && promisedId % 2 != 0) { throw new InvalidOperationException("Client cant send push_promise frames"); } if (Closed) { return; } var headers = new HeadersList(pairs); var frame = new PushPromiseFrame(Id, promisedId, true, true, headers); ReservedLocal = true; _writeQueue.WriteFrame(frame); if (OnFrameSent != null) { OnFrameSent(this, new FrameSentEventArgs(frame)); } }
//Incoming internal Http2Stream(HeadersList headers, int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, int priority = Constants.DefaultStreamPriority) : this(id, writeQueue, flowCrtlManager, priority) { Headers = headers; }
private void HandlePushPromiseFrame(PushPromiseFrame frame, out Http2Stream stream) { Http2Logger.LogDebug("PUSH_PROMISE frame: stream id={0}, payload len={1}, promised id={2}, " + "has pad={3}, pad high={4}, pad low={5}, end headers={6}", frame.StreamId, frame.PayloadLength, frame.PromisedStreamId, frame.HasPadding, frame.PadHigh, frame.PadLow, frame.IsEndHeaders); /* 12 -> 6.6. * PUSH_PROMISE frames MUST be associated with an existing, peer- initiated stream. * If the stream identifier field specifies the value 0x0, a recipient MUST respond * with a connection error of type PROTOCOL_ERROR. */ if (frame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "push promise frame with stream id=0"); } /* 12 -> 5.1 * An endpoint that receives any frame after receiving a RST_STREAM MUST treat * that as a stream error of type STREAM_CLOSED. */ if (StreamDictionary[frame.StreamId].Closed) { throw new Http2StreamNotFoundException(frame.StreamId); } //... a receiver MUST //treat the receipt of a PUSH_PROMISE that promises an illegal stream //identifier (Section 5.1.1) (that is, an identifier for a stream that //is not currently in the "idle" state) as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver recently //sent a RST_STREAM frame to cancel the associated stream (see //Section 5.1). if (frame.StreamId % 2 == 0 || frame.PromisedStreamId == 0 || (frame.PromisedStreamId % 2) != 0 || frame.PromisedStreamId < _lastPromisedId || !((StreamDictionary[frame.StreamId].Opened || StreamDictionary[frame.StreamId].HalfClosedLocal))) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incorrect Promised Stream id"); } var serializedHeaders = new byte[frame.CompressedHeaders.Count]; Buffer.BlockCopy(frame.CompressedHeaders.Array, frame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value); frame.Headers.Add(header); } var sequence = _headersSequences.Find(frame.PromisedStreamId); if (sequence == null) { sequence = new HeadersSequence(frame.PromisedStreamId, frame); _headersSequences.Add(sequence); } else { //09 -> 6.6. PUSH_PROMISE //A receiver MUST //treat the receipt of a PUSH_PROMISE on a stream that is neither //"open" nor "half-closed (local)" as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR. //This means that we already got push_promise with the same PromisedId. //Hence Stream is in the reserved state. throw new ProtocolError(ResetStatusCode.ProtocolError, "Got multiple push promises with same Promised Stream id's"); } //09 -> 6.6. PUSH_PROMISE //A PUSH_PROMISE frame without the END_PUSH_PROMISE flag set MUST be //followed by a CONTINUATION frame for the same stream. A receiver //MUST treat the receipt of any other type of frame or a frame on a //different stream as a connection error (Section 5.4.1) of type //PROTOCOL_ERROR. if (!sequence.IsComplete) { stream = null; return; } //09 -> 8.2.1. Push Requests //The server MUST include a method in the ":method" //header field that is safe (see [HTTP-p2], Section 4.2.1). If a //client receives a PUSH_PROMISE that does not include a complete and // valid set of header fields, or the ":method" header field identifies //a method that is not safe, it MUST respond with a stream error //(Section 5.4.2) of type PROTOCOL_ERROR. //Lets think that only GET method is safe for now var method = sequence.Headers.GetValue(CommonHeaders.Method); if (method == null || !method.Equals(Verbs.Get, StringComparison.OrdinalIgnoreCase)) { var frameReceiveStream = GetStream(frame.StreamId); frameReceiveStream.WriteRst(ResetStatusCode.ProtocolError); frameReceiveStream.Close(ResetStatusCode.None); stream = null; return; } stream = GetStream(frame.PromisedStreamId); if (stream.Idle) { stream = CreateStream(sequence); stream.ReservedRemote = true; ValidateHeaders(stream); _lastPromisedId = stream.Id; } else { /* 12 -> 6.6 * Similarly, a receiver MUST treat the receipt of a PUSH_PROMISE that * promises an illegal stream identifier (that is, an identifier for a stream that * is not currently in the "idle" state) as a connection error of type PROTOCOL_ERROR. */ throw new ProtocolError(ResetStatusCode.ProtocolError, "Remote endpoint tried to Promise incorrect Stream id"); } }
private void HandleHeaders(HeadersFrame headersFrame, out Http2Stream stream) { Http2Logger.LogDebug("HEADERS frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}, pad low={4}, " + "end stream={5}, has priority={6}, exclusive={7}, dependency={8}, weight={9}", headersFrame.StreamId, headersFrame.PayloadLength, headersFrame.HasPadding, headersFrame.PadHigh, headersFrame.PadLow, headersFrame.IsEndStream, headersFrame.HasPriority, headersFrame.Exclusive, headersFrame.StreamDependency, headersFrame.Weight); /* 12 -> 6.2 * HEADERS frames MUST be associated with a stream. If a HEADERS frame * is received whose stream identifier field is 0x0, the recipient MUST * respond with a connection error of type PROTOCOL_ERROR. */ if (headersFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming headers frame with stream id=0"); } var serializedHeaders = new byte[headersFrame.CompressedHeaders.Count]; Buffer.BlockCopy(headersFrame.CompressedHeaders.Array, headersFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); foreach (var header in headers) { Http2Logger.LogDebug("{1}={2}", headersFrame.StreamId, header.Key, header.Value); } headersFrame.Headers.AddRange(headers); var sequence = _headersSequences.Find(headersFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(headersFrame.StreamId, headersFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(headersFrame); } if (headersFrame.HasPriority) { //TODO: Priority was deprecated, now we need to use Dependency and Weight //sequence.Priority = headersFrame.Priority; } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(headersFrame.StreamId); if (stream.Idle) { stream = CreateStream(sequence); ValidateHeaders(stream); } else if (stream.ReservedRemote) { stream = CreateStream(sequence); stream.HalfClosedLocal = true; ValidateHeaders(stream); } else if (stream.Opened || stream.HalfClosedLocal) { stream.Headers = sequence.Headers;//Modify by the last accepted frame ValidateHeaders(stream); } else if (stream.HalfClosedRemote) { throw new ProtocolError(ResetStatusCode.ProtocolError, "headers for half closed remote stream"); } else { throw new Http2StreamNotFoundException(headersFrame.StreamId); } }
private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream) { Http2Logger.LogDebug("CONTINUATION frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}," + " pad low={4}, end headers={5}", contFrame.StreamId, contFrame.PayloadLength, contFrame.HasPadding, contFrame.PadHigh, contFrame.PadLow, contFrame.IsEndHeaders); if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Last frame was not headers or continuation"); } /* 12 -> 6.10 * CONTINUATION frames MUST be associated with a stream. If a CONTINUATION * frame is received whose stream identifier field is 0x0, the recipient MUST * respond with a connection error of type PROTOCOL_ERROR. */ if (contFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with stream id=0"); } var serHeaders = new byte[contFrame.CompressedHeaders.Count]; Buffer.BlockCopy(contFrame.CompressedHeaders.Array, contFrame.CompressedHeaders.Offset, serHeaders, 0, serHeaders.Length); var decomprHeaders = _comprProc.Decompress(serHeaders); var contHeaders = new HeadersList(decomprHeaders); foreach (var header in contHeaders) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", contFrame.StreamId, header.Key, header.Value); } contFrame.Headers.AddRange(contHeaders); var sequence = _headersSequences.Find(contFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(contFrame.StreamId, contFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(contFrame); } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(contFrame.StreamId); if (stream.Idle || stream.ReservedRemote) { stream = CreateStream(sequence); ValidateHeaders(stream); } else if (stream.Opened || stream.HalfClosedLocal) { stream.Headers = sequence.Headers;//Modify by the last accepted frame ValidateHeaders(stream); } else if (stream.HalfClosedRemote) { throw new ProtocolError(ResetStatusCode.ProtocolError, "continuation for half closed remote stream"); } else { throw new Http2StreamNotFoundException(contFrame.StreamId); } }