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 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 } }
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.Dispose(ResetStatusCode.InternalError); return; } stream.ReceivedDataAmount += dataFrame.FrameLength; if (dataFrame.IsEndStream) { if (!stream.EndStreamSent) { //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) { stream.WriteRst(ResetStatusCode.ProtocolError); stream.Dispose(ResetStatusCode.ProtocolError); return; } int code; if (!int.TryParse(stream.Headers.GetValue(CommonHeaders.Status), out code)) { stream.WriteRst(ResetStatusCode.ProtocolError); //Got something strange in the status field stream.Dispose(ResetStatusCode.ProtocolError); } //got some king of error if (code != StatusCode.Code200Ok) { //Close server's stream stream.Dispose(ResetStatusCode.Cancel); //will dispose client's stream and close server's one. } //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); } }); }
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; } } }
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. }