public static async Task WriteResetStream( this IWriteAndCloseableByteStream stream, uint streamId, ErrorCode errc) { var fh = new FrameHeader { Type = FrameType.ResetStream, Flags = 0, Length = ResetFrameData.Size, StreamId = streamId, }; var data = new ResetFrameData { ErrorCode = errc, }; var dataBytes = new byte[ResetFrameData.Size]; data.EncodeInto(new ArraySegment <byte>(dataBytes)); await stream.WriteFrameHeader(fh); await stream.WriteAsync(new ArraySegment <byte>(dataBytes)); }
public static async Task WriteHeaders( this IWriteAndCloseableByteStream stream, Encoder encoder, uint streamId, bool endOfStream, IEnumerable <HeaderField> headers, bool endOfHeaders = true) { var outBuf = new byte[Settings.Default.MaxFrameSize]; var result = encoder.EncodeInto(new ArraySegment <byte>(outBuf), headers); // Check if all headers could be encoded if (result.FieldCount != headers.Count()) { throw new Exception("Could not encode all headers"); } byte flags = 0; if (endOfHeaders) { flags |= (byte)(HeadersFrameFlags.EndOfHeaders); } if (endOfStream) { flags |= (byte)HeadersFrameFlags.EndOfStream; } var fh = new FrameHeader { Type = FrameType.Headers, Length = result.UsedBytes, Flags = flags, StreamId = streamId, }; await stream.WriteFrameHeader(fh); await stream.WriteAsync( new ArraySegment <byte>(outBuf, 0, result.UsedBytes)); }
public static async Task WriteGoAway( this IWriteAndCloseableByteStream stream, uint lastStreamId, ErrorCode errc, byte[] debugData = null) { if (debugData == null) { debugData = new byte[0]; } var goAwayData = new GoAwayFrameData { Reason = new GoAwayReason { LastStreamId = lastStreamId, ErrorCode = errc, DebugData = new ArraySegment <byte>(debugData), }, }; var fh = new FrameHeader { Type = FrameType.GoAway, Flags = 0, StreamId = 0, Length = goAwayData.RequiredSize, }; var dataBytes = new byte[goAwayData.RequiredSize]; goAwayData.EncodeInto(new ArraySegment <byte>(dataBytes)); await stream.WriteFrameHeader(fh); await stream.WriteAsync(new ArraySegment <byte>(dataBytes)); }
/// <summary> /// 在双向流的顶部创建新的HTTP/2连接 /// </summary> /// <param name="config"></param> /// <param name="inputStream"></param> /// <param name="outputStream"></param> /// <param name="options"></param> public Connection(ConnectionConfiguration config, IReadableByteStream inputStream, IWriteAndCloseableByteStream outputStream, Options?options = null) { if (config == null) { throw new ArgumentNullException(nameof(config)); } this.config = config; if (!config.IsServer) { clientState = new ClientState(); } localSettings = config.Settings; localSettings.EnablePush = false; if (IsServer && options?.ServerUpgradeRequest != null) { serverUpgradeRequest = options.Value.ServerUpgradeRequest; if (!serverUpgradeRequest.IsValid) { throw new ArgumentException( "ServerUpgradeRequest无效.\n" + "无效的HTTP/1升级请求必须被拒绝"); } else { remoteSettings = serverUpgradeRequest.Settings; } } if (inputStream == null) { throw new ArgumentNullException(nameof(inputStream)); } if (outputStream == null) { throw new ArgumentNullException(nameof(outputStream)); } this.inputStream = inputStream; shared.Mutex = new object(); shared.streamMap = new Dictionary <uint, StreamImpl>(); shared.LastOutgoingStreamId = 0u; shared.LastIncomingStreamId = 0u; shared.GoAwaySent = false; shared.Closed = false; shared.PingState = null; if (!IsServer && options?.ClientUpgradeRequest != null) { var upgrade = options.Value.ClientUpgradeRequest; if (!upgrade.IsValid) { throw new ArgumentException( "ClientUpgradeRequest无效。\n" + "无效的升级请求必须被HTTP/1处理程序拒绝"); } localSettings = upgrade.Settings; var newStream = new StreamImpl( this, 1u, StreamState.HalfClosedLocal, (int)localSettings.InitialWindowSize); shared.streamMap[1u] = newStream; shared.LastOutgoingStreamId = 1u; var setStream = upgrade.UpgradeRequestStreamTcs.TrySetResult(newStream); } var dynTableSizeLimit = Math.Min(localSettings.HeaderTableSize, int.MaxValue); writer = new ConnectionWriter( this, outputStream, new ConnectionWriter.Options { MaxFrameSize = (int)remoteSettings.MaxFrameSize, MaxHeaderListSize = (int)remoteSettings.MaxHeaderListSize, InitialWindowSize = (int)remoteSettings.InitialWindowSize, DynamicTableSizeLimit = (int)dynTableSizeLimit, }, new HPack.Encoder.Options { DynamicTableSize = (int)remoteSettings.HeaderTableSize, HuffmanStrategy = config.HuffmanStrategy, } ); nrUnackedSettings++; headerReader = new HeaderReader( new HPack.Decoder(new HPack.Decoder.Options { DynamicTableSizeLimit = (int)dynTableSizeLimit, BufferPool = config.BufferPool, }), localSettings.MaxFrameSize, localSettings.MaxHeaderListSize, inputStream ); readerDone = Task.Run(() => this.RunReaderAsync()); }
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); }); }