Example #1
0
        public async Task <bool> Disconnect(TimeSpan timeout)
        {
            var connectionStream = await StreamManager.Get(0).ConfigureAwait(false);

            var semaphoreWait     = new SemaphoreSlim(0);
            var cancelTokenSource = new CancellationTokenSource();
            var sentGoAway        = false;

            var sentDelegate = new Http2Stream.FrameSentDelegate(frame => {
                if (frame.Type == FrameType.GoAway)
                {
                    sentGoAway = true;
                    semaphoreWait.Release();
                }
            });

            connectionStream.OnFrameSent += sentDelegate;

            await connection.QueueFrame(new GoAwayFrame()).ConfigureAwait(false);

            if (timeout != Timeout.InfiniteTimeSpan)
            {
                cancelTokenSource.CancelAfter(timeout);
            }

            await semaphoreWait.WaitAsync(cancelTokenSource.Token).ConfigureAwait(false);

            connectionStream.OnFrameSent -= sentDelegate;

            connection.Disconnect();

            return(sentGoAway);
        }
Example #2
0
        public async Task <bool> Ping(byte[] opaqueData, CancellationToken cancelToken)
        {
            if (opaqueData == null || opaqueData.Length <= 0)
            {
                throw new ArgumentNullException(nameof(opaqueData));
            }

            await connection.Connect().ConfigureAwait(false);

            var semaphoreWait   = new SemaphoreSlim(0);
            var opaqueDataMatch = false;

            var connectionStream = await StreamManager.Get(0).ConfigureAwait(false);

            Http2Stream.FrameReceivedDelegate frameRxAction;
            frameRxAction = new Http2Stream.FrameReceivedDelegate(frame => {
                if (frame is PingFrame pf)
                {
                    opaqueDataMatch = pf.Ack && pf.OpaqueData != null && pf.OpaqueData.SequenceEqual(opaqueData);
                    semaphoreWait.Release();
                }
            });

            // Wire up the event to listen for ping response
            connectionStream.OnFrameReceived += frameRxAction;

            // Construct ping request
            var pingFrame = new PingFrame {
                OpaqueData = new byte[opaqueData.Length]
            };

            opaqueData.CopyTo(pingFrame.OpaqueData, 0);

            // Send ping
            await connection.QueueFrame(pingFrame).ConfigureAwait(false);

            // Wait for either a ping response or timeout
            await semaphoreWait.WaitAsync(cancelToken).ConfigureAwait(false);

            // Cleanup the event
            connectionStream.OnFrameReceived -= frameRxAction;

            return(opaqueDataMatch);
        }
