private static Frame GetFrameType(Frame preamble) { switch (preamble.FrameType) { case FrameType.GoAway: return new GoAwayFrame(preamble); case FrameType.Ping: return new PingFrame(preamble); case FrameType.RstStream: return new RstStreamFrame(preamble); case FrameType.Settings: return new SettingsFrame(preamble); case FrameType.Headers: return new HeadersFrame(preamble); case FrameType.WindowUpdate: return new WindowUpdateFrame(preamble); case FrameType.Data: return new DataFrame(preamble); case FrameType.PushPromise: return new PushPromiseFrame(preamble); case FrameType.Priority: return new PriorityFrame(preamble); default: throw new NotImplementedException("Frame type: " + preamble.FrameType); } }
public Frame ReadFrame() { if (_isDisposed) return null; var preamble = new Frame(); if (!TryFill(preamble.Buffer, 0, preamble.Buffer.Length)) { return null; } Frame wholeFrame; try { wholeFrame = GetFrameType(preamble); } //09 -> 4.1. Frame Format //Implementations MUST ignore frames of unsupported or unrecognized types catch (NotImplementedException) { return preamble; } if (!TryFill(wholeFrame.Buffer, Constants.FramePreambleSize, wholeFrame.Buffer.Length - Constants.FramePreambleSize)) { return null; } return wholeFrame; }
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); }
/// <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); }); }
// Queue up a fully rendered frame to send public void WriteFrame(Frame frame) { if (frame == null) throw new ArgumentNullException("frame is null"); var priority = frame.StreamId != 0 ? _streams[frame.StreamId].Priority : Constants.DefaultStreamPriority; IQueueItem entry; if (IsPriorityTurnedOn) { entry = new PriorityQueueEntry(frame, priority); } else { entry = new QueueEntry(frame); } _messageQueue.Enqueue(entry); }
// Incoming public SettingsFrame(Frame preamble) : base(preamble) { }
private void HandleAltSvcFrame(Frame altSvcFrame) { Http2Logger.LogDebug("ALTSVC frame: stream id={0}, payload len={1}", altSvcFrame.StreamId, altSvcFrame.PayloadLength); /* 12 -> 6.11 The ALTSVC frame is intended for receipt by clients; a server that receives an ALTSVC frame MUST treat it as a connection error of type PROTOCOL_ERROR. */ if (_ourEnd == ConnectionEnd.Server) throw new ProtocolError(ResetStatusCode.ProtocolError, "Rst frame with stream id=0"); /* 12 -> 6.11 The ALTSVC frame advertises the availability of an alternative service to the client. */ // TODO: receiving ALTSVC frame by client is not implemented. // see https://tools.ietf.org/html/draft-ietf-httpbis-http2-12#section-6.11 // and https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-01 }
// Queue up a fully rendered frame to send public void WriteFrame(Frame frame) { if (frame == null) throw new ArgumentNullException("frame is null"); //Do not write to already closed stream if (frame.FrameType != FrameType.Settings && frame.FrameType != FrameType.GoAway && frame.FrameType != FrameType.Ping && _streams[frame.StreamId] == null) { return; } var priority = frame.StreamId != 0 ? _streams[frame.StreamId].Priority : Constants.DefaultStreamPriority; IQueueItem entry; if (IsPriorityTurnedOn) { entry = new PriorityQueueEntry(frame, priority); } else { entry = new QueueEntry(frame); } _messageQueue.Enqueue(entry); }
public FrameSentArgs(Frame frame) { Frame = frame; }
/// <summary> /// Processes the incoming data. /// </summary> /// <param name="stream">The stream.</param> /// <returns></returns> protected abstract void ProcessIncomingData(Http2Stream stream, Frame frame);
public QueueEntry(Frame frame) { _frame = frame; }
private void HandleBlockedFrame(Frame blockedFrame) { Http2Logger.LogDebug("BLOCKED frame: stream id={0}, payload len={1}", blockedFrame.StreamId, blockedFrame.PayloadLength); /* 12 -> 6.12 The BLOCKED frame defines no flags and contains no payload. A receiver MUST treat the receipt of a BLOCKED frame with a payload as a connection error of type FRAME_SIZE_ERROR. */ if (blockedFrame.PayloadLength > 0) throw new ProtocolError(ResetStatusCode.FrameSizeError, "Blocked frame with non-zero payload"); // TODO: receiving BLCOKED frame is not implemented. // see https://tools.ietf.org/html/draft-ietf-httpbis-http2-12#section-6.12 }
public FrameReceivedEventArgs(Http2Stream stream, Frame frame) { Stream = stream; Frame = frame; }
/// <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); } }); }
/// <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 }
public DataFrameSentEventArgs(Frame frame) { Id = frame.StreamId; DataAmount = frame.Buffer.Length - Constants.FramePreambleSize; }
public PushPromiseFrame(Frame preamble) : base(preamble) { Headers = new HeadersList(); }
// for incoming public PushPromiseFrame(Frame preamble) : base(preamble) { }
/// <summary> /// Create an incoming frame /// </summary> /// <param name="preamble">Frame preamble</param> public HeadersFrame(Frame preamble) : base(preamble) { }
// For incoming frames protected Frame(Frame preamble) : this(new byte[Constants.FramePreambleSize + preamble.PayloadLength]) { System.Buffer.BlockCopy(preamble.Buffer, 0, Buffer, 0, Constants.FramePreambleSize); }
//outgoing public ContinuationFrame(Frame preamble) : base(preamble) { }
/// <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.FrameLength > Constants.MaxFrameContentSize) { throw new ProtocolError(ResetStatusCode.FrameSizeError, String.Format("Frame too large: Type: {0} {1}", frame.FrameType, frame.FrameLength)); } //Settings MUST be first frame in the session from server and //client MUST send settings immediately after connection header. //This means that settings ALWAYS first frame in the session. //This block checks if it doesnt. if (frame.FrameType != FrameType.Settings && !_wasSettingsReceived) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Settings was not the first frame in the session"); } 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) { //Send ack 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; default: //09 -> 4.1. Frame Format //Implementations MUST ignore frames of unsupported or unrecognized types. Http2Logger.LogDebug("Unknown frame received. Ignoring it"); break; } _lastFrame = frame; 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; //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 Id = {0}", ex.Id); GetStream(ex.Id).WriteRst(ResetStatusCode.StreamClosed); } 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); } }
// For incoming public DataFrame(Frame preamble) : base(preamble) { }
/// <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);
public PriorityQueueEntry(Frame frame, int priority) { _frame = frame; _priority = priority; }
// for incoming public PriorityFrame(Frame preamble) : base(preamble) { }
/// <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); } }
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) { /* 12 -> 8.1.3.1 All HTTP/2 requests MUST include exactly one valid value for the ":method", ":scheme", and ":path" 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). */ if (stream.Headers.GetValue(CommonHeaders.Method) == null || stream.Headers.GetValue(CommonHeaders.Path) == null || stream.Headers.GetValue(CommonHeaders.Scheme) == null) { stream.WriteRst(ResetStatusCode.ProtocolError); stream.Close(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.HalfClosedLocal = 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); } }); }