public void UpgradeRequestShouldBase64EncodeSettingsCorrectly( Settings settings, string expectedEncoding) { var upgrade = new ClientUpgradeRequestBuilder() .SetHttp2Settings(settings) .Build(); Assert.Equal(expectedEncoding, upgrade.Base64EncodedSettings); }
public async Task ClientUpgradeRequestShouldYieldStream1() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var upgrade = new ClientUpgradeRequestBuilder().Build(); var config = new ConnectionConfigurationBuilder(false) .Build(); var conn = new Connection( config, inPipe, outPipe, new Connection.Options { Logger = loggerProvider.CreateLogger("http2Con"), ClientUpgradeRequest = upgrade, }); await conn.PerformHandshakes(inPipe, outPipe); var stream = await upgrade.UpgradeRequestStream; Assert.Equal(1u, stream.Id); Assert.Equal(1, conn.ActiveStreamCount); Assert.Equal(StreamState.HalfClosedLocal, stream.State); var readHeadersTask = stream.ReadHeadersAsync(); Assert.False(readHeadersTask.IsCompleted); var hEncoder = new Http2.Hpack.Encoder(); await inPipe.WriteHeaders(hEncoder, 1u, false, DefaultStatusHeaders); Assert.True( await Task.WhenAny( readHeadersTask, Task.Delay(ReadableStreamTestExtensions.ReadTimeout)) == readHeadersTask, "Expected to read headers, got timeout"); var headers = await readHeadersTask; Assert.True(headers.SequenceEqual(DefaultStatusHeaders)); Assert.Equal(StreamState.HalfClosedLocal, stream.State); await inPipe.WriteData(1u, 100, 5, true); var data = await stream.ReadAllToArrayWithTimeout(); Assert.True(data.Length == 100); Assert.Equal(StreamState.Closed, stream.State); Assert.Equal(0, conn.ActiveStreamCount); }
async Task <Connection> CreateUpgradeConnection(string host, int port) { // HTTP/2 settings var config = new ConnectionConfigurationBuilder(false) .UseSettings(Settings.Default) .UseHuffmanStrategy(HuffmanStrategy.IfSmaller) .Build(); // Prepare connection upgrade var upgrade = new ClientUpgradeRequestBuilder() .SetHttp2Settings(config.Settings) .Build(); // Create a TCP connection logger.LogInformation($"Starting to connect to {host}:{port}"); var tcpClient = new TcpClient(); await tcpClient.ConnectAsync(host, port); tcpClient.Client.NoDelay = true; logger.LogInformation("Connected to remote"); // Create HTTP/2 stream abstraction on top of the socket var wrappedStreams = tcpClient.Client.CreateStreams(); var upgradeReadStream = new UpgradeReadStream(wrappedStreams.ReadableStream); var needExplicitStreamClose = true; try { // Send a HTTP/1.1 upgrade request with the necessary fields var upgradeHeader = "OPTIONS / HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: Upgrade, HTTP2-Settings\r\n" + "Upgrade: h2c\r\n" + "HTTP2-Settings: " + upgrade.Base64EncodedSettings + "\r\n\r\n"; logger.LogInformation("Sending upgrade request:\n" + upgradeHeader); var encodedHeader = Encoding.ASCII.GetBytes(upgradeHeader); await wrappedStreams.WriteableStream.WriteAsync( new ArraySegment <byte>(encodedHeader)); // Wait for the upgrade response await upgradeReadStream.WaitForHttpHeader(); var headerBytes = upgradeReadStream.HeaderBytes; logger.LogInformation( "Received HTTP/1.1 response: " + Encoding.ASCII.GetString(headerBytes.Array, 0, headerBytes.Count)); // Try to parse the upgrade response as HTTP/1 status and check whether // the upgrade was successful. var response = Http1Response.ParseFrom( Encoding.ASCII.GetString( headerBytes.Array, headerBytes.Offset, headerBytes.Count - 4)); // Mark the HTTP/1.1 bytes as read upgradeReadStream.ConsumeHttpHeader(); if (response.StatusCode != "101") { throw new Exception("Upgrade failed"); } if (!response.Headers.Any(hf => hf.Key == "connection" && hf.Value == "Upgrade") || !response.Headers.Any(hf => hf.Key == "upgrade" && hf.Value == "h2c")) { throw new Exception("Upgrade failed"); } logger.LogInformation("Connection upgrade succesful!"); // If we get here then the connection will be reponsible for closing // the stream needExplicitStreamClose = false; // Build a HTTP connection on top of the stream abstraction var connLogger = verbose ? logProvider.CreateLogger("HTTP2Conn") : null; var conn = new Connection( config, upgradeReadStream, wrappedStreams.WriteableStream, options: new Connection.Options { Logger = connLogger, ClientUpgradeRequest = upgrade, }); // Retrieve the response stream for the connection upgrade. var upgradeStream = await upgrade.UpgradeRequestStream; // As we made the upgrade via a dummy OPTIONS request we are not // really interested in the result of the upgrade request upgradeStream.Cancel(); return(conn); } finally { if (needExplicitStreamClose) { await wrappedStreams.WriteableStream.CloseAsync(); } } }
/// <summary> /// Http2Client /// </summary> /// <param name="host"></param> /// <param name="ip"></param> /// <param name="scheme"></param> /// <param name="path"></param> /// <param name="authority"></param> /// <param name="useHttp1Upgrade"></param> /// <param name="size"></param> /// <param name="timeOut"></param> public Http2Client(string host, int ip, string scheme, string path, string authority, bool useHttp1Upgrade = false, int size = 1024, int timeOut = 180 * 1000) { _path = path; _authority = authority; var options = SocketOptionBuilder.Instance.SetSocket(Sockets.Model.SAEASocketType.Tcp) .UseStream() .SetIP(host) .SetPort(ip) .SetReadBufferSize(size) .SetWriteBufferSize(size) .SetTimeOut(timeOut) .Build(); _clientSocket = SocketFactory.CreateClientSocket(options); _clientSocket.ConnectAsync().GetAwaiter(); var config = new ConnectionConfigurationBuilder(false) .UseSettings(Settings.Default) .UseHuffmanStrategy(HuffmanStrategy.IfSmaller) .Build(); var wrappedStreams = _clientSocket.Socket.CreateStreams(); if (useHttp1Upgrade) { var upgrade = new ClientUpgradeRequestBuilder() .SetHttp2Settings(config.Settings) .Build(); var upgradeReadStream = new UpgradeReadStream(wrappedStreams.ReadableStream); var needExplicitStreamClose = true; try { var upgradeHeader = "OPTIONS / HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: Upgrade, HTTP2-Settings\r\n" + "Upgrade: h2c\r\n" + "HTTP2-Settings: " + upgrade.Base64EncodedSettings + "\r\n\r\n"; var encodedHeader = Encoding.ASCII.GetBytes(upgradeHeader); wrappedStreams.WriteableStream.WriteAsync(new ArraySegment <byte>(encodedHeader)).GetAwaiter(); upgradeReadStream.WaitForHttpHeader().GetAwaiter(); var headerBytes = upgradeReadStream.HeaderBytes; upgradeReadStream.ConsumeHttpHeader(); var response = Http1Response.ParseFrom(Encoding.ASCII.GetString(headerBytes.Array, headerBytes.Offset, headerBytes.Count - 4)); if (response.StatusCode != "101") { throw new Exception("升级失败"); } if (!response.Headers.Any(hf => hf.Key == "connection" && hf.Value == "Upgrade") || !response.Headers.Any(hf => hf.Key == "upgrade" && hf.Value == "h2c")) { throw new Exception("升级失败"); } needExplicitStreamClose = false; var conn = new Connection(config, upgradeReadStream, wrappedStreams.WriteableStream, options: new Connection.Options { ClientUpgradeRequest = upgrade, }); var upgradeStream = upgrade.UpgradeRequestStream.GetAwaiter().GetResult(); upgradeStream.Cancel(); _conn = conn; } finally { if (needExplicitStreamClose) { wrappedStreams.WriteableStream.CloseAsync(); } } } else { _conn = new Connection(config, wrappedStreams.ReadableStream, wrappedStreams.WriteableStream, options: new Connection.Options()); } }
public async Task TheNextOutgoingStreamAfterUpgradeShouldUseId3() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var upgrade = new ClientUpgradeRequestBuilder().Build(); var config = new ConnectionConfigurationBuilder(false) .Build(); var conn = new Connection( config, inPipe, outPipe, new Connection.Options { Logger = loggerProvider.CreateLogger("http2Con"), ClientUpgradeRequest = upgrade, }); await conn.PerformHandshakes(inPipe, outPipe); var stream = await upgrade.UpgradeRequestStream; Assert.Equal(1u, stream.Id); var readHeadersTask = stream.ReadHeadersAsync(); Assert.False(readHeadersTask.IsCompleted); var nextStream = await conn.CreateStreamAsync(DefaultGetHeaders); await outPipe.ReadAndDiscardHeaders(3u, false); Assert.Equal(3u, nextStream.Id); Assert.True(stream != nextStream); Assert.Equal(StreamState.HalfClosedLocal, stream.State); Assert.Equal(StreamState.Open, nextStream.State); var hEncoder = new Http2.Hpack.Encoder(); await inPipe.WriteHeaders(hEncoder, 3u, true, DefaultStatusHeaders); var nextStreamHeaders = await nextStream.ReadHeadersAsync(); Assert.True(nextStreamHeaders.SequenceEqual(DefaultStatusHeaders)); Assert.False(readHeadersTask.IsCompleted); Assert.Equal(StreamState.HalfClosedRemote, nextStream.State); Assert.Equal(StreamState.HalfClosedLocal, stream.State); Assert.Equal(2, conn.ActiveStreamCount); await nextStream.WriteAsync(new ArraySegment <byte>(new byte[0]), true); await outPipe.ReadAndDiscardData(3u, true, 0); Assert.Equal(StreamState.Closed, nextStream.State); Assert.Equal(1, conn.ActiveStreamCount); var headers2 = DefaultStatusHeaders.Append( new HeaderField() { Name = "hh", Value = "vv" }); await inPipe.WriteHeaders(hEncoder, 1u, false, headers2); var streamHeaders = await readHeadersTask; Assert.True(streamHeaders.SequenceEqual(headers2)); await inPipe.WriteData(1u, 10, 0, true); var data = await stream.ReadAllToArrayWithTimeout(); Assert.True(data.Length == 10); Assert.Equal(StreamState.Closed, stream.State); Assert.Equal(0, conn.ActiveStreamCount); }