Example #3
0
        public async Task <Http2Response> Send(CancellationToken cancelToken, Uri uri, HttpMethod method, NameValueCollection headers = null, Stream data = null)
        {
            var semaphoreClose = new SemaphoreSlim(0);

            await connection.Connect().ConfigureAwait(false);

            var stream = await StreamManager.Get().ConfigureAwait(false);

            stream.OnFrameReceived += async(frame) =>
            {
                // Check for an end of stream state
                if (stream.State == StreamState.HalfClosedRemote || stream.State == StreamState.Closed)
                {
                    semaphoreClose.Release();
                }
            };

            var sentEndOfStream = false;

            var allHeaders = new NameValueCollection();

            allHeaders.Add(":method", method.Method.ToUpperInvariant());
            allHeaders.Add(":path", uri.PathAndQuery);
            allHeaders.Add(":scheme", uri.Scheme);
            allHeaders.Add(":authority", uri.Authority);
            if (headers != null && headers.Count > 0)
            {
                allHeaders.Add(headers);
            }

            var headerData = Util.PackHeaders(allHeaders, connection.Settings.HeaderTableSize);

            var numFrames = (int)Math.Ceiling((double)headerData.Length / (double)connection.Settings.MaxFrameSize);

            for (var i = 0; i < numFrames; i++)
            {
                // First item is headers frame, others are continuation
                var frame = (i == 0) ?
                            (IFrameContainsHeaders) new HeadersFrame(stream.StreamIdentifer)
                    : (IFrameContainsHeaders) new ContinuationFrame(stream.StreamIdentifer);

                // Set end flag if this is the last item
                if (i == numFrames - 1)
                {
                    frame.EndHeaders = true;
                }

                var maxFrameSize = connection.Settings.MaxFrameSize;

                var amt = maxFrameSize;
                if (i * maxFrameSize + amt > headerData.Length)
                {
                    amt = (uint)headerData.Length - (uint)(i * maxFrameSize);
                }
                frame.HeaderBlockFragment = new byte[amt];
                Array.Copy(headerData, i * maxFrameSize, frame.HeaderBlockFragment, 0, amt);

                // If we won't s end
                if (data == null && frame is HeadersFrame)
                {
                    sentEndOfStream = true;
                    (frame as HeadersFrame).EndStream = true;
                }

                await connection.QueueFrame(frame).ConfigureAwait(false);
            }

            if (data != null)
            {
                var supportsPosLength = true; // Keep track of if we threw exceptions trying pos/len of stream

                // Break stream up into data frames within allowed size
                var dataFrameBuffer = new byte[connection.Settings.MaxFrameSize];
                while (true)
                {
                    var rd = await data.ReadAsync(dataFrameBuffer, 0, dataFrameBuffer.Length).ConfigureAwait(false);

                    if (rd <= 0)
                    {
                        break;
                    }

                    // Make a new data frame with a buffer the size we read
                    var dataFrame = new DataFrame(stream.StreamIdentifer)
                    {
                        Data = new byte[rd]
                    };

                    // Copy over the data we read
                    Array.Copy(dataFrameBuffer, 0, dataFrame.Data, 0, rd);

                    try {
                        // See if the stream supports Length / Position to try and detect EOS
                        // we also want to see if we previously had an exception trying this
                        // and not try again if we did, since throwing exceptions every single
                        // read operation is wasteful
                        if (supportsPosLength && data.Position >= data.Length)
                        {
                            dataFrame.EndStream = true;
                            sentEndOfStream     = true;
                        }
                    } catch {
                        supportsPosLength = false;
                        sentEndOfStream   = false;
                    }

                    await connection.QueueFrame(dataFrame).ConfigureAwait(false);
                }
            }

            // Send an empty frame with end of stream flag
            if (!sentEndOfStream)
            {
                await connection.QueueFrame(new DataFrame(stream.StreamIdentifer) { EndStream = true }).ConfigureAwait(false);
            }

            if (!await semaphoreClose.WaitAsync(ConnectionSettings.ConnectionTimeout, cancelToken).ConfigureAwait(false))
            {
                throw new TimeoutException();
            }

            var responseData = new List <byte> ();
            var rxHeaderData = new List <byte> ();

            foreach (var f in stream.ReceivedFrames)
            {
                if (f.Type == FrameType.Headers || f.Type == FrameType.Continuation)
                {
                    // Get the header data and add it to our buffer
                    var fch = (IFrameContainsHeaders)f;
                    if (fch.HeaderBlockFragment != null && fch.HeaderBlockFragment.Length > 0)
                    {
                        rxHeaderData.AddRange(fch.HeaderBlockFragment);
                    }
                }
                else if (f.Type == FrameType.PushPromise)
                {
                    // TODO: In the future we need to implement PushPromise beyond grabbing header data
                    var fch = (IFrameContainsHeaders)f;
                    if (fch.HeaderBlockFragment != null && fch.HeaderBlockFragment.Length > 0)
                    {
                        rxHeaderData.AddRange(fch.HeaderBlockFragment);
                    }
                }
                else if (f.Type == FrameType.Data)
                {
                    responseData.AddRange((f as DataFrame).Data);
                }
                else if (f.Type == FrameType.GoAway)
                {
                    if (f is GoAwayFrame fga && fga.AdditionalDebugData != null && fga.AdditionalDebugData.Length > 0)
                    {
                        responseData.AddRange(fga.AdditionalDebugData);
                    }
                }
            }

            var responseHeaders = Util.UnpackHeaders(rxHeaderData.ToArray(),
                                                     connection.Settings.MaxHeaderListSize.HasValue ? (int)connection.Settings.MaxHeaderListSize.Value : 8192,
                                                     (int)connection.Settings.HeaderTableSize);

            var strStatus = "500";

            if (responseHeaders [":status"] != null)
            {
                strStatus = responseHeaders [":status"];
            }

            var statusCode = HttpStatusCode.OK;

            Enum.TryParse <HttpStatusCode> (strStatus, out statusCode);

            // Remove the stream from being tracked since we're done with it
            await StreamManager.Cleanup(stream.StreamIdentifer).ConfigureAwait(false);

            // Send a WINDOW_UPDATE frame to release our stream's data count
            // TODO: Eventually need to do this on the stream itself too (if it's open)
            await connection.FreeUpWindowSpace().ConfigureAwait(false);

            return(new Http2Response {
                Status = statusCode,
                Stream = stream,
                Headers = responseHeaders,
                Body = responseData.ToArray()
            });
        }