// For testing only internal void Initialize(IRequestProcessor requestProcessor) { _requestProcessor = requestProcessor; _http1Connection = requestProcessor as Http1Connection; _protocolSelectionState = ProtocolSelectionState.Selected; }
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(); } }
public Http1ParsingHandler(Http1Connection connection, bool trailers) { Connection = connection; Trailers = trailers; }
public Http1ParsingHandler(Http1Connection connection) { Connection = connection; Trailers = false; }