/// <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()); } }
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(); } } }
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); }); }