private async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> httpApplication) { try { KestrelEventSource.Log.ConnectionStart(this); AdaptedPipeline adaptedPipeline = null; var adaptedPipelineTask = Task.CompletedTask; var transport = _context.Transport; var application = _context.Application; if (_context.ConnectionAdapters.Count > 0) { adaptedPipeline = new AdaptedPipeline(transport, application, new Pipe(AdaptedInputPipeOptions), new Pipe(AdaptedOutputPipeOptions)); transport = adaptedPipeline; } // _http1Connection must be initialized before adding the connection to the connection manager CreateHttp1Connection(httpApplication, transport, application); // _http2Connection must be initialized before yielding control to the transport thread, // to prevent a race condition where _http2Connection.Abort() is called just as // _http2Connection is about to be initialized. CreateHttp2Connection(httpApplication, transport, application); // Do this before the first await so we don't yield control to the transport until we've // added the connection to the connection manager _context.ServiceContext.ConnectionManager.AddConnection(_context.HttpConnectionId, this); _lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks; _http1Connection.ConnectionFeatures.Set <IConnectionTimeoutFeature>(this); _http2Connection.ConnectionFeatures.Set <IConnectionTimeoutFeature>(this); if (adaptedPipeline != null) { // Stream can be null here and run async will close the connection in that case var stream = await ApplyConnectionAdaptersAsync(); adaptedPipelineTask = adaptedPipeline.RunAsync(stream); } var protocol = SelectProtocol(); if (protocol == HttpProtocols.None) { Abort(ex: null); } // One of these has to run even if no protocol was selected so the abort propagates and everything completes properly if (protocol == HttpProtocols.Http2 && Interlocked.CompareExchange(ref _http2ConnectionState, Http2ConnectionStarted, Http2ConnectionNotStarted) == Http2ConnectionNotStarted) { await _http2Connection.ProcessAsync(httpApplication); } else { await _http1Connection.ProcessRequestsAsync(); } await adaptedPipelineTask; await _socketClosedTcs.Task; } catch (Exception ex) { Log.LogError(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}."); } finally { _context.ServiceContext.ConnectionManager.RemoveConnection(_context.HttpConnectionId); DisposeAdaptedConnections(); if (_http1Connection.IsUpgraded) { _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); } KestrelEventSource.Log.ConnectionStop(this); } }
public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> httpApplication) { try { AdaptedPipeline adaptedPipeline = null; var adaptedPipelineTask = Task.CompletedTask; // _adaptedTransport must be set prior to wiring up callbacks // to allow the connection to be aborted prior to protocol selection. _adaptedTransport = _context.Transport; if (_context.ConnectionAdapters.Count > 0) { adaptedPipeline = new AdaptedPipeline(_adaptedTransport, new Pipe(AdaptedInputPipeOptions), new Pipe(AdaptedOutputPipeOptions), Log); _adaptedTransport = adaptedPipeline; } // This feature should never be null in Kestrel var connectionHeartbeatFeature = _context.ConnectionFeatures.Get <IConnectionHeartbeatFeature>(); Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!"); connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this); var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get <IConnectionLifetimeNotificationFeature>(); Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!"); using (connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state).StopProcessingNextRequest(), this)) { // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs. _timeoutControl.Initialize(_systemClock.UtcNowTicks); _context.ConnectionFeatures.Set <IConnectionTimeoutFeature>(_timeoutControl); if (adaptedPipeline != null) { // Stream can be null here and run async will close the connection in that case var stream = await ApplyConnectionAdaptersAsync(); adaptedPipelineTask = adaptedPipeline.RunAsync(stream); } IRequestProcessor requestProcessor = null; lock (_protocolSelectionLock) { // Ensure that the connection hasn't already been stopped. if (_protocolSelectionState == ProtocolSelectionState.Initializing) { var derivedContext = CreateDerivedContext(_adaptedTransport); switch (SelectProtocol()) { case HttpProtocols.Http1: // _http1Connection must be initialized before adding the connection to the connection manager requestProcessor = _http1Connection = new Http1Connection(derivedContext); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.Http2: // _http2Connection must be initialized before yielding control to the transport thread, // to prevent a race condition where _http2Connection.Abort() is called just as // _http2Connection is about to be initialized. requestProcessor = new Http2Connection(derivedContext); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.None: // An error was already logged in SelectProtocol(), but we should close the connection. Abort(ex: null); break; default: // SelectProtocol() only returns Http1, Http2 or None. throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None."); } _requestProcessor = requestProcessor; } } _context.Transport.Input.OnWriterCompleted( (_, state) => ((HttpConnection)state).OnInputOrOutputCompleted(), this); _context.Transport.Output.OnReaderCompleted( (_, state) => ((HttpConnection)state).OnInputOrOutputCompleted(), this); if (requestProcessor != null) { await requestProcessor.ProcessRequestsAsync(httpApplication); } await adaptedPipelineTask; } } catch (Exception ex) { Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}."); } finally { DisposeAdaptedConnections(); if (_http1Connection?.IsUpgraded == true) { _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); } } }
private async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> application) { try { Log.ConnectionStart(ConnectionId); KestrelEventSource.Log.ConnectionStart(this, _context.ConnectionInformation); AdaptedPipeline adaptedPipeline = null; var adaptedPipelineTask = Task.CompletedTask; var input = _context.Input.Reader; var output = _context.Output; if (_context.ConnectionAdapters.Count > 0) { adaptedPipeline = new AdaptedPipeline(input, output, PipeFactory.Create(AdaptedInputPipeOptions), PipeFactory.Create(AdaptedOutputPipeOptions), Log); input = adaptedPipeline.Input.Reader; output = adaptedPipeline.Output; } // _frame must be initialized before adding the connection to the connection manager CreateFrame(application, input, output); // Do this before the first await so we don't yield control to the transport until we've // added the connection to the connection manager _context.ServiceContext.ConnectionManager.AddConnection(_context.FrameConnectionId, this); _lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks; if (adaptedPipeline != null) { // Stream can be null here and run async will close the connection in that case var stream = await ApplyConnectionAdaptersAsync(); adaptedPipelineTask = adaptedPipeline.RunAsync(stream); } await _frame.ProcessRequestsAsync(); await adaptedPipelineTask; await _socketClosedTcs.Task; } catch (Exception ex) { Log.LogError(0, ex, $"Unexpected exception in {nameof(FrameConnection)}.{nameof(ProcessRequestsAsync)}."); } finally { _context.ServiceContext.ConnectionManager.RemoveConnection(_context.FrameConnectionId); DisposeAdaptedConnections(); if (_frame.WasUpgraded) { _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); } else { _context.ServiceContext.ConnectionManager.NormalConnectionCount.ReleaseOne(); } Log.ConnectionStop(ConnectionId); KestrelEventSource.Log.ConnectionStop(this); } }
private async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> httpApplication) { try { KestrelEventSource.Log.ConnectionStart(this); AdaptedPipeline adaptedPipeline = null; var adaptedPipelineTask = Task.CompletedTask; // _adaptedTransport must be set prior to adding the connection to the manager in order // to allow the connection to be aported prior to protocol selection. _adaptedTransport = _context.Transport; var application = _context.Application; if (_context.ConnectionAdapters.Count > 0) { adaptedPipeline = new AdaptedPipeline(_adaptedTransport, application, new Pipe(AdaptedInputPipeOptions), new Pipe(AdaptedOutputPipeOptions)); _adaptedTransport = adaptedPipeline; } // Do this before the first await so we don't yield control to the transport until we've // added the connection to the connection manager _context.ServiceContext.ConnectionManager.AddConnection(_context.HttpConnectionId, this); _lastTimestamp = _context.ServiceContext.SystemClock.UtcNow.Ticks; _context.ConnectionFeatures.Set <IConnectionTimeoutFeature>(this); if (adaptedPipeline != null) { // Stream can be null here and run async will close the connection in that case var stream = await ApplyConnectionAdaptersAsync(); adaptedPipelineTask = adaptedPipeline.RunAsync(stream); } IRequestProcessor requestProcessor = null; lock (_protocolSelectionLock) { // Ensure that the connection hasn't already been stopped. if (_protocolSelectionState == ProtocolSelectionState.Initializing) { switch (SelectProtocol()) { case HttpProtocols.Http1: // _http1Connection must be initialized before adding the connection to the connection manager requestProcessor = _http1Connection = CreateHttp1Connection(_adaptedTransport, application); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.Http2: // _http2Connection must be initialized before yielding control to the transport thread, // to prevent a race condition where _http2Connection.Abort() is called just as // _http2Connection is about to be initialized. requestProcessor = CreateHttp2Connection(_adaptedTransport, application); _protocolSelectionState = ProtocolSelectionState.Selected; break; case HttpProtocols.None: // An error was already logged in SelectProtocol(), but we should close the connection. Abort(ex: null); break; default: // SelectProtocol() only returns Http1, Http2 or None. throw new NotSupportedException($"{nameof(SelectProtocol)} returned something other than Http1, Http2 or None."); } _requestProcessor = requestProcessor; } } if (requestProcessor != null) { await requestProcessor.ProcessRequestsAsync(httpApplication); } await adaptedPipelineTask; await _socketClosedTcs.Task; } catch (Exception ex) { Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}."); } finally { _context.ServiceContext.ConnectionManager.RemoveConnection(_context.HttpConnectionId); DisposeAdaptedConnections(); if (_http1Connection?.IsUpgraded == true) { _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne(); } KestrelEventSource.Log.ConnectionStop(this); } }
private async Task InnerOnConnectionAsync(ConnectionContext context) { SslStream sslStream; bool certificateRequired; var feature = new Core.Internal.TlsConnectionFeature(); context.Features.Set <ITlsConnectionFeature>(feature); context.Features.Set <ITlsHandshakeFeature>(feature); // TODO: Handle the cases where this can be null var memoryPoolFeature = context.Features.Get <IMemoryPoolFeature>(); var inputPipeOptions = new PipeOptions ( pool: memoryPoolFeature.MemoryPool, readerScheduler: _options.Scheduler, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: _options.MaxInputBufferSize ?? 0, resumeWriterThreshold: _options.MaxInputBufferSize / 2 ?? 0, useSynchronizationContext: false, minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() ); var outputPipeOptions = new PipeOptions ( pool: memoryPoolFeature.MemoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, pauseWriterThreshold: _options.MaxOutputBufferSize ?? 0, resumeWriterThreshold: _options.MaxOutputBufferSize / 2 ?? 0, useSynchronizationContext: false, minimumSegmentSize: memoryPoolFeature.MemoryPool.GetMinimumSegmentSize() ); // TODO: eventually make SslDuplexStream : Stream, IDuplexPipe to avoid RawStream allocation and pipe allocations var adaptedPipeline = new AdaptedPipeline(context.Transport, new Pipe(inputPipeOptions), new Pipe(outputPipeOptions), _logger, memoryPoolFeature.MemoryPool.GetMinimumAllocSize()); var transportStream = adaptedPipeline.TransportStream; if (_options.ClientCertificateMode == ClientCertificateMode.NoCertificate) { sslStream = new SslStream(transportStream); certificateRequired = false; } else { sslStream = new SslStream(transportStream, leaveInnerStreamOpen: false, userCertificateValidationCallback: (sender, certificate, chain, sslPolicyErrors) => { if (certificate == null) { return(_options.ClientCertificateMode != ClientCertificateMode.RequireCertificate); } if (_options.ClientCertificateValidation == null) { if (sslPolicyErrors != SslPolicyErrors.None) { return(false); } } var certificate2 = ConvertToX509Certificate2(certificate); if (certificate2 == null) { return(false); } if (_options.ClientCertificateValidation != null) { if (!_options.ClientCertificateValidation(certificate2, chain, sslPolicyErrors)) { return(false); } } return(true); }); certificateRequired = true; } using (var cancellationTokeSource = new CancellationTokenSource(_options.HandshakeTimeout)) using (cancellationTokeSource.Token.UnsafeRegister(state => ((ConnectionContext)state).Abort(), context)) { try { // Adapt to the SslStream signature ServerCertificateSelectionCallback selector = null; if (_serverCertificateSelector != null) { selector = (sender, name) => { context.Features.Set(sslStream); var cert = _serverCertificateSelector(context, name); if (cert != null) { EnsureCertificateIsAllowedForServerAuth(cert); } return(cert); }; } var sslOptions = new SslServerAuthenticationOptions { ServerCertificate = _serverCertificate, ServerCertificateSelectionCallback = selector, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, ApplicationProtocols = new List <SslApplicationProtocol>() }; // This is order sensitive if ((_options.HttpProtocols & HttpProtocols.Http2) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http2); // https://tools.ietf.org/html/rfc7540#section-9.2.1 sslOptions.AllowRenegotiation = false; } if ((_options.HttpProtocols & HttpProtocols.Http1) != 0) { sslOptions.ApplicationProtocols.Add(SslApplicationProtocol.Http11); } _options.OnAuthenticate?.Invoke(context, sslOptions); await sslStream.AuthenticateAsServerAsync(sslOptions, CancellationToken.None); } catch (OperationCanceledException) { _logger?.LogDebug(2, CoreStrings.AuthenticationTimedOut); sslStream.Dispose(); return; } catch (Exception ex) when(ex is IOException || ex is AuthenticationException) { _logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed); sslStream.Dispose(); return; } } feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol; context.Features.Set <ITlsApplicationProtocolFeature>(feature); feature.ClientCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; feature.Protocol = sslStream.SslProtocol; var original = context.Transport; try { context.Transport = adaptedPipeline; using (sslStream) { try { adaptedPipeline.RunAsync(sslStream); await _next(context); } finally { await adaptedPipeline.CompleteAsync(); } } } finally { // Restore the original so that it gets closed appropriately context.Transport = original; } }