Example #1
0
        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));
        }
Example #4
0
        /// <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());
        }
Example #5
0
    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);
        });
    }