예제 #1
0
        public ValueTask <ConnectionContext> StartUnidirectionalStreamAsync()
        {
            var stream = new Http3ControlStream(this, _connection);

            // TODO put these somewhere to be read.
            return(new ValueTask <ConnectionContext>(stream.ConnectionContext));
        }
예제 #2
0
        internal async ValueTask <Http3ControlStream> GetInboundControlStream()
        {
            if (_inboundControlStream == null)
            {
                var reader = MultiplexedConnectionContext.ToClientAcceptQueue.Reader;
#if IS_FUNCTIONAL_TESTS
                while (await reader.WaitToReadAsync().DefaultTimeout())
#else
                while (await reader.WaitToReadAsync())
#endif
                {
                    while (reader.TryRead(out var stream))
                    {
                        _inboundControlStream = stream;
                        var streamId = await stream.TryReadStreamIdAsync();

                        // -1 means stream was completed.
                        Debug.Assert(streamId == 0 || streamId == -1, "StreamId sent that was non-zero, which isn't handled by tests");

                        return(_inboundControlStream);
                    }
                }
            }

            return(_inboundControlStream);
        }
예제 #3
0
        internal async ValueTask <Http3ControlStream> CreateControlStream(int id)
        {
            var stream = new Http3ControlStream(this);

            _multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext);
            await stream.WriteStreamIdAsync(id);

            return(stream);
        }
예제 #4
0
        public async ValueTask <Http3ControlStream> CreateControlStream(int id)
        {
            var stream = new Http3ControlStream(this);

            MultiplexedConnectionContext.ToServerAcceptQueue.Writer.TryWrite(stream.StreamContext);
            await stream.WriteStreamIdAsync(id);

            return(stream);
        }
예제 #5
0
        internal async ValueTask <Http3ControlStream> CreateControlStream(int id)
        {
            var stream = new Http3ControlStream(this, _connection);

            _acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext);
            await stream.WriteStreamIdAsync(id);

            return(stream);
        }
예제 #6
0
        public async ValueTask <Http3ControlStream> CreateControlStream(int?id)
        {
            var stream = new Http3ControlStream(this, StreamInitiator.Client);

            MultiplexedConnectionContext.ToServerAcceptQueue.Writer.TryWrite(stream.StreamContext);
            if (id != null)
            {
                await stream.WriteStreamIdAsync(id.GetValueOrDefault());
            }
            return(stream);
        }
예제 #7
0
        internal async ValueTask <Http3ControlStream> CreateControlStream(int?id)
        {
            var testStreamContext = new TestStreamContext(canRead: true, canWrite: false, this);

            testStreamContext.Initialize(streamId: 2);

            var stream = new Http3ControlStream(this, testStreamContext);

            _runningStreams[stream.StreamId] = stream;

            MultiplexedConnectionContext.ToServerAcceptQueue.Writer.TryWrite(stream.StreamContext);
            if (id != null)
            {
                await stream.WriteStreamIdAsync(id.GetValueOrDefault());
            }
            return(stream);
        }
예제 #8
0
        public async Task ControlStream_ServerToClient_ErrorInitializing_ConnectionError()
        {
            OnCreateServerControlStream = () =>
            {
                var controlStream = new Http3ControlStream(this, StreamInitiator.Server);

                // Make server connection error when trying to write to control stream.
                controlStream.StreamContext.Transport.Output.Complete();

                return(controlStream);
            };

            await InitializeConnectionAsync(_noopApplication);

            AssertConnectionError <Http3ConnectionErrorException>(
                expectedErrorCode: Http3ErrorCode.ClosedCriticalStream,
                expectedErrorMessage: CoreStrings.Http3ControlStreamErrorInitializingOutbound);
        }
예제 #9
0
    private async ValueTask ProcessOutboundControlStreamAsync(Http3ControlStream controlStream)
    {
        try
        {
            await controlStream.SendStreamIdAsync(id : 0);

            await controlStream.SendSettingsFrameAsync();
        }
        catch (Exception ex)
        {
            Log.Http3OutboundControlStreamError(ConnectionId, ex);

            var connectionError = new Http3ConnectionErrorException(CoreStrings.Http3ControlStreamErrorInitializingOutbound, Http3ErrorCode.ClosedCriticalStream);
            Log.Http3ConnectionError(ConnectionId, connectionError);

            // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-6.2.1
            Abort(new ConnectionAbortedException(connectionError.Message, connectionError), connectionError.ErrorCode);
        }
    }
예제 #10
0
        internal async ValueTask <Http3ControlStream> GetInboundControlStream()
        {
            if (_inboundControlStream == null)
            {
                var reader = MultiplexedConnectionContext.ToClientAcceptQueue.Reader;
                while (await reader.WaitToReadAsync())
                {
                    while (reader.TryRead(out var stream))
                    {
                        _inboundControlStream = stream;
                        var streamId = await stream.TryReadStreamIdAsync();

                        Debug.Assert(streamId == 0, "StreamId sent that was non-zero, which isn't handled by tests");
                        return(_inboundControlStream);
                    }
                }
            }

            return(null);
        }
