private void SaveDataFrame(Http2Stream stream, DataFrame dataFrame) { string originalPath = stream.Headers.GetValue(CommonHeaders.Path.ToLower()); //If user sets the empty file in get command we return notFound webpage string fileName = string.IsNullOrEmpty(Path.GetFileName(originalPath)) ? Index : Path.GetFileName(originalPath); string path = Path.Combine(AssemblyPath, fileName); try { _fileHelper.SaveToFile(dataFrame.Data.Array, dataFrame.Data.Offset, dataFrame.Data.Count, path, stream.ReceivedDataAmount != 0); } catch (IOException) { Http2Logger.LogError("File is still downloading. Repeat request later"); stream.Close(ResetStatusCode.InternalError); return; } stream.ReceivedDataAmount += dataFrame.Data.Count; if (dataFrame.IsEndStream) { if (stream.HalfClosedRemote) { //send terminator stream.WriteDataFrame(new ArraySegment<byte>(new byte[0]), true); Http2Logger.LogConsole("Terminator was sent"); } _fileHelper.RemoveStream(path); Http2Logger.LogConsole("Bytes received: " + stream.ReceivedDataAmount); } }
protected override void ProcessRequest(Http2Stream stream, Frame frame) { //spec 09 -> 8.1.3.2. Response Header Fields //A single ":status" header field is defined that carries the HTTP //status code field (see [HTTP-p2], Section 6). This header field MUST //be included in all responses, otherwise the response is malformed if (stream.Headers.GetValue(CommonHeaders.Status) == null) { throw new ProtocolError(ResetStatusCode.ProtocolError, "no status header in response. StreamId = " + stream.Id); } int code; if (!int.TryParse(stream.Headers.GetValue(CommonHeaders.Status), out code)) { stream.WriteRst(ResetStatusCode.ProtocolError); //Got something strange in the status field stream.Close(ResetStatusCode.ProtocolError); } //got some king of error if (code != StatusCode.Code200Ok) { //Close server's stream stream.Close(ResetStatusCode.Cancel); //will dispose client's stream and close server's one. } //Do nothing. Client may not process requests for now }
protected override void ProcessIncomingData(Http2Stream stream, Frame frame) { //wont process incoming non data frames for now. if (!(frame is DataFrame)) return; var dataFrame = frame as DataFrame; SaveDataFrame(stream, dataFrame); }
private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream) { if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Last frame was not headers or continuation"); Http2Logger.LogDebug("New continuation with id = " + contFrame.StreamId); if (contFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with 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(seq => seq.StreamId == 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 == null) { stream = CreateStream(sequence.Headers, contFrame.StreamId, sequence.Priority); } else { stream.Headers.AddRange(sequence.Headers); } }
private void ValidateHeaders(Http2Stream stream) { /* 12 -> 8.1.3 Header field names MUST be converted to lowercase prior to their encoding in HTTP/2.0. A request or response containing uppercase header field names MUST be treated as malformed. */ foreach (var header in stream.Headers) { string key = header.Key; if (!_matcher.IsMatch(key) || key == ":") { stream.WriteRst(ResetStatusCode.RefusedStream); stream.Close(ResetStatusCode.RefusedStream); break; } } }
/// <summary> /// Overrides request processing logic. /// </summary> /// <param name="stream">The stream.</param> /// <param name="frame">The request header frame.</param> /// <returns></returns> protected override void ProcessRequest(Http2Stream stream, Frame frame) { //spec 06 //8.1.2.1. Request Header Fields //HTTP/2.0 defines a number of headers starting with a ':' character //that carry information about the request target: //o The ":method" header field includes the HTTP method ([HTTP-p2], // Section 4). //o The ":scheme" header field includes the scheme portion of the // target URI ([RFC3986], Section 3.1). //o The ":host" header field includes the authority portion of the // target URI ([RFC3986], Section 3.2). //o The ":path" header field includes the path and query parts of the //target URI (the "path-absolute" production from [RFC3986] and //optionally a '?' character followed by the "query" production, see //[RFC3986], Section 3.3 and [RFC3986], Section 3.4). This field //MUST NOT be empty; URIs that do not contain a path component MUST //include a value of '/', unless the request is an OPTIONS request //for '*', in which case the ":path" header field MUST include '*'. //All HTTP/2.0 requests MUST include exactly one valid value for all of //these header fields. An intermediary MUST ensure that requests that //it forwards are correct. A server MUST treat the absence of any of //these header fields, presence of multiple values, or an invalid value //as a stream error (Section 5.4.2) of type PROTOCOL_ERROR. if (stream.Headers.GetValue(CommonHeaders.Method) == null || stream.Headers.GetValue(CommonHeaders.Path) == null || stream.Headers.GetValue(CommonHeaders.Scheme) == null || stream.Headers.GetValue(CommonHeaders.Host) == null) { stream.WriteRst(ResetStatusCode.ProtocolError); return; } Task.Factory.StartNew(async () => { var context = PopulateEnvironment(stream.Headers); await SendResponse(stream, context); }); }
private void ValidateHeaders(Http2Stream stream) { /* 12 -> 8.1.3 * Header field names MUST be converted to lowercase prior to their * encoding in HTTP/2.0. A request or response containing uppercase * header field names MUST be treated as malformed. */ foreach (var header in stream.Headers) { string key = header.Key; if (!_matcher.IsMatch(key) || key == ":") { stream.WriteRst(ResetStatusCode.RefusedStream); stream.Close(ResetStatusCode.RefusedStream); break; } } }
private void HandleDataFrame(DataFrame dataFrame, out Http2Stream stream) { Http2Logger.LogDebug("DATA frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}, pad low={4}, " + "end stream={5}", dataFrame.StreamId, dataFrame.PayloadLength, dataFrame.HasPadding, dataFrame.PadHigh, dataFrame.PadLow, dataFrame.IsEndStream); /* 12 -> 6.1 * DATA frames MUST be associated with a stream. If a DATA frame is * received whose stream identifier field is 0x0, the recipient MUST * respond with a connection error of type PROTOCOL_ERROR. */ if (dataFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with stream id=0"); } /* 12 -> 6.1 * An endpoint that has not enabled DATA frame compression MUST * treat the receipt of a DATA frame with the COMPRESSED flag set as a * connection error of type PROTOCOL_ERROR. */ if (dataFrame.IsCompressed) { throw new ProtocolError(ResetStatusCode.ProtocolError, "GZIP compression is not enabled"); } stream = GetStream(dataFrame.StreamId); if (stream.Closed) { throw new Http2StreamNotFoundException(dataFrame.StreamId); } if (!(stream.Opened || stream.HalfClosedLocal)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "data in non opened or half closed local stream"); } if (stream.IsFlowControlEnabled && !dataFrame.IsEndStream) { stream.WriteWindowUpdate(Constants.MaxFramePayloadSize); } }
private void HandleDataFrame(DataFrame dataFrame, out Http2Stream stream) { stream = GetStream(dataFrame.StreamId); //Aggressive window update if (stream != null) { Http2Logger.LogDebug("Data frame. StreamId: {0} Length: {1}", dataFrame.StreamId, dataFrame.FrameLength); if (stream.IsFlowControlEnabled) { stream.WriteWindowUpdate(Constants.MaxFrameContentSize); } } else { throw new Http2StreamNotFoundException(dataFrame.StreamId); } }
private void HandlePriority(PriorityFrame priorityFrame, out Http2Stream stream) { Http2Logger.LogDebug("PRIORITY frame: stream id={0}, exclusive={1}, dependency={2}, weight={3}", priorityFrame.StreamId, priorityFrame.Exclusive, priorityFrame.StreamDependency, priorityFrame.Weight); /* 12 -> 6.3 * The PRIORITY frame is associated with an existing stream. If a * PRIORITY frame is received with a stream identifier of 0x0, the * recipient MUST respond with a connection error of type PROTOCOL_ERROR. */ if (priorityFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming priority frame with stream id=0"); } stream = GetStream(priorityFrame.StreamId); /* 12 -> 6.3 * The PRIORITY frame can be sent on a stream in any of the "reserved * (remote)", "open", "half-closed (local)", or "half closed (remote)" * states, though it cannot be sent between consecutive frames that * comprise a single header block. */ if (stream.Closed) { throw new Http2StreamNotFoundException(priorityFrame.StreamId); } if (!(stream.Opened || stream.ReservedRemote || stream.HalfClosedLocal)) { throw new ProtocolError(ResetStatusCode.ProtocolError, "priority for non opened or reserved stream"); } if (!_usePriorities) { return; } stream.Priority = priorityFrame.Weight; }
/// <summary> /// Creates stream. /// </summary> /// <param name="headers"></param> /// <param name="streamId"></param> /// <param name="priority"></param> /// <returns></returns> private 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 (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var stream = new Http2Stream(headers, streamId, _writeQueue, _flowControlManager, _comprProc, priority); ActiveStreams[stream.Id] = stream; stream.OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } }; return stream; }
public Http2Session(Stream stream, ConnectionEnd end, bool usePriorities, bool useFlowControl, bool isSecure, CancellationToken cancel, int initialWindowSize = Constants.InitialFlowControlWindowSize, int maxConcurrentStreams = Constants.DefaultMaxConcurrentStreams) { if (stream == null) { throw new ArgumentNullException("stream is null"); } if (cancel == null) { throw new ArgumentNullException("cancellation token is null"); } if (maxConcurrentStreams <= 0) { throw new ArgumentOutOfRangeException("maxConcurrentStreams cant be less or equal then 0"); } if (initialWindowSize <= 0 && useFlowControl) { throw new ArgumentOutOfRangeException("initialWindowSize cant be less or equal then 0"); } _ourEnd = end; _usePriorities = usePriorities; _useFlowControl = useFlowControl; _isSecure = isSecure; _cancelSessionToken = cancel; if (_ourEnd == ConnectionEnd.Client) { _remoteEnd = ConnectionEnd.Server; _lastId = -1; // Streams opened by client are odd //if we got unsecure connection then server will respond with id == 1. We cant initiate //new stream with id == 1. if (!(stream is SslStream)) { _lastId = 3; } } else { _remoteEnd = ConnectionEnd.Client; _lastId = 0; // Streams opened by server are even } _goAwayReceived = false; _comprProc = new CompressionProcessor(); _ioStream = stream; _frameReader = new FrameReader(_ioStream); _writeQueue = new WriteQueue(_ioStream, _comprProc, _usePriorities); OurMaxConcurrentStreams = maxConcurrentStreams; RemoteMaxConcurrentStreams = maxConcurrentStreams; InitialWindowSize = initialWindowSize; _flowControlManager = new FlowControlManager(this); if (!_useFlowControl) { _flowControlManager.Options = (byte)FlowControlOptions.DontUseFlowControl; } SessionWindowSize = 0; _headersSequences = new HeadersSequenceList(); _promisedResources = new Dictionary <int, string>(); StreamDictionary = new StreamDictionary(); for (byte i = 0; i < OurMaxConcurrentStreams; i++) { var http2Stream = new Http2Stream(new HeadersList(), i + 1, _writeQueue, _flowControlManager) { Idle = true }; StreamDictionary.Add(new KeyValuePair <int, Http2Stream>(i + 1, http2Stream)); } _flowControlManager.SetStreamDictionary(StreamDictionary); _writeQueue.SetStreamDictionary(StreamDictionary); }
private void SaveDataFrame(Http2Stream stream, DataFrame dataFrame) { string originalPath = stream.Headers.GetValue(CommonHeaders.Path.ToLower()); //If user sets the empty file in get command we return notFound webpage string fileName = string.IsNullOrEmpty(Path.GetFileName(originalPath)) ? Index : Path.GetFileName(originalPath); string path = Path.Combine(AssemblyPath, fileName); try { _fileHelper.SaveToFile(dataFrame.Data.Array, dataFrame.Data.Offset, dataFrame.Data.Count, path, stream.ReceivedDataAmount != 0); } catch (IOException) { Http2Logger.LogError("File is still downloading. Repeat request later"); //stream.WriteDataFrame(new byte[0], true); //RST always has endstream flag //_fileHelper.RemoveStream(path); stream.Dispose(ResetStatusCode.InternalError); return; } stream.ReceivedDataAmount += dataFrame.FrameLength; if (dataFrame.IsEndStream) { if (!stream.EndStreamSent) { //send terminator stream.WriteDataFrame(new byte[0], true); Http2Logger.LogConsole("Terminator was sent"); } _fileHelper.RemoveStream(path); Http2Logger.LogConsole("Bytes received " + stream.ReceivedDataAmount); #if DEBUG const string wayToServerRoot1 = @"..\..\..\..\Drop\Root"; const string wayToServerRoot2 = @".\Root"; var areFilesEqual = _fileHelper.CompareFiles(path, wayToServerRoot1 + originalPath) || _fileHelper.CompareFiles(path, wayToServerRoot2 + originalPath); if (!areFilesEqual) { Console.ForegroundColor = ConsoleColor.Red; Http2Logger.LogError("Files are NOT EQUAL!"); } else { Console.ForegroundColor = ConsoleColor.Green; Http2Logger.LogConsole("Files are EQUAL!"); } Console.ForegroundColor = ConsoleColor.Gray; #endif } }
/// <summary> /// Processes the request. /// </summary> /// <param name="stream">The stream.</param> /// <param name="frame">The request header frame.</param> /// <returns></returns> protected abstract void ProcessRequest(Http2Stream stream, Frame frame);
/// <summary> /// Creates new http2 stream. /// </summary> /// <param name="priority">The stream priority.</param> /// <returns></returns> /// <exception cref="System.InvalidOperationException">Thrown when trying to create more streams than allowed by the remote side</exception> public Http2Stream CreateStream(int priority) { if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (ActiveStreams.GetOpenedStreamsBy(_ourEnd) + 1 > RemoteMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var id = GetNextId(); if (_usePriorities) { ActiveStreams[id] = new Http2Stream(id, _writeQueue, _flowControlManager, priority); } else { ActiveStreams[id] = new Http2Stream(id, _writeQueue, _flowControlManager); } var streamSequence = new HeadersSequence(id, (new HeadersFrame(id, priority))); _headersSequences.Add(streamSequence); ActiveStreams[id].OnFrameSent += (o, args) => { if (!(args.Frame is IHeadersFrame)) return; var streamSeq = _headersSequences.Find(id); streamSeq.AddHeaders(args.Frame as IHeadersFrame); }; ActiveStreams[id].OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Can't remove stream from ActiveStreams."); } var streamSeq = _headersSequences.Find(id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; return ActiveStreams[id]; }
private void ValidateHeaders(Http2Stream stream) { //spec 09 -> 8.1.3. HTTP Header Fields //Header field names MUST be //converted to lowercase prior to their encoding in HTTP/2.0. A //request or response containing uppercase header field names MUST be //treated as malformed (Section 8.1.3.5). foreach (var header in stream.Headers) { if (!_matcher.IsMatch(header.Key)) { stream.WriteRst(ResetStatusCode.RefusedStream); stream.Dispose(ResetStatusCode.RefusedStream); break; } } }
/// <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); } }
private void HandleRstFrame(RstStreamFrame resetFrame, out Http2Stream stream) { stream = GetStream(resetFrame.StreamId); //Spec 06 tells that impl MUST not answer with rst on rst to avoid loop. if (stream != null) { Http2Logger.LogDebug("RST frame with code {0} for id {1}", resetFrame.StatusCode, resetFrame.StreamId); stream.Dispose(ResetStatusCode.None); } //Do not signal an error because (06) //WINDOW_UPDATE [WINDOW_UPDATE], PRIORITY [PRIORITY], or RST_STREAM //[RST_STREAM] frames can be received in this state for a short //period after a frame containing an END_STREAM flag is sent. }
private void HandleRstFrame(RstStreamFrame resetFrame, out Http2Stream stream) { //spec 09 //6.4. RST_STREAM //RST_STREAM frames MUST be associated with a stream. If a RST_STREAM //frame is received with a stream identifier of 0x0, the recipient MUST //treat this as a connection error (Section 5.4.1) of type //PROTOCOL_ERROR. if (resetFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "resetFrame with id = 0"); } stream = GetStream(resetFrame.StreamId); //09 -> 5.4.2. Stream Error Handling //An endpoint MUST NOT send a RST_STREAM in response to an RST_STREAM //frame, to avoid looping. if (stream == null) return; Http2Logger.LogDebug("RST frame with code {0} for id {1}", resetFrame.StatusCode, resetFrame.StreamId); stream.Dispose(ResetStatusCode.None); //09 -> 5.1. Stream States //WINDOW_UPDATE, PRIORITY, or RST_STREAM frames can be received in //this state for a short period after a DATA or HEADERS frame //containing an END_STREAM flag is sent. Until the remote peer //receives and processes the frame bearing the END_STREAM flag, it //might send frame of any of these types. Endpoints MUST ignore //WINDOW_UPDATE, PRIORITY, or RST_STREAM frames received in this //state, though endpoints MAY choose to treat frames that arrive a //significant time after sending END_STREAM as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR. }
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); } }
private void HandleDataFrame(DataFrame dataFrame, out Http2Stream stream) { //09 -> 6.1. DATA //DATA frames MUST be associated with a stream. If a DATA frame is //received whose stream identifier field is 0x0, the recipient MUST //respond with a connection error (Section 5.4.1) of type //PROTOCOL_ERROR. if (dataFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with id = 0"); } stream = GetStream(dataFrame.StreamId); //Aggressive window update if (stream != null) { Http2Logger.LogDebug("Data frame. StreamId: {0} Length: {1}", dataFrame.StreamId, dataFrame.FrameLength); if (stream.IsFlowControlEnabled) { stream.WriteWindowUpdate(Constants.MaxFrameContentSize); } } else { throw new Http2StreamNotFoundException(dataFrame.StreamId); } }
/// <summary> /// Creates new http2 stream. /// </summary> /// <param name="priority">The stream priority.</param> /// <returns></returns> /// <exception cref="System.InvalidOperationException">Thrown when trying to create more streams than allowed by the remote side</exception> private Http2Stream CreateStream(int priority) { if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (ActiveStreams.GetOpenedStreamsBy(_ourEnd) + 1 > RemoteMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var id = GetNextId(); if (_usePriorities) { ActiveStreams[id] = new Http2Stream(id, _writeQueue, _flowControlManager, _comprProc, priority); } else { ActiveStreams[id] = new Http2Stream(id, _writeQueue, _flowControlManager, _comprProc); } ActiveStreams[id].OnClose += (o, args) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Can't remove stream from ActiveStreams."); } var streamSequence = _headersSequences.Find(seq => seq.StreamId == args.Id); if (streamSequence != null) _headersSequences.Remove(streamSequence); }; ActiveStreams[id].OnFrameSent += (o, args) => { if (OnFrameSent != null) { OnFrameSent(o, args); } }; return ActiveStreams[id]; }
private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream) { if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Last frame was not headers or continuation"); Http2Logger.LogDebug("New continuation with id = " + contFrame.StreamId); //09 -> 6.10. CONTINUATION //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 (Section 5.4.1) of //type PROTOCOL_ERROR. if (contFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming continuation frame with 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 == null) { stream = CreateStream(sequence); ValidateHeaders(stream); } else { stream.Headers = sequence.Headers; ValidateHeaders(stream); } }
/// <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 (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } var stream = new Http2Stream(headers, streamId, _writeQueue, _flowControlManager, priority); var streamSequence = new HeadersSequence(streamId, (new HeadersFrame(streamId, priority){Headers = headers})); _headersSequences.Add(streamSequence); ActiveStreams[stream.Id] = stream; 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) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; return stream; }
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 HandleWindowUpdateFrame(WindowUpdateFrame windowUpdateFrame, out Http2Stream stream) { if (_useFlowControl) { Http2Logger.LogDebug("WindowUpdate frame. Delta: {0} StreamId: {1}", windowUpdateFrame.Delta, windowUpdateFrame.StreamId); stream = GetStream(windowUpdateFrame.StreamId); //09 -> 6.9. WINDOW_UPDATE //The payload of a WINDOW_UPDATE frame is one reserved bit, plus an //unsigned 31-bit integer indicating the number of bytes that the //sender can transmit in addition to the existing flow control window. //The legal range for the increment to the flow control window is 1 to //2^31 - 1 (0x7fffffff) bytes. if (!(0 < windowUpdateFrame.Delta && windowUpdateFrame.Delta <= Constants.MaxPriority)) { Http2Logger.LogDebug("Incorrect window update delta : {0}", windowUpdateFrame.Delta); throw new ProtocolError(ResetStatusCode.FlowControlError, String.Format("Incorrect window update delta : {0}", windowUpdateFrame.Delta)); } //TODO Remove this hack //Connection window size if (windowUpdateFrame.StreamId == 0) { _flowControlManager.StreamsInitialWindowSize += windowUpdateFrame.Delta; } //09 -> 5.1. Stream States //A receiver can ignore WINDOW_UPDATE [WINDOW_UPDATE] or PRIORITY //[PRIORITY] frames in this state. if (stream != null) { stream.UpdateWindowSize(windowUpdateFrame.Delta); stream.PumpUnshippedFrames(); } //09 -> 5.1. Stream States //WINDOW_UPDATE, PRIORITY, or RST_STREAM frames can be received in //this state for a short period after a DATA or HEADERS frame //containing an END_STREAM flag is sent. Until the remote peer //receives and processes the frame bearing the END_STREAM flag, it //might send frame of any of these types. Endpoints MUST ignore //WINDOW_UPDATE, PRIORITY, or RST_STREAM frames received in this //state, though endpoints MAY choose to treat frames that arrive a //significant time after sending END_STREAM as a connection error //(Section 5.4.1) of type PROTOCOL_ERROR. } else { stream = null; } }
private void HandlePriority(PriorityFrame priorityFrame, out Http2Stream stream) { //spec 06: //The PRIORITY frame is associated with an existing stream. If a //PRIORITY frame is received with a stream identifier of 0x0, the //recipient MUST respond with a connection error (Section 5.4.1) of //type PROTOCOL_ERROR [PROTOCOL_ERROR]. if (priorityFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming priority frame with id = 0"); } Http2Logger.LogDebug("Priority frame. StreamId: {0} Priority: {1}", priorityFrame.StreamId, priorityFrame.Priority); stream = GetStream(priorityFrame.StreamId); if (!_usePriorities) return; //06 //A receiver can ignore WINDOW_UPDATE [WINDOW_UPDATE] or PRIORITY //[PRIORITY] frames in this state. if (stream != null && !stream.EndStreamReceived) { stream.Priority = priorityFrame.Priority; } //Do not signal an error because (06) //WINDOW_UPDATE [WINDOW_UPDATE], PRIORITY [PRIORITY], or RST_STREAM //[RST_STREAM] frames can be received in this state for a short //period after a frame containing an END_STREAM flag is sent. }
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 HandleWindowUpdateFrame(WindowUpdateFrame windowUpdateFrame, out Http2Stream stream) { if (_useFlowControl) { Http2Logger.LogDebug("WindowUpdate frame. Delta: {0} StreamId: {1}", windowUpdateFrame.Delta, windowUpdateFrame.StreamId); stream = GetStream(windowUpdateFrame.StreamId); //06 //The legal range for the increment to the flow control window is 1 to //2^31 - 1 (0x7fffffff) bytes. if (!(0 < windowUpdateFrame.Delta && windowUpdateFrame.Delta <= Constants.MaxPriority)) { Http2Logger.LogDebug("Incorrect window update delta : {0}", windowUpdateFrame.Delta); throw new ProtocolError(ResetStatusCode.FlowControlError, String.Format("Incorrect window update delta : {0}", windowUpdateFrame.Delta)); } //06 //A receiver can ignore WINDOW_UPDATE [WINDOW_UPDATE] or PRIORITY //[PRIORITY] frames in this state. if (stream != null) { stream.UpdateWindowSize(windowUpdateFrame.Delta); stream.PumpUnshippedFrames(); } //Do not signal an error because (06) //WINDOW_UPDATE [WINDOW_UPDATE], PRIORITY [PRIORITY], or RST_STREAM //[RST_STREAM] frames can be received in this state for a short //period after a frame containing an END_STREAM flag is sent. } else { stream = null; } }
private void HandleHeaders(HeadersFrame headersFrame, out Http2Stream stream) { Http2Logger.LogDebug("New headers with id = " + headersFrame.StreamId); //spec 06: //If a HEADERS frame //is received whose stream identifier field is 0x0, the recipient MUST //respond with a connection error (Section 5.4.1) of type //PROTOCOL_ERROR [PROTOCOL_ERROR]. if (headersFrame.StreamId == 0) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming headers frame with 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("Stream {0} header: {1}={2}", headersFrame.StreamId, header.Key, header.Value); } headersFrame.Headers.AddRange(headers); var sequence = _headersSequences.Find(seq => seq.StreamId == headersFrame.StreamId); if (sequence == null) { sequence = new HeadersSequence(headersFrame.StreamId, headersFrame); _headersSequences.Add(sequence); } else { sequence.AddHeaders(headersFrame); } if (headersFrame.HasPriority) { sequence.Priority = headersFrame.Priority; } if (!sequence.IsComplete) { stream = null; return; } stream = GetStream(headersFrame.StreamId); if (stream == null) { stream = CreateStream(sequence.Headers, headersFrame.StreamId, sequence.Priority); } else { stream.Headers.AddRange(sequence.Headers); } }
/// <summary> /// Processes the incoming data. /// </summary> /// <param name="stream">The stream.</param> /// <param name="frame"></param> /// <returns></returns> protected abstract void ProcessIncomingData(Http2Stream stream, Frame frame);
internal Http2Stream CreateStream(HeadersSequence sequence) { if (sequence == null) throw new ArgumentNullException("sequence is null"); if (sequence.Priority < 0 || sequence.Priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); if (ActiveStreams.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams) { throw new MaxConcurrentStreamsLimitException(); } int id = sequence.StreamId; int priority = sequence.Priority; var headers = sequence.Headers; var stream = new Http2Stream(headers, id, _writeQueue, _flowControlManager, priority); if (sequence.WasEndStreamReceived) stream.EndStreamReceived = sequence.WasEndStreamReceived; ActiveStreams[stream.Id] = stream; 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) => { if (!ActiveStreams.Remove(ActiveStreams[args.Id])) { throw new ArgumentException("Cant remove stream from ActiveStreams"); } var streamSeq = _headersSequences.Find(stream.Id); if (streamSeq != null) _headersSequences.Remove(streamSeq); }; return stream; }
/// <summary> /// Ends the response in case of error. /// </summary> /// <param name="stream">The stream.</param> /// <param name="ex">The caught exception.</param> private void EndResponse(Http2Stream stream, Exception ex) { Http2Logger.LogDebug("Error processing request:\r\n" + ex); // TODO: What if the response has already started? WriteStatus(stream, StatusCode.Code500InternalServerError, true); }
/// <summary> /// Processes the incoming data. /// </summary> /// <param name="stream">The stream.</param> /// <returns></returns> protected abstract void ProcessIncomingData(Http2Stream stream, Frame frame);
/// <summary> /// Writes the status header to the stream. /// </summary> /// <param name="stream">The stream.</param> /// <param name="statusCode">The status code.</param> /// <param name="final">if set to <c>true</c> then marks headers frame as final.</param> /// <param name="headers">Additional headers</param> private void WriteStatus(Http2Stream stream, int statusCode, bool final, HeadersList headers = null) { if (headers == null) { headers = new HeadersList(); } headers.Add(new KeyValuePair<string, string>(CommonHeaders.Status, statusCode.ToString(CultureInfo.InvariantCulture))); stream.WriteHeadersFrame(headers, final, true); }
internal Http2OwinMessageContext(Http2Stream protocolStream) { _protocolStream = protocolStream; PopulateEnvironment(); }
/// <summary> /// Overrides data processing logic. /// </summary> /// <param name="stream">The stream.</param> /// <param name="frame"></param> /// <returns></returns> protected override void ProcessIncomingData(Http2Stream stream, Frame frame) { //Do nothing... handling data is not supported by the server yet }
protected override void ProcessRequest(Http2Stream stream, Frame frame) { //spec 06 //A client //MUST treat the absence of the ":status" header field, the presence of //multiple values, or an invalid value as a stream error //(Section 5.4.2) of type PROTOCOL_ERROR [PROTOCOL_ERROR]. if (stream.Headers.GetValue(CommonHeaders.Status) == null) { stream.WriteRst(ResetStatusCode.ProtocolError); } //Do nothing. Client may not process requests for now }
/// <summary> /// Overrides request processing logic. /// </summary> /// <param name="stream">The stream.</param> /// <param name="frame">The request header frame.</param> /// <returns></returns> protected override void ProcessRequest(Http2Stream stream, Frame frame) { //spec 09 //8.1.3.1. Request Header Fields //HTTP/2.0 defines a number of header fields starting with a colon ':' //character that carry information about the request target: //o The ":method" header field includes the HTTP method ([HTTP-p2], //Section 4). //o The ":scheme" header field includes the scheme portion of the //target URI ([RFC3986], Section 3.1). //o The ":authority" header field includes the authority portion of //the target URI ([RFC3986], Section 3.2). //To ensure that the HTTP/1.1 request line can be reproduced //accurately, this header field MUST be omitted when translating //from an HTTP/1.1 request that has a request target in origin or //asterisk form (see [HTTP-p1], Section 5.3). Clients that generate //HTTP/2.0 requests directly SHOULD instead omit the "Host" header //field. An intermediary that converts a request to HTTP/1.1 MUST //create a "Host" header field if one is not present in a request by //copying the value of the ":authority" header field. //o The ":path" header field includes the path and query parts of the //target URI (the "path-absolute" production from [RFC3986] and //optionally a '?' character followed by the "query" production, see //[RFC3986], Section 3.3 and [RFC3986], Section 3.4). This field //MUST NOT be empty; URIs that do not contain a path component MUST //include a value of '/', unless the request is an OPTIONS in //asterisk form, in which case the ":path" header field MUST include //'*'. //All HTTP/2.0 requests MUST include exactly one valid value for all of //these header fields, unless this is a CONNECT request (Section 8.3). //An HTTP request that omits mandatory header fields is malformed //(Section 8.1.3.5). //Header field names that contain a colon are only valid in the //HTTP/2.0 context. These are not HTTP header fields. Implementations //MUST NOT generate header fields that start with a colon, but they //MUST ignore any header field that starts with a colon. In //particular, header fields with names starting with a colon MUST NOT //be exposed as HTTP header fields. if (stream.Headers.GetValue(CommonHeaders.Method) == null || stream.Headers.GetValue(CommonHeaders.Path) == null || stream.Headers.GetValue(CommonHeaders.Scheme) == null || stream.Headers.GetValue(CommonHeaders.Authority) == null) { stream.WriteRst(ResetStatusCode.ProtocolError); stream.Dispose(ResetStatusCode.ProtocolError); return; } Task.Factory.StartNew(async () => { try { var context = new Http2OwinMessageContext(stream); var contextEnv = context.OwinContext.Environment; PushFunc pushDelegate = null; pushDelegate = async pairs => { var promisedStream = CreateStream(); //assume that we have already received endStream promisedStream.EndStreamReceived = true; stream.WritePushPromise(pairs, promisedStream.Id); var headers = new HeadersList(pairs); promisedStream.Headers.AddRange(headers); var http2MsgCtx = new Http2OwinMessageContext(promisedStream); var http2PushCtx = http2MsgCtx.OwinContext; http2PushCtx.Set(CommonOwinKeys.ServerPushFunc, pushDelegate); //pass add info from parent to child context. This info can store //reference table for example or something els that should be passed from //client request into child push requests. if (contextEnv.ContainsKey(CommonOwinKeys.AdditionalInfo)) http2PushCtx.Set(CommonOwinKeys.AdditionalInfo, contextEnv[CommonOwinKeys.AdditionalInfo]); await _next(http2PushCtx); http2MsgCtx.FinishResponse(); }; context.OwinContext.Set(CommonOwinKeys.ServerPushFunc, pushDelegate); context.OwinContext.Set(CommonOwinKeys.EnableServerPush, _isPushEnabled); await _next(context.OwinContext); context.FinishResponse(); } catch (Exception ex) { EndResponse(stream, ex); } }); }