Beispiel #1
0
 // For testing only
 internal void Initialize(IRequestProcessor requestProcessor)
 {
     _requestProcessor       = requestProcessor;
     _http1Connection        = requestProcessor as Http1Connection;
     _protocolSelectionState = ProtocolSelectionState.Selected;
 }
Beispiel #2
0
        public async Task ProcessRequestsAsync <TContext>(IHttpApplication <TContext> httpApplication)
        {
            try {
                // Ensure TimeoutControl._lastTimestamp is initialized before anything that could set timeouts runs.

                _timeoutControl.Initialize(_systemClock.UtcNowTicks);

                IRequestProcessor requestProcessor = null;

                switch (SelectProtocol())
                {
                case HttpProtocols.Http1:
                    // _http1Connection must be initialized before adding the connection to the connection manager
                    requestProcessor        = _http1Connection = new Http1Connection <TContext>(_context);
                    _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(_context);
                    _protocolSelectionState = ProtocolSelectionState.Selected;
                    break;

                case HttpProtocols.None:
                    // An error was already logged in SelectProtocol(), but we should close the connection.
                    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)
                {
                    var connectionHeartbeatFeature            = _context.ConnectionFeatures.Get <IConnectionHeartbeatFeature>();
                    var connectionLifetimeNotificationFeature = _context.ConnectionFeatures.Get <IConnectionLifetimeNotificationFeature>();

                    // These features should never be null in Kestrel itself, if this middleware is ever refactored to run outside of kestrel,
                    // we'll need to handle these missing.
                    Debug.Assert(connectionHeartbeatFeature != null, nameof(IConnectionHeartbeatFeature) + " is missing!");
                    Debug.Assert(connectionLifetimeNotificationFeature != null, nameof(IConnectionLifetimeNotificationFeature) + " is missing!");

                    // Register the various callbacks once we're going to start processing requests

                    // The heart beat for various timeouts
                    connectionHeartbeatFeature?.OnHeartbeat(state => ((HttpConnection)state).Tick(), this);

                    // Register for graceful shutdown of the server
                    using var shutdownRegistration = connectionLifetimeNotificationFeature?.ConnectionClosedRequested.Register(state => ((HttpConnection)state).StopProcessingNextRequest(), this);

                    // Register for connection close
                    using var closedRegistration = _context.ConnectionContext.ConnectionClosed.Register(state => ((HttpConnection)state).OnConnectionClosed(), this);

                    await requestProcessor.ProcessRequestsAsync(httpApplication);
                }
            } catch (Exception ex) {
                Log.LogCritical(0, ex, $"Unexpected exception in {nameof(HttpConnection)}.{nameof(ProcessRequestsAsync)}.");
            } finally {
                if (_http1Connection?.IsUpgraded == true)
                {
                    _context.ServiceContext.ConnectionManager.UpgradedConnectionCount.ReleaseOne();
                }
            }
        }
        private async Task WriteOutputAsync(LibuvOutputConsumer consumer, IPipeReader outputReader, Http1Connection http1Connection)
        {
            // This WriteOutputAsync() calling code is equivalent to that in LibuvConnection.
            try
            {
                // Ensure that outputReader.Complete() runs on the LibuvThread.
                // Without ConfigureAwait(false), xunit will dispatch.
                await consumer.WriteOutputAsync().ConfigureAwait(false);

                http1Connection.Abort(error: null);
                outputReader.Complete();
            }
            catch (UvException ex)
            {
                http1Connection.Abort(ex);
                outputReader.Complete(ex);
            }
        }
        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();
                }
            }
        }
    public void AuthorityForms(string rawTarget, string path, string query)
    {
        Http1Connection.Reset();
        var ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
        var reader = new SequenceReader <byte>(ros);

        Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));

        var prevRequestUrl = Http1Connection.RawTarget;
        var prevPath       = Http1Connection.Path;
        var prevQuery      = Http1Connection.QueryString;

        // Identical requests keep same materialized string values
        for (var i = 0; i < 5; i++)
        {
            Http1Connection.Reset();
            // RawTarget, Path, QueryString are null after reset
            Assert.Null(Http1Connection.RawTarget);
            Assert.Null(Http1Connection.Path);
            Assert.Null(Http1Connection.QueryString);

            ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
            reader = new SequenceReader <byte>(ros);
            Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));

            // Equal the inputs.
            Assert.Equal(rawTarget, Http1Connection.RawTarget);
            Assert.Equal(path, Http1Connection.Path);
            Assert.Equal(query, Http1Connection.QueryString);

            // RawTarget not the same as the input.
            Assert.NotSame(rawTarget, Http1Connection.RawTarget);
            // Others same as the inputs, empty strings.
            Assert.Same(path, Http1Connection.Path);
            Assert.Same(query, Http1Connection.QueryString);

            // However, materalized strings are reused if generated for previous requests.

            Assert.Same(prevRequestUrl, Http1Connection.RawTarget);
            Assert.Same(prevPath, Http1Connection.Path);
            Assert.Same(prevQuery, Http1Connection.QueryString);

            prevRequestUrl = Http1Connection.RawTarget;
            prevPath       = Http1Connection.Path;
            prevQuery      = Http1Connection.QueryString;
        }

        // Different Authority Form request changes values
        rawTarget = "example.org:2345";
        path      = "";
        query     = "";

        Http1Connection.Reset();
        ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"CONNECT {rawTarget} HTTP/1.1\r\n"));
        reader = new SequenceReader <byte>(ros);
        Parser.ParseRequestLine(ParsingHandler, ref reader);

        // Equal the inputs.
        Assert.Equal(rawTarget, Http1Connection.RawTarget);
        Assert.Equal(path, Http1Connection.Path);
        Assert.Equal(query, Http1Connection.QueryString);

        // But not the same as the inputs.
        Assert.NotSame(rawTarget, Http1Connection.RawTarget);
        // Empty interned strings
        Assert.Same(path, Http1Connection.Path);
        Assert.Same(query, Http1Connection.QueryString);

        // Not equal previous request.
        Assert.NotEqual(prevRequestUrl, Http1Connection.RawTarget);

        DifferentFormsWorkTogether();
    }
 public Http1ContentLengthMessageBody(Http1Connection context, long contentLength, bool keepAlive)
     : base(context, keepAlive)
 {
     _contentLength         = contentLength;
     _unexaminedInputLength = contentLength;
 }
    public void AsteriskForms()
    {
        var rawTarget = "*";
        var path      = string.Empty;
        var query     = string.Empty;

        Http1Connection.Reset();
        var ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
        var reader = new SequenceReader <byte>(ros);

        Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));

        var prevRequestUrl = Http1Connection.RawTarget;
        var prevPath       = Http1Connection.Path;
        var prevQuery      = Http1Connection.QueryString;

        // Identical requests keep same materialized string values
        for (var i = 0; i < 5; i++)
        {
            Http1Connection.Reset();
            // RawTarget, Path, QueryString are null after reset
            Assert.Null(Http1Connection.RawTarget);
            Assert.Null(Http1Connection.Path);
            Assert.Null(Http1Connection.QueryString);

            ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"OPTIONS {rawTarget} HTTP/1.1\r\n"));
            reader = new SequenceReader <byte>(ros);
            Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));

            // Equal the inputs.
            Assert.Equal(rawTarget, Http1Connection.RawTarget);
            Assert.Equal(path, Http1Connection.Path);
            Assert.Equal(query, Http1Connection.QueryString);

            // Also same as the inputs (interned strings).

            Assert.Same(rawTarget, Http1Connection.RawTarget);
            Assert.Same(path, Http1Connection.Path);
            Assert.Same(query, Http1Connection.QueryString);

            // Materalized strings are reused if generated for previous requests.

            Assert.Same(prevRequestUrl, Http1Connection.RawTarget);
            Assert.Same(prevPath, Http1Connection.Path);
            Assert.Same(prevQuery, Http1Connection.QueryString);

            prevRequestUrl = Http1Connection.RawTarget;
            prevPath       = Http1Connection.Path;
            prevQuery      = Http1Connection.QueryString;
        }

        // Different request changes values (can't be Astrisk Form as all the same)
        rawTarget = "http://localhost/path1?q=123&w=xyzw";
        path      = "/path1";
        query     = "?q=123&w=xyzw";

        Http1Connection.Reset();
        ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
        reader = new SequenceReader <byte>(ros);
        Parser.ParseRequestLine(ParsingHandler, ref reader);

        // Equal the inputs.
        Assert.Equal(rawTarget, Http1Connection.RawTarget);
        Assert.Equal(path, Http1Connection.Path);
        Assert.Equal(query, Http1Connection.QueryString);

        // But not the same as the inputs.
        Assert.NotSame(rawTarget, Http1Connection.RawTarget);
        Assert.NotSame(path, Http1Connection.Path);
        Assert.NotSame(query, Http1Connection.QueryString);

        // Not equal previous request.
        Assert.NotEqual(prevRequestUrl, Http1Connection.RawTarget);
        Assert.NotEqual(prevPath, Http1Connection.Path);
        Assert.NotEqual(prevQuery, Http1Connection.QueryString);

        DifferentFormsWorkTogether();
    }
    public void AbsoluteForms(string rawTarget, string path, string query)
    {
        Http1Connection.Reset();
        var ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
        var reader = new SequenceReader <byte>(ros);

        Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));

        var prevRequestUrl = Http1Connection.RawTarget;
        var prevPath       = Http1Connection.Path;
        var prevQuery      = Http1Connection.QueryString;

        // Identical requests keep same materialized string values
        for (var i = 0; i < 5; i++)
        {
            Http1Connection.Reset();
            // RawTarget, Path, QueryString are null after reset
            Assert.Null(Http1Connection.RawTarget);
            Assert.Null(Http1Connection.Path);
            Assert.Null(Http1Connection.QueryString);

            ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
            reader = new SequenceReader <byte>(ros);
            Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));

            // Equal the inputs.
            Assert.Equal(rawTarget, Http1Connection.RawTarget);
            Assert.Equal(path, Http1Connection.Path);
            Assert.Equal(query, Http1Connection.QueryString);

            // But not the same as the inputs.

            Assert.NotSame(rawTarget, Http1Connection.RawTarget);
            Assert.NotSame(path, Http1Connection.Path);
            // string.Empty is used for empty strings, so should be the same.
            Assert.True(query.Length == 0 || !ReferenceEquals(query, Http1Connection.QueryString));

            // However, materalized strings are reused if generated for previous requests.

            Assert.Same(prevRequestUrl, Http1Connection.RawTarget);
            Assert.Same(prevPath, Http1Connection.Path);
            Assert.Same(prevQuery, Http1Connection.QueryString);

            prevRequestUrl = Http1Connection.RawTarget;
            prevPath       = Http1Connection.Path;
            prevQuery      = Http1Connection.QueryString;
        }

        // Different Absolute Form request changes values

        rawTarget = "http://localhost/path1?q=123&w=xyzw";
        path      = "/path1";
        query     = "?q=123&w=xyzw";

        Http1Connection.Reset();
        ros    = new ReadOnlySequence <byte>(Encoding.ASCII.GetBytes($"GET {rawTarget} HTTP/1.1\r\n"));
        reader = new SequenceReader <byte>(ros);
        Parser.ParseRequestLine(ParsingHandler, ref reader);

        // Equal the inputs.
        Assert.Equal(rawTarget, Http1Connection.RawTarget);
        Assert.Equal(path, Http1Connection.Path);
        Assert.Equal(query, Http1Connection.QueryString);

        // But not the same as the inputs.
        Assert.NotSame(rawTarget, Http1Connection.RawTarget);
        Assert.NotSame(path, Http1Connection.Path);
        Assert.NotSame(query, Http1Connection.QueryString);

        // Not equal previous request.
        Assert.NotEqual(prevRequestUrl, Http1Connection.RawTarget);
        Assert.NotEqual(prevPath, Http1Connection.Path);
        Assert.NotEqual(prevQuery, Http1Connection.QueryString);

        DifferentFormsWorkTogether();
    }
        public async Task CopyToAsyncDoesNotCopyBlocks()
        {
            var writeCount      = 0;
            var writeTcs        = new TaskCompletionSource <(byte[], int, int)>();
            var mockDestination = new Mock <Stream>()
            {
                CallBase = true
            };

            mockDestination
            .Setup(m => m.WriteAsync(It.IsAny <byte[]>(), It.IsAny <int>(), It.IsAny <int>(), CancellationToken.None))
            .Callback((byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
            {
                writeTcs.SetResult((buffer, offset, count));
                writeCount++;
            })
            .Returns(Task.CompletedTask);

            using (var memoryPool = KestrelMemoryPool.Create())
            {
                var options                = new PipeOptions(pool: memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
                var pair                   = DuplexPipe.CreateConnectionPair(options, options);
                var transport              = pair.Transport;
                var application            = pair.Application;
                var http1ConnectionContext = new Http1ConnectionContext
                {
                    ServiceContext     = new TestServiceContext(),
                    ConnectionFeatures = new FeatureCollection(),
                    Application        = application,
                    Transport          = transport,
                    MemoryPool         = memoryPool,
                    TimeoutControl     = Mock.Of <ITimeoutControl>()
                };
                var http1Connection = new Http1Connection(http1ConnectionContext)
                {
                    HasStartedConsumingRequestBody = true
                };

                var headers = new HttpRequestHeaders {
                    HeaderContentLength = "12"
                };
                var body = Http1MessageBody.For(HttpVersion.Http11, headers, http1Connection);

                var copyToAsyncTask = body.CopyToAsync(mockDestination.Object);

                var bytes  = Encoding.ASCII.GetBytes("Hello ");
                var buffer = http1Connection.RequestBodyPipe.Writer.GetMemory(2048);
                ArraySegment <byte> segment;
                Assert.True(MemoryMarshal.TryGetArray(buffer, out segment));
                Buffer.BlockCopy(bytes, 0, segment.Array, segment.Offset, bytes.Length);
                http1Connection.RequestBodyPipe.Writer.Advance(bytes.Length);
                await http1Connection.RequestBodyPipe.Writer.FlushAsync();

                // Verify the block passed to Stream.WriteAsync() is the same one incoming data was written into.
                Assert.Equal((segment.Array, segment.Offset, bytes.Length), await writeTcs.Task);

                // Verify the again when GetMemory returns the tail space of the same block.
                writeTcs = new TaskCompletionSource <(byte[], int, int)>();
                bytes    = Encoding.ASCII.GetBytes("World!");
                buffer   = http1Connection.RequestBodyPipe.Writer.GetMemory(2048);
                Assert.True(MemoryMarshal.TryGetArray(buffer, out segment));
                Buffer.BlockCopy(bytes, 0, segment.Array, segment.Offset, bytes.Length);
                http1Connection.RequestBodyPipe.Writer.Advance(bytes.Length);
                await http1Connection.RequestBodyPipe.Writer.FlushAsync();

                Assert.Equal((segment.Array, segment.Offset, bytes.Length), await writeTcs.Task);

                http1Connection.RequestBodyPipe.Writer.Complete();

                await copyToAsyncTask;

                Assert.Equal(2, writeCount);

                // Don't call body.StopAsync() because PumpAsync() was never called.
                http1Connection.RequestBodyPipe.Reader.Complete();
            }
        }
Beispiel #10
0
 public Http1ParsingHandler(Http1Connection connection, bool trailers)
 {
     Connection = connection;
     Trailers   = trailers;
 }
Beispiel #11
0
 public Http1ParsingHandler(Http1Connection connection)
 {
     Connection = connection;
     Trailers   = false;
 }