/// <summary> /// Adopts protocol terms into owin environment. /// </summary> /// <param name="headers">The headers.</param> /// <returns></returns> private OwinContext PopulateEnvironment(HeadersList headers) { var owinContext = new OwinContext(); var headersAsDict = headers.ToDictionary(header => header.Key, header => new[] {header.Value}, StringComparer.OrdinalIgnoreCase); owinContext.Environment[CommonOwinKeys.RequestHeaders] = headersAsDict; owinContext.Environment[CommonOwinKeys.ResponseHeaders] = new Dictionary<string, string[]>(); var owinRequest = owinContext.Request; var owinResponse = owinContext.Response; owinRequest.Method = headers.GetValue(CommonHeaders.Method); owinRequest.Path = headers.GetValue(CommonHeaders.Path); owinRequest.CallCancelled = CancellationToken.None; owinRequest.Host = headers.GetValue(CommonHeaders.Host); owinRequest.PathBase = String.Empty; owinRequest.QueryString = String.Empty; owinRequest.Body = new MemoryStream(); owinRequest.Protocol = Protocols.Http2; owinRequest.Scheme = headers.GetValue(CommonHeaders.Scheme) == Uri.UriSchemeHttp ? Uri.UriSchemeHttp : Uri.UriSchemeHttps; owinRequest.RemoteIpAddress = _transportInfo.RemoteIpAddress; owinRequest.RemotePort = Convert.ToInt32(_transportInfo.RemotePort); owinRequest.LocalIpAddress = _transportInfo.LocalIpAddress; owinRequest.LocalPort = _transportInfo.LocalPort; owinResponse.Body = new ResponseStream{Capacity = 16384}; return owinContext; }
public void CompressionSuccessful() { var clientHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/test.txt"), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "http"), }; var clientCompressor = new CompressionProcessor(ConnectionEnd.Client); var serverDecompressor = new CompressionProcessor(ConnectionEnd.Server); var serializedHeaders = clientCompressor.Compress(clientHeaders); var decompressedHeaders = new HeadersList(serverDecompressor.Decompress(serializedHeaders)); foreach (var t in clientHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } var serverHeaders = new HeadersList { new KeyValuePair <string, string>(":status", StatusCode.Code200Ok.ToString()), }; var serverCompressor = new CompressionProcessor(ConnectionEnd.Server); var clientDecompressor = new CompressionProcessor(ConnectionEnd.Client); serializedHeaders = serverCompressor.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressor.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } }
/// <summary> /// Dispatches the incoming frame. /// </summary> /// <param name="frame">The frame.</param> /// <exception cref="System.NotImplementedException"></exception> private void DispatchIncomingFrame(Frame frame) { Http2Stream stream = null; //Spec 03 tells that frame with continues flag MUST be followed by a frame with the same type //and the same stread id. if (_toBeContinuedHeaders.Count != 0) { if (_toBeContinuedFrame.FrameType != frame.FrameType || _toBeContinuedFrame.StreamId != frame.StreamId) { //If not, we must close the session. Dispose(); return; } } try { switch (frame.FrameType) { case FrameType.Headers: Http2Logger.LogDebug("New headers with id = " + frame.StreamId); var headersFrame = (HeadersFrame)frame; var serializedHeaders = new byte[headersFrame.CompressedHeaders.Count]; Buffer.BlockCopy(headersFrame.CompressedHeaders.Array, headersFrame.CompressedHeaders.Offset, serializedHeaders, 0, serializedHeaders.Length); var decompressedHeaders = _comprProc.Decompress(serializedHeaders); var headers = new HeadersList(decompressedHeaders); if (!headersFrame.IsEndHeaders) { _toBeContinuedHeaders.AddRange(decompressedHeaders); _toBeContinuedFrame = headersFrame; break; } headers.AddRange(_toBeContinuedHeaders); _toBeContinuedHeaders.Clear(); _toBeContinuedFrame = null; headersFrame.Headers.AddRange(headers); foreach (var header in headers) { Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value); } stream = GetStream(headersFrame.StreamId); if (stream == null) { if (_ourEnd == ConnectionEnd.Server) { string path = headers.GetValue(":path"); if (path == null) { path = _handshakeHeaders.ContainsKey(":path") ? _handshakeHeaders[":path"] : @"\index.html"; headers.Add(new KeyValuePair<string, string>(":path", path)); } } else { headers.AddRange(_handshakeHeaders); } stream = CreateStream(headers, frame.StreamId); } break; case FrameType.Priority: var priorityFrame = (PriorityFrame)frame; Http2Logger.LogDebug("Priority frame. StreamId: {0} Priority: {1}", priorityFrame.StreamId, priorityFrame.Priority); stream = GetStream(priorityFrame.StreamId); if (_usePriorities) { stream.Priority = priorityFrame.Priority; } break; case FrameType.RstStream: var resetFrame = (RstStreamFrame)frame; stream = GetStream(resetFrame.StreamId); if (stream != null) { Http2Logger.LogDebug("RST frame with code " + resetFrame.StatusCode); stream.Dispose(); } break; case FrameType.Data: var dataFrame = (DataFrame)frame; Http2Logger.LogDebug("Data frame. StreamId:{0} Length:{1}", dataFrame.StreamId, dataFrame.FrameLength); stream = GetStream(dataFrame.StreamId); //Aggressive window update if (stream != null && stream.IsFlowControlEnabled) { stream.WriteWindowUpdate(InitialWindowSize); } break; case FrameType.Ping: var pingFrame = (PingFrame)frame; Http2Logger.LogDebug("Ping frame with StreamId:{0} Payload:{1}", pingFrame.StreamId, pingFrame.Payload.Count); if (pingFrame.FrameLength != PingFrame.PayloadLength) { throw new ProtocolError(ResetStatusCode.ProtocolError, "Ping payload size is not equal to 8"); } if (pingFrame.IsPong) { _wasPingReceived = true; _pingReceived.Set(); } else { var pongFrame = new PingFrame(true, pingFrame.Payload.ToArray()); _writeQueue.WriteFrame(pongFrame); } break; case FrameType.Settings: //Not first frame in the session. //Client initiates connection and sends settings before request. //It means that if server sent settings before it will not be a first frame, //because client initiates connection. if (_ourEnd == ConnectionEnd.Server && !_wasSettingsReceived && (ActiveStreams.Count > 0)) { Dispose(); return; } var settingFrame = (SettingsFrame)frame; Http2Logger.LogDebug("Settings frame. Entry count: {0} StreamId: {1}", settingFrame.EntryCount, settingFrame.StreamId); _wasSettingsReceived = true; _settingsManager.ProcessSettings(settingFrame, this, _flowControlManager); if (_ourEnd == ConnectionEnd.Server && _sessionSocket.SecureProtocol == SecureProtocol.None) { //The HTTP/1.1 request that is sent prior to upgrade is associated with //stream 1 and is assigned the highest possible priority. Stream 1 is //implicitly half closed from the client toward the server, since the //request is completed as an HTTP/1.1 request. After commencing the //HTTP/2.0 connection, stream 1 is used for the response. stream = CreateStream(Priority.Pri0); stream.EndStreamReceived = true; stream.Headers.Add(new KeyValuePair<string, string>(":method", _handshakeHeaders[":method"])); stream.Headers.Add(new KeyValuePair<string, string>(":path", _handshakeHeaders[":path"])); OnFrameReceived(this, new FrameReceivedEventArgs(stream, new HeadersFrame(stream.Id, false))); } break; case FrameType.WindowUpdate: if (_useFlowControl) { var windowFrame = (WindowUpdateFrame)frame; Http2Logger.LogDebug("WindowUpdate frame. Delta: {0} StreamId: {1}", windowFrame.Delta, windowFrame.StreamId); stream = GetStream(windowFrame.StreamId); if (stream != null) { stream.UpdateWindowSize(windowFrame.Delta); stream.PumpUnshippedFrames(); } } break; case FrameType.GoAway: _goAwayReceived = true; Http2Logger.LogDebug("GoAway frame received"); Dispose(); break; default: throw new NotImplementedException(frame.FrameType.ToString()); } if (stream != null && frame is IEndStreamFrame && ((IEndStreamFrame)frame).IsEndStream) { //Tell the stream that it was the last frame Http2Logger.LogDebug("Final frame received for stream with id = " + stream.Id); stream.EndStreamReceived = true; } if (stream != null && OnFrameReceived != null) { OnFrameReceived(this, new FrameReceivedEventArgs(stream, frame)); } } //Frame came for already closed stream. Ignore it. //Spec: //An endpoint that sends RST_STREAM MUST ignore //frames that it receives on closed streams if it sends RST_STREAM. // //An endpoint MUST NOT send frames on a closed stream. An endpoint //that receives a frame after receiving a RST_STREAM or a frame //containing a END_STREAM flag on that stream MUST treat that as a //stream error (Section 5.4.2) of type PROTOCOL_ERROR. catch (Http2StreamNotFoundException) { if (stream != null) { stream.WriteRst(ResetStatusCode.ProtocolError); } else { //GoAway? } } catch (CompressionError ex) { //The endpoint is unable to maintain the compression context for the connection. Http2Logger.LogError("Compression error occurred: " + ex.Message); Close(ResetStatusCode.CompressionError); } catch (ProtocolError pEx) { Http2Logger.LogError("Protocol error occurred: " + pEx.Message); Close(pEx.Code); } }
/// <summary> /// Sends the headers with request headers. /// </summary> /// <param name="pairs">The header pairs.</param> /// <param name="priority">The stream priority.</param> /// <param name="isEndStream">True if initial headers+priority is also the final frame from endpoint.</param> public void SendRequest(HeadersList pairs, int priority, bool isEndStream) { if (_ourEnd == ConnectionEnd.Server) throw new ProtocolError(ResetStatusCode.ProtocolError, "Server should not initiate request"); if (pairs == null) throw new ArgumentNullException("pairs is null"); if (priority < 0 || priority > Constants.MaxPriority) throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority"); var path = pairs.GetValue(CommonHeaders.Path); if (path == null) throw new ProtocolError(ResetStatusCode.ProtocolError, "Invalid request ex"); //09 -> 8.2.2. Push Responses //Once a client receives a PUSH_PROMISE frame and chooses to accept the //pushed resource, the client SHOULD NOT issue any requests for the //promised resource until after the promised stream has closed. if (_promisedResources.ContainsValue(path)) throw new ProtocolError(ResetStatusCode.ProtocolError, "Resource has been promised. Client should not request it."); var stream = CreateStream(priority); stream.WriteHeadersFrame(pairs, isEndStream, true); var streamSequence = _headersSequences.Find(stream.Id); streamSequence.AddHeaders(new HeadersFrame(stream.Id, stream.Priority) { Headers = pairs }); if (OnRequestSent != null) { OnRequestSent(this, new RequestSentEventArgs(stream)); } }
public void HeadersCompression() { var clientHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/Y3A9NTcuNjE2NjY1fjM5Ljg2NjY2NSZsdmw9NyZzdHk9ciZxPVlhcm9zbGF2bA=="), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "https"), }; var clientCompressionProc = new CompressionProcessor(); var serverCompressionProc = new CompressionProcessor(); var serializedHeaders = clientCompressionProc.Compress(clientHeaders); var decompressedHeaders = new HeadersList(serverCompressionProc.Decompress(serializedHeaders)); foreach (var t in clientHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } var serverHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/simpleTest.txt"), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "https"), }; serializedHeaders = serverCompressionProc.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressionProc.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } serverHeaders = new HeadersList { new KeyValuePair <string, string>(":status", StatusCode.Code404NotFound.ToString(CultureInfo.InvariantCulture)), }; serializedHeaders = serverCompressionProc.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressionProc.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } serverHeaders = new HeadersList { new KeyValuePair <string, string>("content-type", "text/plain"), new KeyValuePair <string, string>("last-modified", "Wed, 23 Oct 2013 21:32:06 GMT"), new KeyValuePair <string, string>("etag", "1cedo15cb041fc1"), new KeyValuePair <string, string>("content-length", "749761"), new KeyValuePair <string, string>(":status", StatusCode.Code200Ok.ToString(CultureInfo.InvariantCulture)), }; serializedHeaders = serverCompressionProc.Compress(serverHeaders); decompressedHeaders = new HeadersList(clientCompressionProc.Decompress(serializedHeaders)); foreach (var t in serverHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } clientHeaders = new HeadersList { new KeyValuePair <string, string>(":method", "get"), new KeyValuePair <string, string>(":path", "/index.html"), new KeyValuePair <string, string>(":version", Protocols.Http2), new KeyValuePair <string, string>(":host", "localhost"), new KeyValuePair <string, string>(":scheme", "https"), }; serializedHeaders = clientCompressionProc.Compress(clientHeaders); decompressedHeaders = new HeadersList(serverCompressionProc.Decompress(serializedHeaders)); foreach (var t in clientHeaders) { Assert.Equal(decompressedHeaders.GetValue(t.Key), t.Value); } }
private async Task SendResponse(Http2Stream stream, IOwinContext context) { try { var isFirstWrite = true; var response = context.Response; var respBody = response.Body as ResponseStream; HeadersList responseHeaders = null; if (respBody == null) { stream.WriteRst(ResetStatusCode.InternalError); return; } int contentLen = 0; int read = 0; respBody.OnDataWritten += (sender, args) => { if (isFirstWrite) { Http2Logger.LogDebug("Transfer begin"); if (response.Headers != null) { responseHeaders = new HeadersList(response.Headers); contentLen = int.Parse(responseHeaders.GetValue(CommonHeaders.ContentLength)); } WriteStatus(stream, response.StatusCode, response.StatusCode != StatusCode.Code200Ok, responseHeaders); } isFirstWrite = false; if (read < contentLen) { var temp = new byte[args.Count]; respBody.Seek(0, SeekOrigin.Begin); int tmpRead = respBody.Read(temp, 0, temp.Length); respBody.Seek(0, SeekOrigin.Begin); Debug.Assert(tmpRead > 0); var readBytes = new byte[tmpRead]; Buffer.BlockCopy(temp, 0, readBytes, 0, tmpRead); read += tmpRead; SendDataTo(stream, readBytes, read == contentLen); } }; await _next(context.Environment); //Handle file not found case if (response.StatusCode == StatusCode.Code404NotFound) { WriteStatus(stream, response.StatusCode, response.StatusCode != StatusCode.Code200Ok, responseHeaders); } Http2Logger.LogDebug("Transfer end"); } catch (Exception ex) { EndResponse(stream, ex);; } }