public ValueTask <ConnectionContext> StartUnidirectionalStreamAsync() { var stream = new Http3ControlStream(this, _connection); // TODO put these somewhere to be read. return(new ValueTask <ConnectionContext>(stream.ConnectionContext)); }
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); }
internal async ValueTask <Http3ControlStream> CreateControlStream(int id) { var stream = new Http3ControlStream(this); _multiplexedContext.AcceptQueue.Writer.TryWrite(stream.StreamContext); await stream.WriteStreamIdAsync(id); return(stream); }
public async ValueTask <Http3ControlStream> CreateControlStream(int id) { var stream = new Http3ControlStream(this); MultiplexedConnectionContext.ToServerAcceptQueue.Writer.TryWrite(stream.StreamContext); await stream.WriteStreamIdAsync(id); return(stream); }
internal async ValueTask <Http3ControlStream> CreateControlStream(int id) { var stream = new Http3ControlStream(this, _connection); _acceptConnectionQueue.Writer.TryWrite(stream.ConnectionContext); await stream.WriteStreamIdAsync(id); return(stream); }
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); }
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); }
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); }
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); } }
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); }
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); } } }