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); } }
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); } } }
public bool OnInboundEncoderStream(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3ControlStream stream) { return(_inner.OnInboundEncoderStream(stream)); }
public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) { var streamListenerFeature = Context.ConnectionFeatures.Get <IQuicStreamListenerFeature>(); // Start other three unidirectional streams here. var settingsStream = CreateSettingsStream(application); var encoderStream = CreateEncoderStream(application); var decoderStream = CreateDecoderStream(application); try { while (true) { var connectionContext = await streamListenerFeature.AcceptAsync(); if (connectionContext == null) { break; } //if (_haveSentGoAway) //{ // // error here. //} var httpConnectionContext = new HttpConnectionContext { ConnectionId = connectionContext.ConnectionId, ConnectionContext = connectionContext, Protocols = Context.Protocols, ServiceContext = Context.ServiceContext, ConnectionFeatures = connectionContext.Features, MemoryPool = Context.MemoryPool, Transport = connectionContext.Transport, TimeoutControl = Context.TimeoutControl, LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint, RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint }; var streamFeature = httpConnectionContext.ConnectionFeatures.Get <IQuicStreamFeature>(); var streamId = streamFeature.StreamId; HighestStreamId = streamId; if (streamFeature.IsUnidirectional) { var stream = new Http3ControlStream <TContext>(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { var http3Stream = new Http3Stream <TContext>(application, this, httpConnectionContext); var stream = http3Stream; _streams[streamId] = http3Stream; ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } } finally { await settingsStream; await encoderStream; await decoderStream; foreach (var stream in _streams.Values) { stream.Abort(new ConnectionAbortedException("")); } } }
internal async Task InnerProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) { // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); var encoderTask = CreateEncoderStream(application); var decoderTask = CreateDecoderStream(application); try { while (true) { var streamContext = await _multiplexedContext.AcceptAsync(); if (streamContext == null || _haveSentGoAway) { break; } var quicStreamFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(quicStreamFeature != null); var httpConnectionContext = new Http3StreamContext { ConnectionId = streamContext.ConnectionId, StreamContext = streamContext, // TODO connection context is null here. Should we set it to anything? ServiceContext = _context.ServiceContext, ConnectionFeatures = streamContext.Features, MemoryPool = _context.MemoryPool, Transport = streamContext.Transport, TimeoutControl = _context.TimeoutControl, LocalEndPoint = streamContext.LocalEndPoint as IPEndPoint, RemoteEndPoint = streamContext.RemoteEndPoint as IPEndPoint }; if (!quicStreamFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { // Keep track of highest stream id seen for GOAWAY var streamId = streamIdFeature.StreamId; HighestStreamId = streamId; var http3Stream = new Http3Stream <TContext>(application, this, httpConnectionContext); var stream = http3Stream; lock (_streams) { _streams[streamId] = http3Stream; } KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } } finally { // Abort all streams as connection has shutdown. lock (_streams) { foreach (var stream in _streams.Values) { stream.Abort(new ConnectionAbortedException("Connection is shutting down.")); } } ControlStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); EncoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); DecoderStream?.Abort(new ConnectionAbortedException("Connection is shutting down.")); await controlTask; await encoderTask; await decoderTask; } }
internal async Task InnerProcessStreamsAsync <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; // TODO should we await the control stream task? var controlTask = CreateControlStream(application); _timeoutControl.SetTimeout(Limits.KeepAliveTimeout.Ticks, TimeoutReason.KeepAlive); try { while (_isClosed == 0) { // TODO implement way to unblock loop for one call to accept async to update state. // Use cts for now, update to custom awaitable or different solution in the future. var streamContext = await _multiplexedContext.AcceptAsync(); try { if (streamContext == null) { break; } var quicStreamFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(quicStreamFeature != null); Debug.Assert(streamIdFeature != null); var httpConnectionContext = new Http3StreamContext( streamContext.ConnectionId, protocols: default, connectionContext: null !, // TODO connection context is null here. Should we set it to anything? _context.ServiceContext, streamContext.Features, _context.MemoryPool, streamContext.LocalEndPoint as IPEndPoint, streamContext.RemoteEndPoint as IPEndPoint, streamContext.Transport, streamContext, _serverSettings); httpConnectionContext.TimeoutControl = _context.TimeoutControl; if (!quicStreamFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { var streamId = streamIdFeature.StreamId; HighestStreamId = streamId; var http3Stream = new Http3Stream <TContext>(application, this, httpConnectionContext); var stream = http3Stream; lock (_streams) { _activeRequestCount++; _streams[streamId] = http3Stream; } KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } }
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; ValueTask outboundControlStreamTask = default; try { var outboundControlStream = await CreateNewUnidirectionalStreamAsync(application); lock (_sync) { OutboundControlStream = outboundControlStream; } // Don't delay on waiting to send outbound control stream settings. outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream); while (_isClosed == 0) { // Don't pass a cancellation token to AcceptAsync. // AcceptAsync will return null if the connection is gracefully shutting down or aborted. var streamContext = await _multiplexedContext.AcceptAsync(); try { if (streamContext == null) { break; } 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 { var persistentStateFeature = streamContext.Features.Get <IPersistentStateFeature>(); Debug.Assert(persistentStateFeature != null, $"Required {nameof(IPersistentStateFeature)} not on stream context."); // Request stream UpdateHighestStreamId(streamIdFeature.StreamId); 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; } 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 { var connectionError = error as ConnectionAbortedException ?? new ConnectionAbortedException(CoreStrings.Http3ConnectionFaulted, error !); try { if (TryClose()) { // This throws when connection is shut down. // TODO how to make it so we can distinguish between Abort from server vs client? await SendGoAway(GetHighestStreamId()); } foreach (var stream in _streams.Values) { stream.Abort(connectionError, (Http3ErrorCode)_errorCodeFeature.Error); } lock (_sync) { OutboundControlStream?.Abort(connectionError, (Http3ErrorCode)_errorCodeFeature.Error); } while (_activeRequestCount > 0) { await _streamCompletionAwaitable; } await outboundControlStreamTask; _context.TimeoutControl.CancelTimeout(); _context.TimeoutControl.StartDrainTimeout(Limits.MinResponseDataRate, Limits.MaxResponseBufferSize); } catch { Abort(connectionError, Http3ErrorCode.InternalError); throw; } } }
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; ValueTask outboundControlStreamTask = default; try { var outboundControlStream = await CreateNewUnidirectionalStreamAsync(application); lock (_sync) { OutboundControlStream = outboundControlStream; } // Don't delay on waiting to send outbound control stream settings. outboundControlStreamTask = ProcessOutboundControlStreamAsync(outboundControlStream); while (_isClosed == 0) { // Don't pass a cancellation token to AcceptAsync. // AcceptAsync will return null if the connection is gracefully shutting down or aborted. var streamContext = await _multiplexedContext.AcceptAsync(); try { if (streamContext == null) { break; } var streamDirectionFeature = streamContext.Features.Get <IStreamDirectionFeature>(); var streamIdFeature = streamContext.Features.Get <IStreamIdFeature>(); Debug.Assert(streamDirectionFeature != null); Debug.Assert(streamIdFeature != null); var httpConnectionContext = new Http3StreamContext( streamContext.ConnectionId, protocols: default, connectionContext: null !, // TODO connection context is null here. Should we set it to anything? _context.ServiceContext, streamContext.Features, _context.MemoryPool, streamContext.LocalEndPoint as IPEndPoint, streamContext.RemoteEndPoint as IPEndPoint, streamContext.Transport, _streamLifetimeHandler, streamContext, _clientSettings, _serverSettings); httpConnectionContext.TimeoutControl = _context.TimeoutControl; if (!streamDirectionFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, httpConnectionContext); _streamLifetimeHandler.OnStreamCreated(stream); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { // Request stream UpdateHighestStreamId(streamIdFeature.StreamId); var stream = new Http3Stream <TContext>(application, httpConnectionContext); _streamLifetimeHandler.OnStreamCreated(stream); KestrelEventSource.Log.RequestQueuedStart(stream, AspNetCore.Http.HttpProtocol.Http3); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } }
public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) { var streamListenerFeature = Context.ConnectionFeatures.Get <IQuicStreamListenerFeature>(); // Start other three unidirectional streams here. var controlTask = CreateControlStream(application); var encoderTask = CreateEncoderStream(application); var decoderTask = CreateDecoderStream(application); try { while (true) { var connectionContext = await streamListenerFeature.AcceptAsync(); if (connectionContext == null || _haveSentGoAway) { break; } var httpConnectionContext = new HttpConnectionContext { ConnectionId = connectionContext.ConnectionId, ConnectionContext = connectionContext, Protocols = Context.Protocols, ServiceContext = Context.ServiceContext, ConnectionFeatures = connectionContext.Features, MemoryPool = Context.MemoryPool, Transport = connectionContext.Transport, TimeoutControl = Context.TimeoutControl, LocalEndPoint = connectionContext.LocalEndPoint as IPEndPoint, RemoteEndPoint = connectionContext.RemoteEndPoint as IPEndPoint }; var streamFeature = httpConnectionContext.ConnectionFeatures.Get <IQuicStreamFeature>(); if (!streamFeature.CanWrite) { // Unidirectional stream var stream = new Http3ControlStream <TContext>(application, this, httpConnectionContext); ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } else { // Keep track of highest stream id seen for GOAWAY var streamId = streamFeature.StreamId; HighestStreamId = streamId; var http3Stream = new Http3Stream <TContext>(application, this, httpConnectionContext); var stream = http3Stream; _streams[streamId] = http3Stream; ThreadPool.UnsafeQueueUserWorkItem(stream, preferLocal: false); } } } finally { // Abort all streams as connection has shutdown. foreach (var stream in _streams.Values) { stream.Abort(new ConnectionAbortedException("Connection is shutting down.")); } ControlStream.Abort(new ConnectionAbortedException("Connection is shutting down.")); EncoderStream.Abort(new ConnectionAbortedException("Connection is shutting down.")); DecoderStream.Abort(new ConnectionAbortedException("Connection is shutting down.")); await controlTask; await encoderTask; await decoderTask; } }