Beispiel #1
0
        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);
            }
        }
Beispiel #4
0
        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);
            }
        }
Beispiel #5
0
        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;
            }
        }