/// <summary> /// Gets or creates a new stream for the dictionary. /// </summary> /// <param name="streamId">The stream id.</param> /// <returns>The current stream.</returns> internal ContextStream GetStream(int streamId) { ContextStream stream; if (!_contextStreamDictionary.TryGetValue(streamId, out stream)) { // Get the max stream concurrent streams SettingsPair max = _settings[2]; // If more streams can be created. if (_contextStreamDictionary.Count < max.Value) { // Try to create the stream. stream = new ContextStream(streamId, this); stream.Opened = true; // Add the stream to the connection. _contextStreamDictionary.Add(streamId, stream); return(stream); } else { return(null); } } return(stream); }
/// <summary> /// Validate all headers. /// </summary> /// <param name="stream">The current stream.</param> internal void ValidateHeaders(ContextStream 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 (NameValue header in stream.HttpRequest.OriginalHeaders) { string key = header.Name; if (!_matcher.IsMatch(key) || key == ":") { // Clear all headers found from the list. stream.HttpRequest.HeadersFound = false; stream.HttpRequest.OriginalHeaders.Clear(); // Write to the client indicating that the // headers are incorrect. Utility.ProcessCloseRstStreamFrame(this, ErrorCodeRegistry.Refused_Stream, stream.StreamId); Utility.ProcessClose(this, ErrorCodeRegistry.Refused_Stream, stream); break; } } }
/// <summary> /// Process a priority frame request. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="priorityFrame">The priority frame.</param> /// <param name="stream">The selected stream context.</param> private static void ProcessPriorityFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, PriorityFrame priorityFrame, out ContextStream stream) { /* 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(ErrorCodeRegistry.Protocol_Error, "Incoming priority frame with stream id equal to 0."); } // Attempt to get the sepcific stream. stream = httpContext.GetStream(priorityFrame.StreamId); if (stream == null) { throw new MaxConcurrentStreamsLimitException(); } /* 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 StreamNotFoundException(priorityFrame.StreamId); } if (!(stream.Opened || stream.ReservedRemote || stream.HalfClosedLocal)) { throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Priority for non opened or reserved stream."); } // If using priorities. if (httpContext.UsePriorities) { // Set the priority weight. stream.Priority = priorityFrame.Weight; } }
/// <summary> /// Process a continuation frame request. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="continuationFrame">The continuation frame.</param> /// <param name="stream">The selected stream context.</param> /// <param name="canPassContext">Can the data be sent to the client context.</param> private static void ProcessContinuationFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, ContinuationFrame continuationFrame, out ContextStream stream, out bool canPassContext) { // The data can be sent to the client // through the http context session. canPassContext = false; /* 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 (continuationFrame.StreamId == 0) { throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Incoming continuation frame with stream id is equal to 0."); } // Attempt to get the sepcific stream. stream = httpContext.GetStream(continuationFrame.StreamId); if (stream == null) { throw new MaxConcurrentStreamsLimitException(); } // Get the number of compressed headers. var serializedHeaders = new byte[continuationFrame.CompressedHeaders.Count]; // Copy the compressed frame. Buffer.BlockCopy(continuationFrame.CompressedHeaders.Array, continuationFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); // Decompress the compressed headers. HeadersList decompressedHeaders = new HeaderCompression().Decompress(serializedHeaders); HeadersList headers = new HeadersList(decompressedHeaders); // Add the list of headers. foreach (KeyValuePair <string, string> header in headers) { stream.HttpRequest.OriginalHeaders.Add(new NameValue() { Name = header.Key, Value = header.Value }); } // Determine if all headers have been found. if (continuationFrame.IsEndHeaders) { // The end of the headers has been found. stream.HttpRequest.HeadersFound = true; } bool wasValidated = false; // Check the current stream state. if (stream.Idle || stream.ReservedRemote) { // Validate all the headers. httpContext.ValidateHeaders(stream); wasValidated = true; } else if (stream.Opened || stream.HalfClosedLocal) { // Validate all the headers. httpContext.ValidateHeaders(stream); wasValidated = true; } else if (stream.HalfClosedRemote) { // Half closed remote stream. throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Continuation for half closed remote stream."); } else { // Stream has no state error. throw new StreamNotFoundException(continuationFrame.StreamId); } // If the headers where validated. if (wasValidated) { // If headers have been found. if (stream.HttpRequest.HeadersFound) { // Set the current stream id httpContext.StreamId = stream.StreamId; stream.HttpRequest.IsEndOfData = continuationFrame.IsEndStream; // Get the request resources. RequestResource resource = Nequeo.Net.Http2.Utility.GetRequestResource(stream.HttpRequest.OriginalHeaders); // Assign the http request content. stream.HttpRequest.ReadRequestHeaders(stream.HttpRequest.OriginalHeaders, resource); // If this is the last bit of data // that has been sent then sent // the data to the client context. if (continuationFrame.IsEndStream) { canPassContext = true; } } } }
/// <summary> /// Process a data frame request. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="dataFrame">The data frame.</param> /// <param name="stream">The selected stream context.</param> /// <param name="canPassContext">Can the data be sent to the client context.</param> private static void ProcessDataFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, DataFrame dataFrame, out ContextStream stream, out bool canPassContext) { // The data can be sent to the client // through the http context session. canPassContext = false; /* 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(ErrorCodeRegistry.Protocol_Error, "Incoming continuation frame with stream id is equal to 0."); } // Attempt to get the sepcific stream. stream = httpContext.GetStream(dataFrame.StreamId); if (stream == null) { throw new MaxConcurrentStreamsLimitException(); } // Set the current stream id. httpContext.StreamId = stream.StreamId; // Is the data compressed. stream.HttpRequest.IsCompressed = dataFrame.IsCompressed; stream.HttpRequest.IsEndOfData = dataFrame.IsEndStream; // Write the data to the buffer. stream.HttpRequest.Input.Write(dataFrame.Data.ToArray(), 0, dataFrame.Data.Count); canPassContext = true; }
/// <summary> /// Process a window update frame request. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="windowUpdateFrame">The window update frame.</param> /// <param name="stream">The selected stream context.</param> private static void ProcessWindowUpdateFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, WindowUpdateFrame windowUpdateFrame, out ContextStream stream) { // Attempt to get the sepcific stream. stream = httpContext.GetStream(windowUpdateFrame.StreamId); if (stream == null) { throw new MaxConcurrentStreamsLimitException(); } }
/// <summary> /// Process close connection frame. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="errorCode">Error code registry.</param> /// <param name="stream">The current stream.</param> internal static void ProcessClose(Nequeo.Net.Http2.HttpContext httpContext, ErrorCodeRegistry errorCode, ContextStream stream) { // If the stream has been cancelled of an internal error. if (errorCode == ErrorCodeRegistry.Cancel || errorCode == ErrorCodeRegistry.Internal_Error) { ProcessCloseRstStreamFrame(httpContext, errorCode, stream.StreamId); } // Close flow control manager. stream.FlowControlManager.StreamClosedHandler(stream); // Indicate the stream is closed. stream.Closed = true; stream.Opened = false; // Remove the stream from this list. httpContext.RemoveStream(stream.StreamId); }
/// <summary> /// Process frame requests from the input stream within the current http context. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="canPassContext">True if the current frame processed is a data frame and can be send to the client context.</param> /// <param name="timeout">The maximum time in milliseconds to wait for the end of the header data; -1 wait Indefinitely.</param> /// <param name="maxReadLength">The maximun number of bytes to read before cancelling (must be greater then zero).</param> /// <param name="requestBufferStore">The request buffer store stream.</param> /// <exception cref="System.Exception"></exception> /// <returns>True if the processing of frames was successful; else false.</returns> public static bool ProcessFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, out bool canPassContext, long timeout = -1, int maxReadLength = 0, System.IO.Stream requestBufferStore = null) { bool ret = true; canPassContext = false; ContextStream stream = null; // Http context is null. if (httpContext == null) { return(false); } // Http request context stream is null. if (httpContext.Input == null) { return(false); } // Read the current frame. FrameReader frameReader = null; // If not using the buffer store. if (requestBufferStore == null) { // We need to wait until we get all the header // data then send the context to the server. frameReader = new FrameReader(httpContext.Input); } else { // We need to wait until we get all the header // data then send the context to the server. frameReader = new FrameReader(httpContext.RequestBufferStore); } try { // Attempt to read the current frame. Frame http2Frame = frameReader.ReadFrame(timeout); // If ma frame payload size. if (http2Frame.PayloadLength > Constants.MaxFramePayloadSize) { // Throw exception. throw new ProtocolError(ErrorCodeRegistry.Frame_Size_Error, String.Format("Frame too large: Type: {0} {1}", http2Frame.FrameType, http2Frame.PayloadLength)); } // Select the frame type. switch (http2Frame.FrameType) { case OpCodeFrame.Continuation: ProcessContinuationFrameRequest(httpContext, http2Frame as ContinuationFrame, out stream, out canPassContext); break; case OpCodeFrame.Data: ProcessDataFrameRequest(httpContext, http2Frame as DataFrame, out stream, out canPassContext); break; case OpCodeFrame.Go_Away: ProcessGoAwayFrameRequest(httpContext, http2Frame as GoAwayFrame); break; case OpCodeFrame.Headers: ProcessHeaderFrameRequest(httpContext, http2Frame as HeadersFrame, out stream, out canPassContext); break; case OpCodeFrame.Ping: ProcessPingFrameRequest(httpContext, http2Frame as PingFrame); break; case OpCodeFrame.Priority: ProcessPriorityFrameRequest(httpContext, http2Frame as PriorityFrame, out stream); break; case OpCodeFrame.Push_Promise: ProcessPushPromiseFrameRequest(httpContext, http2Frame as PushPromiseFrame, out stream); break; case OpCodeFrame.Reset_Stream: ProcessResetStreamFrameRequest(httpContext, http2Frame as RstStreamFrame, out stream); break; case OpCodeFrame.Settings: ProcessSettingFrameRequest(httpContext, http2Frame as SettingsFrame); break; case OpCodeFrame.Window_Update: ProcessWindowUpdateFrameRequest(httpContext, http2Frame as WindowUpdateFrame, out stream); break; default: /* 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(ErrorCodeRegistry.Protocol_Error, "Unknown frame type detected."); } // If the frame is type end stream frame and end of stream is true. if (http2Frame is IEndStreamFrame && ((IEndStreamFrame)http2Frame).IsEndStream) { if (stream != null) { // Half closed remote state. stream.HalfClosedRemote = true; } // Promised resource has been pushed. if (httpContext.PromisedResources.ContainsKey(stream.StreamId)) { httpContext.PromisedResources.Remove(stream.StreamId); } } if (stream != null) { // Increment the frame received count. stream.FramesReceived++; } // Set the return value to true // att this point no errors have // been head. ret = true; } catch (StreamNotFoundException snfex) { /* 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].*/ ProcessStreamNotFoundFrame(httpContext, snfex.StreamId); if (stream != null) { stream.WasResetSent = true; } ret = true; } catch (ProtocolError pex) { // Close the connection. ProcessCloseFrame(httpContext, pex.Code); ret = true; } catch (MaxConcurrentStreamsLimitException) { // Close the connection. ProcessCloseFrame(httpContext, ErrorCodeRegistry.No_Error); ret = true; } catch (Exception) { // Close the connection. ProcessCloseFrame(httpContext, ErrorCodeRegistry.Internal_Error); ret = false; } // Return the result. return(ret); }
/// <summary> /// Process a reset stream frame request. /// </summary> /// <param name="httpContext">The current http context.</param> /// <param name="resetFrame">The reset stream frame.</param> /// <param name="stream">The selected stream context.</param> private static void ProcessResetStreamFrameRequest(Nequeo.Net.Http2.HttpContext httpContext, RstStreamFrame resetFrame, out ContextStream 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 of type PROTOCOL_ERROR. */ if (resetFrame.StreamId == 0) { throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Reset stream frame with stream id is equal to 0."); } // Attempt to get the sepcific stream. stream = httpContext.GetStream(resetFrame.StreamId); if (stream == null) { throw new MaxConcurrentStreamsLimitException(); } // If the stream is closed. if (stream.Closed) { /* 12 -> 5.4.2 * An endpoint MUST NOT send a RST_STREAM in response to an RST_STREAM * frame, to avoid looping. */ if (!stream.WasResetSent) { throw new StreamNotFoundException(resetFrame.StreamId); } return; } if (!(stream.ReservedRemote || stream.Opened || stream.HalfClosedLocal)) { throw new ProtocolError(ErrorCodeRegistry.Protocol_Error, "Reset stream for non opened or reserved stream."); } // Close the current stream. ProcessClose(httpContext, ErrorCodeRegistry.No_Error, stream); }