public void CreatingAConnectionWithInvalidUpgradeShouldThrow() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var config = new ConnectionConfigurationBuilder(true) .UseStreamListener(s => false) .Build(); var builder = new ServerUpgradeRequestBuilder(); builder.SetHeaders(DefaultGetHeaders.ToList()); builder.SetHttp2Settings("!"); var upgrade = builder.Build(); Assert.False(upgrade.IsValid); var ex = Assert.Throws <ArgumentException>(() => { var conn = new Connection( config, inPipe, outPipe, new Connection.Options { Logger = loggerProvider.CreateLogger(""), ServerUpgradeRequest = upgrade, }); }); Assert.Equal( "The ServerUpgradeRequest is invalid.\n" + "Invalid upgrade requests must be denied by the HTTP/1 handler", ex.Message); }
public void UpgradesWithPayloadMustHaveMatchingContentLength( int?contentLength, int?payloadLength, bool isOk) { var builder = new ServerUpgradeRequestBuilder(); builder.SetHttp2Settings(""); var headers = DefaultGetHeaders.ToList(); if (contentLength != null) { headers.Add(new HeaderField() { Name = "content-length", Value = contentLength.ToString() }); } builder.SetHeaders(headers); if (payloadLength != null) { const int padding = 3; var pl = new byte[padding + payloadLength.Value]; builder.SetPayload(new ArraySegment <byte>(pl, padding, payloadLength.Value)); } var upgrade = builder.Build(); Assert.Equal(isOk, upgrade.IsValid); }
public void UpgradesWithInvalidEncodedHttp2SettingsStringShouldBeInvalid( string encodedSettings) { var builder = new ServerUpgradeRequestBuilder(); builder.SetHeaders(DefaultGetHeaders.ToList()); builder.SetHttp2Settings(encodedSettings); var upgrade = builder.Build(); Assert.False(upgrade.IsValid); }
public void UpgradesWithInvalidHeadersShouldBeInvalid( HeaderField[] headers) { var builder = new ServerUpgradeRequestBuilder(); builder.SetHttp2Settings(""); builder.SetHeaders(headers.ToList()); var upgrade = builder.Build(); Assert.False(upgrade.IsValid); }
public void ValidHttp2SettingsShouldBeAccepted( byte[] payload) { var builder = new ServerUpgradeRequestBuilder(); builder.SetHeaders(DefaultGetHeaders.ToList()); var base64 = Convert.ToBase64String(payload); base64 = base64.Replace('/', '_'); base64 = base64.Replace('+', '-'); builder.SetHttp2Settings(base64); var upgrade = builder.Build(); Assert.True(upgrade.IsValid); }
public async Task ServerUpgradeRequestsShouldDispatchStream1( int payloadLength) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); int nrAcceptedStreams = 0; IStream stream = null; var handlerDone = new SemaphoreSlim(0); Func <IStream, bool> listener = (s) => { Interlocked.Increment(ref nrAcceptedStreams); Task.Run(() => { stream = s; handlerDone.Release(); }); return(true); }; var startOfPayload = 44; byte[] payload = new byte[payloadLength]; for (var i = 0; i < payloadLength; i++) { payload[i] = (byte)(startOfPayload + i); } var builder = new ServerUpgradeRequestBuilder(); var headers = DefaultGetHeaders.ToList(); if (payloadLength != 0) { builder.SetPayload(new ArraySegment <byte>(payload)); headers.Add(new HeaderField() { Name = "content-length", Value = payloadLength.ToString() }); } builder.SetHeaders(headers); builder.SetHttp2Settings(""); var upgrade = builder.Build(); var config = new ConnectionConfigurationBuilder(true) .UseStreamListener(listener) .Build(); var conn = new Connection( config, inPipe, outPipe, new Connection.Options { Logger = loggerProvider.CreateLogger("http2Con"), ServerUpgradeRequest = upgrade, }); await conn.PerformHandshakes(inPipe, outPipe); var requestDone = await handlerDone.WaitAsync( ReadableStreamTestExtensions.ReadTimeout); Assert.True(requestDone, "Expected handler to complete within timeout"); Assert.Equal(1u, stream.Id); Assert.Equal(StreamState.HalfClosedRemote, stream.State); var rcvdHeaders = await stream.ReadHeadersAsync(); Assert.True(headers.SequenceEqual(rcvdHeaders)); var allData = await stream.ReadAllToArrayWithTimeout(); Assert.Equal(payloadLength, allData.Length); Assert.Equal(payload, allData); Assert.Equal(1, nrAcceptedStreams); }
static async Task HandleConnection( ILoggerProvider logProvider, ConnectionConfiguration config, bool useUpgrade, IReadableByteStream inputStream, IWriteAndCloseableByteStream outputStream, int connectionId) { var upgradeReadStream = new UpgradeReadStream(inputStream); ServerUpgradeRequest upgrade = null; try { // Wait for either HTTP/1 upgrade header or HTTP/2 magic header await upgradeReadStream.WaitForHttpHeader(); var headerBytes = upgradeReadStream.HeaderBytes; if (MaybeHttpStart(headerBytes)) { // This seems to be a HTTP/2 request // No upgrade necessary // Make the header rereadable by the stream reader consumer, // so that the library can also read the preface upgradeReadStream.UnreadHttpHeader(); } else { // This seems to be a HTTP/1 request // Parse the header and check whether it's an upgrade var request = Http1Request.ParseFrom( Encoding.ASCII.GetString( headerBytes.Array, headerBytes.Offset, headerBytes.Count - 4)); // Assure that the HTTP/2 library does not get passed the HTTP/1 request upgradeReadStream.ConsumeHttpHeader(); if (request.Protocol != "HTTP/1.1") { throw new Exception("Invalid upgrade request"); } // If the request has some payload we can't process it in this demo string contentLength; if (request.Headers.TryGetValue("content-length", out contentLength)) { await outputStream.WriteAsync( new ArraySegment <byte>(UpgradeErrorResponse)); await outputStream.CloseAsync(); return; } string connectionHeader; string hostHeader; string upgradeHeader; string http2SettingsHeader; if (!request.Headers.TryGetValue("connection", out connectionHeader) || !request.Headers.TryGetValue("host", out hostHeader) || !request.Headers.TryGetValue("upgrade", out upgradeHeader) || !request.Headers.TryGetValue("http2-settings", out http2SettingsHeader) || upgradeHeader != "h2c" || http2SettingsHeader.Length == 0) { throw new Exception("Invalid upgrade request"); } var connParts = connectionHeader .Split(new char[] { ',' }) .Select(p => p.Trim()) .ToArray(); if (connParts.Length != 2 || !connParts.Contains("Upgrade") || !connParts.Contains("HTTP2-Settings")) { throw new Exception("Invalid upgrade request"); } var headers = new List <HeaderField>(); headers.Add(new HeaderField { Name = ":method", Value = request.Method }); headers.Add(new HeaderField { Name = ":path", Value = request.Path }); headers.Add(new HeaderField { Name = ":scheme", Value = "http" }); foreach (var kvp in request.Headers) { // Skip Connection upgrade related headers if (kvp.Key == "connection" || kvp.Key == "upgrade" || kvp.Key == "http2-settings") { continue; } headers.Add(new HeaderField { Name = kvp.Key, Value = kvp.Value, }); } var upgradeBuilder = new ServerUpgradeRequestBuilder(); upgradeBuilder.SetHeaders(headers); upgradeBuilder.SetHttp2Settings(http2SettingsHeader); upgrade = upgradeBuilder.Build(); if (!upgrade.IsValid) { await outputStream.WriteAsync( new ArraySegment <byte>(UpgradeErrorResponse)); await outputStream.CloseAsync(); return; } // Respond to upgrade await outputStream.WriteAsync( new ArraySegment <byte>(UpgradeSuccessResponse)); } } catch (Exception e) { Console.WriteLine("Error during connection upgrade: {0}", e.Message); await outputStream.CloseAsync(); return; } // Build a HTTP connection on top of the stream abstraction var http2Con = new Connection( config, upgradeReadStream, outputStream, options: new Connection.Options { Logger = logProvider.CreateLogger("HTTP2Conn" + connectionId), ServerUpgradeRequest = upgrade, }); // Close the connection if we get a GoAway from the client var remoteGoAwayTask = http2Con.RemoteGoAwayReason; var closeWhenRemoteGoAway = Task.Run(async() => { await remoteGoAwayTask; await http2Con.GoAwayAsync(ErrorCode.NoError, true); }); }
private async Task HandleConnection( ILogger logger, ConnectionByteStream stream) { var config = new ConnectionConfigurationBuilder(true) .UseStreamListener(arg => { Task.Run(() => { var headers = arg.ReadHeadersAsync().GetAwaiter().GetResult(); var optional = HttpRequest.ParseFrom(new HttpHeaderCollection(headers), new HttpMethodResolver(Methods)); if (optional.ExpectPayload) { var seg = new ArraySegment <byte>(new byte[int.Parse(optional.Headers["content-length"])]); // TODO: Use the StreamReadResult, as sometimes the packets won't all be sent at once arg.ReadAsync(seg).GetAwaiter().GetResult(); optional.Payload = seg.ToArray(); } var req = optional.Request; PassContext(new HttpContext(req, new HttpTwoResponse(arg), stream.Connection, logger)); }); return(true); }) .UseSettings(Settings.Default) .UseHuffmanStrategy(HuffmanStrategy.IfSmaller) .Build(); var upgradeReadStream = new HttpStream(stream); ServerUpgradeRequest upgrade = null; var writeAndCloseableByteStream = stream; try { // Wait for either HTTP/1 upgrade header or HTTP/2 magic header await upgradeReadStream.WaitForHttpHeader(); var headerBytes = upgradeReadStream.HeaderBytes; if (MaybeHttpStart(headerBytes)) { // This seems to be a HTTP/2 request upgradeReadStream.Unread(); } else { // This seems to be a HTTP/1 request // Parse the header and check whether it's an upgrade var req = HttpRequest.ParseFrom( Encoding.ASCII.GetString( headerBytes.Array, headerBytes.Offset, headerBytes.Count - 4), (MethodResolver <HttpMethod>)MethodResolver, stream.Connection); if (req.ExpectPayload && req.Headers.TryGetValue("content-length", out string contentLength)) { if (int.TryParse(contentLength, out var length)) { await upgradeReadStream.WaitForPayloadAsync(length); req.Payload = upgradeReadStream.Payload.ToArray(); } else { await stream.WriteAsync( new ArraySegment <byte>(Encoding.ASCII.GetBytes( "HTTP/1.1 400 Bad Request\r\nServer: SimpleServer/1.0 sshttp2\r\nContent-Type: text/html\r\nContent-Length: 26\r\n\r\nThe payload was too large."))); await stream.CloseAsync(); return; } } else if (req.ExpectPayload) { await stream.WriteAsync( new ArraySegment <byte>(Encoding.ASCII.GetBytes( "HTTP/1.1 400 Bad Request\r\nServer: SimpleServer/1.0 sshttp2\r\nContent-Type: text/html\r\nContent-Length: 25\r\n\r\nNo content-length header."))); await stream.CloseAsync(); return; } // Assure that the HTTP/2 library does not get passed the HTTP/1 request upgradeReadStream.Consume(); var request = req.Request; #pragma warning disable 618 if (request.Protocol != "HTTP/1.1") #pragma warning restore 618 { PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection), stream.Connection, logger)); return; } if (!request.Headers.TryGetValue("connection", out string connectionHeader) || !request.Headers.TryGetValue("host", out string _) || !request.Headers.TryGetValue("upgrade", out string upgradeHeader) || !request.Headers.TryGetValue("http2-settings", out string http2SettingsHeader) || upgradeHeader != "h2c" || http2SettingsHeader.Length == 0) { // STAY H1 PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection), stream.Connection, logger)); return; } var connParts = connectionHeader .Split(',') .Select(p => p.Trim()) .ToArray(); if (connParts.Length != 2 || !connParts.Contains("Upgrade") || !connParts.Contains("HTTP2-Settings")) { PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection), stream.Connection, logger)); } var headers = new List <HeaderField> { new HeaderField { Name = ":method", Value = request.Method.Id }, new HeaderField { Name = ":path", Value = request.RawUrl }, new HeaderField { Name = ":scheme", #if NETCOREAPP2_1 || NETSTANDARD2_1 Value = stream.Connection is SslListener.SecureConnection ? "https" : "http" #else Value = "http" #endif } }; foreach (var kvp in request.Headers) { // Skip Connection upgrade related headers if (kvp.Name == "connection" || kvp.Name == "upgrade" || kvp.Name == "http2-settings") { continue; } headers.Add(new HeaderField { Name = kvp.Name, Value = kvp.Value }); } var upgradeBuilder = new ServerUpgradeRequestBuilder(); upgradeBuilder.SetHeaders(headers); upgradeBuilder.SetPayload(new ArraySegment <byte>(req.Payload)); upgradeBuilder.SetHttp2Settings(http2SettingsHeader); upgrade = upgradeBuilder.Build(); if (!upgrade.IsValid) { // STAY H1 PassContext(new HttpContext(request, new HttpOneResponse(request, stream.Connection), stream.Connection, logger)); return; } // Respond to upgrade await writeAndCloseableByteStream.WriteAsync( new ArraySegment <byte>(UpgradeSuccessResponse)); } } catch (Exception e) { logger?.LogError(new EventId(stream.Connection.Id, "StartFail"), e, "An error occured while trying to upgrade a HTTP/1.X request to a H2 request."); await writeAndCloseableByteStream.CloseAsync(); return; } // Build a H2 connection var http2Con = new Connection( config, upgradeReadStream, writeAndCloseableByteStream, new Connection.Options { Logger = logger, ServerUpgradeRequest = upgrade }); // Close the connection if we get a GoAway from the client var remoteGoAwayTask = http2Con.RemoteGoAwayReason; #pragma warning disable 4014 Task.Run(async() => #pragma warning restore 4014 { await remoteGoAwayTask; await http2Con.GoAwayAsync(ErrorCode.NoError, true); }); }