예제 #11
0
    public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) where TContext : notnull
    {
        // An endpoint MAY avoid creating an encoder stream if it's not going to
        // be used(for example if its encoder doesn't wish to use the dynamic
        // table, or if the maximum size of the dynamic table permitted by the
        // peer is zero).

        // An endpoint MAY avoid creating a decoder stream if its decoder sets
        // the maximum capacity of the dynamic table to zero.

        // Don't create Encoder and Decoder as they aren't used now.

        Exception?         error = null;
        Http3ControlStream?outboundControlStream     = null;
        ValueTask          outboundControlStreamTask = default;
        bool clientAbort = false;

        try
        {
            outboundControlStream = await CreateNewUnidirectionalStreamAsync(application);

            lock (_sync)
            {
                OutboundControlStream = outboundControlStream;
            }

            // Don't delay on waiting to send outbound control stream settings.
            outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream);

            while (_stoppedAcceptingStreams == 0)
            {
                var streamContext = await _multiplexedContext.AcceptAsync(_acceptStreamsCts.Token);

                try
                {
                    // Return null on server close or cancellation.
                    if (streamContext == null)
                    {
                        if (_acceptStreamsCts.Token.IsCancellationRequested)
                        {
                            _acceptStreamsCts = new CancellationTokenSource();
                        }

                        // There is no stream so continue to skip to UpdateConnectionState in finally.
                        // UpdateConnectionState is responsible for updating connection to
                        // stop accepting streams and break out of accept loop.
                        continue;
                    }

                    var streamDirectionFeature = streamContext.Features.Get <IStreamDirectionFeature>();
                    var streamIdFeature        = streamContext.Features.Get <IStreamIdFeature>();

                    Debug.Assert(streamDirectionFeature != null);
                    Debug.Assert(streamIdFeature != null);

                    if (!streamDirectionFeature.CanWrite)
                    {
                        // Unidirectional stream
                        var stream = new Http3ControlStream <TContext>(application, CreateHttpStreamContext(streamContext));
                        _streamLifetimeHandler.OnStreamCreated(stream);

                        ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false);
                    }
                    else
                    {
                        // Request stream

                        // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-5.2-2
                        if (_gracefulCloseStarted)
                        {
                            // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1.2-3
                            streamContext.Features.Get <IProtocolErrorCodeFeature>() !.Error = (long)Http3ErrorCode.RequestRejected;
                            streamContext.Abort(new ConnectionAbortedException("HTTP/3 connection is closing and no longer accepts new requests."));
                            await streamContext.DisposeAsync();

                            continue;
                        }

                        // Request stream IDs are tracked.
                        UpdateHighestOpenedRequestStreamId(streamIdFeature.StreamId);

                        var persistentStateFeature = streamContext.Features.Get <IPersistentStateFeature>();
                        Debug.Assert(persistentStateFeature != null, $"Required {nameof(IPersistentStateFeature)} not on stream context.");

                        Http3Stream <TContext> stream;

                        // Check whether there is an existing HTTP/3 stream on the transport stream.
                        // A stream will only be cached if the transport stream itself is reused.
                        if (!persistentStateFeature.State.TryGetValue(StreamPersistentStateKey, out var s))
                        {
                            stream = new Http3Stream <TContext>(application, CreateHttpStreamContext(streamContext));
                            persistentStateFeature.State.Add(StreamPersistentStateKey, stream);
                        }
                        else
                        {
                            stream = (Http3Stream <TContext>)s !;
                            stream.InitializeWithExistingContext(streamContext.Transport);
                        }

                        _streamLifetimeHandler.OnStreamCreated(stream);

                        KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3);
                        ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false);
                    }
                }
                finally
                {
                    UpdateConnectionState();
                }
            }
        }
        catch (ConnectionResetException ex)
        {
            lock (_streams)
            {
                if (_activeRequestCount > 0)
                {
                    Log.RequestProcessingError(_context.ConnectionId, ex);
                }
            }
            error       = ex;
            clientAbort = true;
        }
        catch (IOException ex)
        {
            Log.RequestProcessingError(_context.ConnectionId, ex);
            error = ex;
        }
        catch (ConnectionAbortedException ex)
        {
            Log.RequestProcessingError(_context.ConnectionId, ex);
            error = ex;
        }
        catch (Http3ConnectionErrorException ex)
        {
            Log.Http3ConnectionError(_context.ConnectionId, ex);
            error = ex;
        }
        catch (Exception ex)
        {
            error = ex;
        }
        finally
        {
            try
            {
                // Don't try to send GOAWAY if the client has already closed the connection.
                if (!clientAbort)
                {
                    if (TryStopAcceptingStreams() || _gracefulCloseStarted)
                    {
                        await SendGoAwayAsync(GetCurrentGoAwayStreamId());
                    }
                }

                // Abort active request streams.
                lock (_streams)
                {
                    foreach (var stream in _streams.Values)
                    {
                        stream.Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error);
                    }
                }

                if (outboundControlStream != null)
                {
                    // Don't gracefully close the outbound control stream. If the peer detects
                    // the control stream closes it will close with a procotol error.
                    // Instead, allow control stream to be automatically aborted when the
                    // connection is aborted.
                    await outboundControlStreamTask;
                }

                // Complete
                Abort(CreateConnectionAbortError(error, clientAbort), (Http3ErrorCode)_errorCodeFeature.Error);

                // Wait for active requests to complete.
                while (_activeRequestCount > 0)
                {
                    await _streamCompletionAwaitable;
                }

                _context.TimeoutControl.CancelTimeout();
            }
            catch
            {
                Abort(CreateConnectionAbortError(error, clientAbort), Http3ErrorCode.InternalError);
                throw;
            }
            finally
            {
                // Connection can close without processing any request streams.
                var streamId = _highestOpenedRequestStreamId != DefaultHighestOpenedRequestStreamId
                    ? _highestOpenedRequestStreamId
                    : (long?)null;

                Log.Http3ConnectionClosed(_context.ConnectionId, streamId);
            }
        }
    }