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)); } }
public void WriteHeadersFrame(HeadersList headers, bool isEndStream) { Headers = headers; byte[] headerBytes = _compressionProc.Compress(headers); var frame = new HeadersFrame(_id, headerBytes, Priority) { IsEndHeaders = true, IsEndStream = isEndStream, }; if (frame.IsEndStream) { EndStreamSent = true; } _writeQueue.WriteFrame(frame); if (OnFrameSent != null) { OnFrameSent(this, new FrameSentArgs(frame)); } }
/// <summary> /// Dispatches the incoming frame. /// </summary> /// <param name="frame">The frame.</param> /// <exception cref="System.NotImplementedException"></exception> private void DispatchIncomingFrame(Frame frame) { Http2Stream stream = null; //Spec 03 tells that frame with continues flag MUST be followed by a frame with the same type //and the same stread id. if (_toBeContinuedHeaders != null) { if (_toBeContinuedFrame.FrameType != frame.FrameType || _toBeContinuedFrame.StreamId != frame.StreamId) { //If not, we must close the session. Dispose(); return; } } try { switch (frame.FrameType) { case FrameType.Headers: Http2Logger.LogDebug("New headers with id = " + frame.StreamId); var headersFrame = (HeadersFrame)frame; 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); if (!headersFrame.IsEndHeaders) { _toBeContinuedHeaders = decompressedHeaders; _toBeContinuedFrame = headersFrame; break; } if (_toBeContinuedHeaders != null) { headers.AddRange(_toBeContinuedHeaders); } headersFrame.Headers.AddRange(headers); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value); } stream = GetStream(headersFrame.StreamId); if (stream == null) { // new stream if (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { //Remote side tries to open more streams than allowed Dispose(); return; } string path = headers.GetValue(":path"); if (path == null) { path = _handshakeHeaders.ContainsKey(":path") ? _handshakeHeaders[":path"] : @"\index.html"; headers.Add(new KeyValuePair <string, string>(":path", path)); } stream = new Http2Stream(headers, headersFrame.StreamId, _writeQueue, _flowControlManager, _comprProc); ActiveStreams[stream.Id] = stream; stream.OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } }; _toBeContinuedFrame = null; _toBeContinuedHeaders = null; } break; case FrameType.Priority: var priorityFrame = (PriorityFrame)frame; Http2Logger.LogDebug("Priority frame. StreamId: {0} Priority: {1}", priorityFrame.StreamId, priorityFrame.Priority); stream = GetStream(priorityFrame.StreamId); if (_usePriorities) { stream.Priority = priorityFrame.Priority; } break; case FrameType.RstStream: var resetFrame = (RstStreamFrame)frame; stream = GetStream(resetFrame.StreamId); if (stream != null) { Http2Logger.LogDebug("RST frame with code " + resetFrame.StatusCode); stream.Dispose(); } break; case FrameType.Data: var dataFrame = (DataFrame)frame; Http2Logger.LogDebug("Data frame. StreamId:{0} Length:{1}", dataFrame.StreamId, dataFrame.FrameLength); stream = GetStream(dataFrame.StreamId); //Aggressive window update if (stream.IsFlowControlEnabled) { stream.WriteWindowUpdate(2000000); } break; case FrameType.Ping: var pingFrame = (PingFrame)frame; Http2Logger.LogDebug("Ping frame with StreamId:{0} Payload:{1}", pingFrame.StreamId, pingFrame.Payload.Count); if (pingFrame.FrameLength != PingFrame.PayloadLength) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Ping payload size is not equal to 8"); } if (pingFrame.IsPong) { _wasPingReceived = true; _pingReceived.Set(); } else { var pongFrame = new PingFrame(true, pingFrame.Payload.ToArray()); _writeQueue.WriteFrame(pongFrame); } break; case FrameType.Settings: //Not first frame in the session. //Client initiates connection and sends settings before request. //It means that if server sent settings before it will not be a first frame, //because client initiates connection. if (_ourEnd == ConnectionEnd.Server && !_wasSettingsReceived && ActiveStreams.Count != 0) { Dispose(); return; } var settingFrame = (SettingsFrame)frame; Http2Logger.LogDebug("Settings frame. Entry count: {0} StreamId: {1}", settingFrame.EntryCount, settingFrame.StreamId); _wasSettingsReceived = true; _settingsManager.ProcessSettings(settingFrame, this, _flowControlManager); break; case FrameType.WindowUpdate: if (_useFlowControl) { var windowFrame = (WindowUpdateFrame)frame; Http2Logger.LogDebug("WindowUpdate frame. Delta: {0} StreamId: {1}", windowFrame.Delta, windowFrame.StreamId); stream = GetStream(windowFrame.StreamId); if (stream != null) { stream.UpdateWindowSize(windowFrame.Delta); stream.PumpUnshippedFrames(); } } break; case FrameType.GoAway: _goAwayReceived = true; Http2Logger.LogDebug("GoAway frame received"); Dispose(); break; default: throw new NotImplementedException(frame.FrameType.ToString()); } if (stream != null && frame is IEndStreamFrame && ((IEndStreamFrame)frame).IsEndStream) { //Tell the stream that it was the last frame Http2Logger.LogDebug("Final frame received for stream with id = " + stream.Id); stream.EndStreamReceived = true; } if (stream != null && OnFrameReceived != null) { OnFrameReceived(this, new FrameReceivedEventArgs(stream, frame)); } } //Frame came for already closed stream. Ignore it. //Spec: //An endpoint that sends RST_STREAM MUST ignore //frames that it receives on closed streams if it sends RST_STREAM. // //An endpoint MUST NOT send frames on a closed stream. An endpoint //that receives a frame after receiving a RST_STREAM or a frame //containing a END_STREAM flag on that stream MUST treat that as a //stream error (Section 5.4.2) of type PROTOCOL_ERROR. catch (Http2StreamNotFoundException) { if (stream != null) { stream.WriteRst(ResetStatusCode.ProtocolError); } else { //GoAway? } } catch (CompressionError ex) { //The endpoint is unable to maintain the compression context for the connection. Http2Logger.LogError("Compression error occured: " + ex.Message); Close(ResetStatusCode.CompressionError); } catch (ProtocolError pEx) { Http2Logger.LogError("Protocol error occured: " + pEx.Message); Close(pEx.Code); } }
/// <summary> /// Dispatches the incoming frame. /// </summary> /// <param name="frame">The frame.</param> /// <exception cref="System.NotImplementedException"></exception> private void DispatchIncomingFrame(Frame frame) { Http2Stream stream = null; try { if (frame.PayloadLength > Constants.MaxFramePayloadSize) { throw new ProtocolError(ResetStatusCode.FrameSizeError, String.Format("Frame too large: Type: {0} {1}", frame.FrameType, frame.PayloadLength)); } /* 12 -> 6.5 * A SETTINGS frame MUST be sent by both endpoints at the start of a * connection, and MAY be sent at any other time by either endpoint over * the lifetime of the connection.*/ if (frame.FrameType != FrameType.Settings && !_wasSettingsReceived) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Settings frame was not the first frame in the session"); } Http2Logger.LogDebug("Incoming frame: " + frame.FrameType); switch (frame.FrameType) { case FrameType.Headers: HandleHeaders(frame as HeadersFrame, out stream); break; case FrameType.Continuation: HandleContinuation(frame as ContinuationFrame, out stream); break; case FrameType.Priority: HandlePriority(frame as PriorityFrame, out stream); break; case FrameType.RstStream: HandleRstFrame(frame as RstStreamFrame, out stream); break; case FrameType.Data: HandleDataFrame(frame as DataFrame, out stream); break; case FrameType.Ping: HandlePingFrame(frame as PingFrame); break; case FrameType.Settings: HandleSettingsFrame(frame as SettingsFrame); if (!(frame as SettingsFrame).IsAck) { // sending ACK settings WriteSettings(new SettingsPair[0], true); } break; case FrameType.WindowUpdate: HandleWindowUpdateFrame(frame as WindowUpdateFrame, out stream); break; case FrameType.GoAway: HandleGoAwayFrame(frame as GoAwayFrame); break; case FrameType.PushPromise: HandlePushPromiseFrame(frame as PushPromiseFrame, out stream); if (stream != null) //This means that sequence is complete { _promisedResources.Add(stream.Id, stream.Headers.GetValue(CommonHeaders.Path)); } break; case FrameType.AltSvc: HandleAltSvcFrame(frame); break; case FrameType.Blocked: HandleBlockedFrame(frame); break; default: /* 12 -> 4.1 * Implementations MUST treat the receipt of an unknown frame type * (any frame types not defined in this document) as a connection * error of type PROTOCOL_ERROR. */ throw new ProtocolError(ResetStatusCode.ProtocolError, "Unknown frame type detected"); } _lastFrame = frame; if (frame is IEndStreamFrame && ((IEndStreamFrame)frame).IsEndStream) { //Tell the stream that it was the last frame Http2Logger.LogDebug("Final frame received for stream id=" + stream.Id); stream.HalfClosedRemote = true; //Promised resource has been pushed if (_promisedResources.ContainsKey(stream.Id)) { _promisedResources.Remove(stream.Id); } } if (stream == null || OnFrameReceived == null) { return; } OnFrameReceived(this, new FrameReceivedEventArgs(stream, frame)); stream.FramesReceived++; } //09 //5.1. Stream States //An endpoint MUST NOT send frames on a closed stream. An endpoint //that receives a frame after receiving a RST_STREAM [RST_STREAM] or //a frame containing a END_STREAM flag on that stream MUST treat //that as a stream error (Section 5.4.2) of type STREAM_CLOSED //[STREAM_CLOSED]. catch (Http2StreamNotFoundException ex) { Http2Logger.LogDebug("Frame for already Closed stream with stream id={0}", ex.Id); var rstFrame = new RstStreamFrame(ex.Id, ResetStatusCode.StreamClosed); Http2Logger.LogDebug("Sending RST_STREAM frame: stream id={0}, status code={1}", rstFrame.StreamId, rstFrame.StatusCode); _writeQueue.WriteFrame(rstFrame); stream.WasRstSent = true; } catch (CompressionError ex) { //The endpoint is unable to maintain the compression context for the connection. Http2Logger.LogError("Compression error occurred: " + ex.Message); Close(ResetStatusCode.CompressionError); } catch (ProtocolError pEx) { Http2Logger.LogError("Protocol error occurred: " + pEx.Message); Close(pEx.Code); } catch (MaxConcurrentStreamsLimitException) { //Remote side tries to open more streams than allowed Dispose(); } catch (Exception ex) { Http2Logger.LogError("Unknown error occurred: " + ex.Message); Close(ResetStatusCode.InternalError); } }