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); }
async Task <Connection> CreateDirectConnection(string host, int port) { // HTTP/2 settings var config = new ConnectionConfigurationBuilder(false) .UseSettings(Settings.Default) .UseHuffmanStrategy(HuffmanStrategy.IfSmaller) .Build(); // Create a TCP connection logger.LogInformation($"Starting to connect to {host}:{port}"); var tcpClient = new TcpClient(); await tcpClient.ConnectAsync(host, port); logger.LogInformation("Connected to remote"); tcpClient.Client.NoDelay = true; // Create HTTP/2 stream abstraction on top of the socket var wrappedStreams = tcpClient.Client.CreateStreams(); // Build a HTTP connection on top of the stream abstraction var connLogger = verbose ? logProvider.CreateLogger("HTTP2Conn") : null; var conn = new Connection( config, wrappedStreams.ReadableStream, wrappedStreams.WriteableStream, options: new Connection.Options { Logger = connLogger, }); return(conn); }
public void Parse(Dictionary<string, string> parameters, ConnectionConfigurationBuilder builder) { if (requiredParameters.All(parameters.ContainsKey)) { configAction(parameters, builder); } }
static async Task AcceptTask(TcpListener listener, ILoggerProvider logProvider) { var connectionId = 0; var config = new ConnectionConfigurationBuilder(isServer: true) .UseStreamListener(AcceptIncomingStream) .UseHuffmanStrategy(HuffmanStrategy.Never) .UseBufferPool(Buffers.Pool) .Build(); while (true) { // Accept TCP sockets var clientSocket = await listener.AcceptSocketAsync(); clientSocket.NoDelay = true; // Create HTTP/2 stream abstraction on top of the socket var wrappedStreams = clientSocket.CreateStreams(); // Alternatively on top of a System.IO.Stream //var netStream = new NetworkStream(clientSocket, true); //var wrappedStreams = netStream.CreateStreams(); // Build a HTTP connection on top of the stream abstraction var http2Con = new Connection( config, wrappedStreams.ReadableStream, wrappedStreams.WriteableStream, options: new Connection.Options { Logger = logProvider.CreateLogger("HTTP2Conn" + connectionId), }); connectionId++; } }
private Connection BuildConnection( bool isServer, IReadableByteStream inputStream, IWriteAndCloseableByteStream outputStream) { ILogger logger = null; if (loggerProvider != null) { logger = loggerProvider.CreateLogger("http2Con"); } // Decrease the timeout for the preface, // as this speeds the test up var config = new ConnectionConfigurationBuilder(isServer) .UseStreamListener((s) => false) .UseClientPrefaceTimeout(200) .Build(); return(new Connection( config, inputStream, outputStream, new Connection.Options { Logger = logger, })); }
private static ConnectionSettings ParseConnectionSettings(string connectionString) { var parser = new ConnectionStringParser(); var builder = new ConnectionConfigurationBuilder(); parser.Parse(connectionString, builder); return builder.ConnectionSettings; }
private static ConnectionSettings ParseConnectionSettings(string connectionString) { var parser = new ConnectionStringParser(); var builder = new ConnectionConfigurationBuilder(); parser.Parse(connectionString, builder); return(builder.ConnectionSettings); }
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); }
public void Parse(string connectionString, ConnectionConfigurationBuilder builder) { var settingsDictionary = connectionString.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim(new[] {' '})) .Select(x => x.Split(new[] {'='}, StringSplitOptions.RemoveEmptyEntries)) .Where(x => x.Length == 1 || x.Length == 2) .ToDictionary(x => x[0], x => x.Length == 1 ? null : x[1]); foreach (var parser in propertyParsers) { parser.Parse(settingsDictionary, builder); } }
static async Task AcceptTask(TcpListener listener, ILoggerProvider logProvider) { var connectionId = 0; var settings = Settings.Default; settings.MaxConcurrentStreams = 50; var config = new ConnectionConfigurationBuilder(true) .UseStreamListener(AcceptIncomingStream) .UseSettings(settings) .UseHuffmanStrategy(HuffmanStrategy.IfSmaller) .Build(); while (true) { // Accept TCP sockets var clientSocket = await listener.AcceptSocketAsync(); clientSocket.NoDelay = true; // Create an SSL stream var sslStream = new SslStream(new NetworkStream(clientSocket, true)); // Authenticate on the stream await sslStream.AuthenticateAsServerAsync(options, CancellationToken.None); // wrap the SslStream var wrappedStreams = sslStream.CreateStreams(); // Build a HTTP connection on top of the stream abstraction var http2Con = new Connection( config, wrappedStreams.ReadableStream, wrappedStreams.WriteableStream, options: new Connection.Options { Logger = logProvider.CreateLogger("HTTP2Conn" + connectionId), }); // 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); }); connectionId++; } }
private async Task <Connection> CreateDirectConnection(Uri uri) { var config = new ConnectionConfigurationBuilder(false) .UseSettings(Settings.Default) .UseHuffmanStrategy(HuffmanStrategy.IfSmaller) .Build(); var tcpClient = new TcpClient(); await tcpClient.ConnectAsync(uri.Host, uri.Port); tcpClient.Client.NoDelay = true; var wrappedStreams = tcpClient.Client.CreateStreams(); return(new Connection( config, wrappedStreams.ReadableStream, wrappedStreams.WriteableStream)); }
private void _serverSocket_OnAccepted(object obj) { var ci = (ChannelInfo)obj; if (ci != null) { var config = new ConnectionConfigurationBuilder(isServer: true) .UseStreamListener(Http2Handler) .UseHuffmanStrategy(HuffmanStrategy.Never) .UseBufferPool(Buffers.Pool) .Build(); var wrappedStreams = ci.ClientSocket.CreateStreams(); var http2Con = new Connection(config, wrappedStreams.ReadableStream, wrappedStreams.WriteableStream, options: new Connection.Options()); } }
Connection BuildConnection( bool isServer, Settings settings, IReadableByteStream inputStream, IWriteAndCloseableByteStream outputStream) { var config = new ConnectionConfigurationBuilder(isServer) .UseSettings(settings) .UseStreamListener((s) => false) .Build(); return(new Connection( config, inputStream, outputStream, new Connection.Options { Logger = loggerProvider.CreateLogger("http2Con"), })); }
public static async Task <Connection> BuildEstablishedConnection( bool isServer, IBufferedPipe inputStream, IBufferedPipe outputStream, ILoggerProvider loggerProvider, Func <IStream, bool> streamListener = null, Settings?localSettings = null, Settings?remoteSettings = null, HuffmanStrategy huffmanStrategy = HuffmanStrategy.Never) { ILogger logger = null; if (loggerProvider != null) { logger = loggerProvider.CreateLogger("http2Con"); } if (streamListener == null) { streamListener = (s) => false; } var config = new ConnectionConfigurationBuilder(isServer) .UseStreamListener(streamListener) .UseHuffmanStrategy(huffmanStrategy) .UseSettings(localSettings ?? Settings.Default) .Build(); var conn = new Connection( config, inputStream, outputStream, new Connection.Options { Logger = logger, }); await PerformHandshakes( conn, inputStream, outputStream, remoteSettings); return(conn); }
static async Task AcceptTask(TcpListener listener, ILoggerProvider logProvider) { var connectionId = 0; var settings = Settings.Default; settings.MaxConcurrentStreams = 50; var config = new ConnectionConfigurationBuilder(true) .UseStreamListener(AcceptIncomingStream) .UseSettings(settings) .UseHuffmanStrategy(HuffmanStrategy.IfSmaller) .Build(); var useUpgrade = true; while (true) { // Accept TCP sockets var clientSocket = await listener.AcceptSocketAsync(); clientSocket.NoDelay = true; // Create HTTP/2 stream abstraction on top of the socket var wrappedStreams = clientSocket.CreateStreams(); // Alternatively on top of a System.IO.Stream //var netStream = new NetworkStream(clientSocket, true); //var wrappedStreams = netStream.CreateStreams(); await HandleConnection( logProvider, config, useUpgrade, wrappedStreams.ReadableStream, wrappedStreams.WriteableStream, connectionId); connectionId++; } }
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); }); }
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); }
/// <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 ConnectionConfiguration Parse(string connectionString) { var builder = new ConnectionConfigurationBuilder(); Parse(connectionString, builder); return builder.Build(); }
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); }
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(); } } }
public override AsyncUnaryCall <TResponse> AsyncUnaryCall <TRequest, TResponse>(Method <TRequest, TResponse> method, string host, CallOptions options, TRequest request) { // The HTTP/2 gRPC protocol is defined here // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md // When debugging, be aware of the timeouts! // You can get some insights about what's going on by running kubectl logs <tiller> -n kube-system -f ConnectionConfiguration config = new ConnectionConfigurationBuilder(isServer: false) .Build(); using (Stream networkStream = this.stream()) { var streams = networkStream.CreateStreams(); Connection http2Connection = new Connection( config: config, inputStream: streams.ReadableStream, outputStream: streams.WriteableStream); var headers = new Collection <HeaderField> { new HeaderField { Name = ":method", Value = "POST" }, new HeaderField { Name = ":scheme", Value = "http" }, new HeaderField { Name = ":path", Value = method.FullName }, new HeaderField { Name = ":authority", Value = "pubsub.googleapis.com" }, new HeaderField { Name = "grpc-timeout", Value = "60S" }, new HeaderField { Name = "content-type", Value = "application/grpc+proto" } }; if (options.Headers != null) { foreach (var header in options.Headers) { headers.Add(new HeaderField() { Name = header.Key, Value = header.Value }); } } var stream = http2Connection.CreateStreamAsync( headers, endOfStream: false).GetAwaiter().GetResult(); var requestMessage = request as IMessage; byte[] buffer = new byte[4]; stream.WriteAsync(new ArraySegment <byte>(buffer, 0, 1), endOfStream: false); int size = requestMessage.CalculateSize(); buffer = BitConverter.GetBytes(size); Array.Reverse(buffer); stream.WriteAsync(new ArraySegment <byte>(buffer, 0, 4), endOfStream: size == 0); if (size > 0) { buffer = requestMessage.ToByteArray(); stream.WriteAsync(new ArraySegment <byte>(buffer, 0, buffer.Length), endOfStream: true); } // Wait for response headers var responseHeaders = stream.ReadHeadersAsync().GetAwaiter().GetResult(); if (this.logger != null) { foreach (var header in responseHeaders) { this.logger.LogTrace("{header.Name} = {header.Value}"); } } var statusCode = responseHeaders.SingleOrDefault(h => h.Name == ":status").Value; var contentType = responseHeaders.SingleOrDefault(h => h.Name == "content-type").Value; var grpcStatusCodeString = responseHeaders.SingleOrDefault(h => h.Name == "grpc-status").Value; var grpcMessage = responseHeaders.SingleOrDefault(h => h.Name == "grpc-message").Value; var grpcStatusCode = grpcStatusCodeString == null ? StatusCode.OK : (StatusCode)Enum.Parse(typeof(StatusCode), grpcStatusCodeString); // Read response data var response = Activator.CreateInstance <TResponse>(); using (MemoryStream ms = new MemoryStream()) { // See https://github.com/Matthias247/http2dotnet/issues/1 var streamImplType = typeof(IStream).Assembly.GetType("Http2.StreamImpl"); var readDataPossibleField = streamImplType.GetField("readDataPossible", BindingFlags.NonPublic | BindingFlags.Instance); var readDataPossible = (AsyncManualResetEvent)readDataPossibleField.GetValue(stream); readDataPossible.Set(); buffer = new byte[1024]; while (true) { var readDataResult = stream.ReadAsync(new ArraySegment <byte>(buffer)).GetAwaiter().GetResult(); ms.Write(buffer, 0, readDataResult.BytesRead); if (readDataResult.EndOfStream) { break; } } var responseMessage = response as IMessage; ms.Position = 0; bool isCompressed = ms.ReadByte() != 0; byte[] lengthBuffer = new byte[4]; ms.Read(lengthBuffer, 0, 4); Array.Reverse(lengthBuffer); var length = BitConverter.ToUInt32(lengthBuffer, 0); this.logger?.LogTrace("Read {length} bytes of data"); responseMessage.MergeFrom(ms); } var responseTrailers = stream.ReadTrailersAsync().GetAwaiter().GetResult(); if (grpcStatusCode != StatusCode.OK) { var status = new Status(grpcStatusCode, grpcMessage); var metadata = new Metadata(); foreach (var trailer in responseTrailers) { metadata.Add(new Metadata.Entry(trailer.Name, trailer.Value)); } throw new Grpc.Core.RpcException(status, metadata); } return(new AsyncUnaryCall <TResponse>(Task.FromResult(response), Task.FromResult((Metadata)null), () => Status.DefaultSuccess, null, null)); } }