Better context pooling (#12385)

- This change goes from pooling just the HttpContext to pooling the entire TContext. In the past this was a huge struct that got copied around and now it can be a class. Servers can provide the storage for the TContext via a new `IHostContextContainer<TContext>` interface. 
- Removed IDefaultHttpContextContainer since it's been superseded by IHostContextContainer
- Move DefaultHttpContextFactory to Hosting to take advantage of internal methods
- Also handle a null FeatureCollection and null HttpContext and throw a better exception
This commit is contained in:
Ben Adams 2019-07-20 21:07:20 +01:00 committed by David Fowler
parent 4ac6a4ad35
commit 65ca72c420
18 changed files with 250 additions and 117 deletions

View File

@ -85,6 +85,15 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
public static void UseStaticWebAssets(Microsoft.AspNetCore.Hosting.IWebHostEnvironment environment, Microsoft.Extensions.Configuration.IConfiguration configuration) { } public static void UseStaticWebAssets(Microsoft.AspNetCore.Hosting.IWebHostEnvironment environment, Microsoft.Extensions.Configuration.IConfiguration configuration) { }
} }
} }
namespace Microsoft.AspNetCore.Http
{
public partial class DefaultHttpContextFactory : Microsoft.AspNetCore.Http.IHttpContextFactory
{
public DefaultHttpContextFactory(System.IServiceProvider serviceProvider) { }
public Microsoft.AspNetCore.Http.HttpContext Create(Microsoft.AspNetCore.Http.Features.IFeatureCollection featureCollection) { throw null; }
public void Dispose(Microsoft.AspNetCore.Http.HttpContext httpContext) { }
}
}
namespace Microsoft.Extensions.Hosting namespace Microsoft.Extensions.Hosting
{ {
public static partial class GenericHostWebHostBuilderExtensions public static partial class GenericHostWebHostBuilderExtensions

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -26,12 +28,30 @@ namespace Microsoft.AspNetCore.Http
public HttpContext Create(IFeatureCollection featureCollection) public HttpContext Create(IFeatureCollection featureCollection)
{ {
if (featureCollection == null) if (featureCollection is null)
{ {
throw new ArgumentNullException(nameof(featureCollection)); throw new ArgumentNullException(nameof(featureCollection));
} }
var httpContext = CreateHttpContext(featureCollection); var httpContext = new DefaultHttpContext(featureCollection);
Initialize(httpContext);
return httpContext;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Initialize(DefaultHttpContext httpContext, IFeatureCollection featureCollection)
{
Debug.Assert(featureCollection != null);
Debug.Assert(httpContext != null);
httpContext.Initialize(featureCollection);
Initialize(httpContext);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private DefaultHttpContext Initialize(DefaultHttpContext httpContext)
{
if (_httpContextAccessor != null) if (_httpContextAccessor != null)
{ {
_httpContextAccessor.HttpContext = httpContext; _httpContextAccessor.HttpContext = httpContext;
@ -43,16 +63,6 @@ namespace Microsoft.AspNetCore.Http
return httpContext; return httpContext;
} }
private static DefaultHttpContext CreateHttpContext(IFeatureCollection featureCollection)
{
if (featureCollection is IDefaultHttpContextContainer container)
{
return container.HttpContext;
}
return new DefaultHttpContext(featureCollection);
}
public void Dispose(HttpContext httpContext) public void Dispose(HttpContext httpContext)
{ {
if (_httpContextAccessor != null) if (_httpContextAccessor != null)
@ -60,5 +70,15 @@ namespace Microsoft.AspNetCore.Http
_httpContextAccessor.HttpContext = null; _httpContextAccessor.HttpContext = null;
} }
} }
internal void Dispose(DefaultHttpContext httpContext)
{
if (_httpContextAccessor != null)
{
_httpContextAccessor.HttpContext = null;
}
httpContext.Uninitialize();
}
} }
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Abstractions;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -15,6 +16,7 @@ namespace Microsoft.AspNetCore.Hosting
{ {
private readonly RequestDelegate _application; private readonly RequestDelegate _application;
private readonly IHttpContextFactory _httpContextFactory; private readonly IHttpContextFactory _httpContextFactory;
private readonly DefaultHttpContextFactory _defaultHttpContextFactory;
private HostingApplicationDiagnostics _diagnostics; private HostingApplicationDiagnostics _diagnostics;
public HostingApplication( public HostingApplication(
@ -25,19 +27,58 @@ namespace Microsoft.AspNetCore.Hosting
{ {
_application = application; _application = application;
_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource); _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
_httpContextFactory = httpContextFactory; if (httpContextFactory is DefaultHttpContextFactory factory)
{
_defaultHttpContextFactory = factory;
}
else
{
_httpContextFactory = httpContextFactory;
}
} }
// Set up the request // Set up the request
public Context CreateContext(IFeatureCollection contextFeatures) public Context CreateContext(IFeatureCollection contextFeatures)
{ {
var context = new Context(); Context hostContext;
var httpContext = _httpContextFactory.Create(contextFeatures); if (contextFeatures is IHostContextContainer<Context> container)
{
hostContext = container.HostContext;
if (hostContext is null)
{
hostContext = new Context();
container.HostContext = hostContext;
}
}
else
{
// Server doesn't support pooling, so create a new Context
hostContext = new Context();
}
_diagnostics.BeginRequest(httpContext, ref context); HttpContext httpContext;
if (_defaultHttpContextFactory != null)
{
var defaultHttpContext = (DefaultHttpContext)hostContext.HttpContext;
if (defaultHttpContext is null)
{
httpContext = _defaultHttpContextFactory.Create(contextFeatures);
hostContext.HttpContext = httpContext;
}
else
{
_defaultHttpContextFactory.Initialize(defaultHttpContext, contextFeatures);
httpContext = defaultHttpContext;
}
}
else
{
httpContext = _httpContextFactory.Create(contextFeatures);
hostContext.HttpContext = httpContext;
}
context.HttpContext = httpContext; _diagnostics.BeginRequest(httpContext, hostContext);
return context; return hostContext;
} }
// Execute the request // Execute the request
@ -51,18 +92,44 @@ namespace Microsoft.AspNetCore.Hosting
{ {
var httpContext = context.HttpContext; var httpContext = context.HttpContext;
_diagnostics.RequestEnd(httpContext, exception, context); _diagnostics.RequestEnd(httpContext, exception, context);
_httpContextFactory.Dispose(httpContext);
if (_defaultHttpContextFactory != null)
{
_defaultHttpContextFactory.Dispose((DefaultHttpContext)httpContext);
}
else
{
_httpContextFactory.Dispose(httpContext);
}
_diagnostics.ContextDisposed(context); _diagnostics.ContextDisposed(context);
// Reset the context as it may be pooled
context.Reset();
} }
internal struct Context
internal class Context
{ {
public HttpContext HttpContext { get; set; } public HttpContext HttpContext { get; set; }
public IDisposable Scope { get; set; } public IDisposable Scope { get; set; }
public long StartTimestamp { get; set; }
public bool EventLogEnabled { get; set; }
public Activity Activity { get; set; } public Activity Activity { get; set; }
public long StartTimestamp { get; set; }
internal bool HasDiagnosticListener { get; set; } internal bool HasDiagnosticListener { get; set; }
public bool EventLogEnabled { get; set; }
public void Reset()
{
// Not resetting HttpContext here as we pool it on the Context
Scope = null;
Activity = null;
StartTimestamp = 0;
HasDiagnosticListener = false;
EventLogEnabled = false;
}
} }
} }
} }

View File

@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Hosting
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void BeginRequest(HttpContext httpContext, ref HostingApplication.Context context) public void BeginRequest(HttpContext httpContext, HostingApplication.Context context)
{ {
long startTimestamp = 0; long startTimestamp = 0;

View File

@ -1,11 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Http namespace Microsoft.AspNetCore.Http

View File

@ -883,23 +883,21 @@ namespace Microsoft.AspNetCore.Hosting
public async Task WebHost_CreatesDefaultRequestIdentifierFeature_IfNotPresent() public async Task WebHost_CreatesDefaultRequestIdentifierFeature_IfNotPresent()
{ {
// Arrange // Arrange
HttpContext httpContext = null; var requestDelegate = new RequestDelegate(httpContext =>
var requestDelegate = new RequestDelegate(innerHttpContext =>
{
httpContext = innerHttpContext;
return Task.FromResult(0);
});
using (var host = CreateHost(requestDelegate))
{ {
// Act
await host.StartAsync();
// Assert // Assert
Assert.NotNull(httpContext); Assert.NotNull(httpContext);
var featuresTraceIdentifier = httpContext.Features.Get<IHttpRequestIdentifierFeature>().TraceIdentifier; var featuresTraceIdentifier = httpContext.Features.Get<IHttpRequestIdentifierFeature>().TraceIdentifier;
Assert.False(string.IsNullOrWhiteSpace(httpContext.TraceIdentifier)); Assert.False(string.IsNullOrWhiteSpace(httpContext.TraceIdentifier));
Assert.Same(httpContext.TraceIdentifier, featuresTraceIdentifier); Assert.Same(httpContext.TraceIdentifier, featuresTraceIdentifier);
return Task.CompletedTask;
});
using (var host = CreateHost(requestDelegate))
{
// Act
await host.StartAsync();
} }
} }
@ -907,13 +905,15 @@ namespace Microsoft.AspNetCore.Hosting
public async Task WebHost_DoesNot_CreateDefaultRequestIdentifierFeature_IfPresent() public async Task WebHost_DoesNot_CreateDefaultRequestIdentifierFeature_IfPresent()
{ {
// Arrange // Arrange
HttpContext httpContext = null;
var requestDelegate = new RequestDelegate(innerHttpContext =>
{
httpContext = innerHttpContext;
return Task.FromResult(0);
});
var requestIdentifierFeature = new StubHttpRequestIdentifierFeature(); var requestIdentifierFeature = new StubHttpRequestIdentifierFeature();
var requestDelegate = new RequestDelegate(httpContext =>
{
// Assert
Assert.NotNull(httpContext);
Assert.Same(requestIdentifierFeature, httpContext.Features.Get<IHttpRequestIdentifierFeature>());
return Task.CompletedTask;
});
using (var host = CreateHost(requestDelegate)) using (var host = CreateHost(requestDelegate))
{ {
@ -926,10 +926,6 @@ namespace Microsoft.AspNetCore.Hosting
}; };
// Act // Act
await host.StartAsync(); await host.StartAsync();
// Assert
Assert.NotNull(httpContext);
Assert.Same(requestIdentifierFeature, httpContext.Features.Get<IHttpRequestIdentifierFeature>());
} }
} }
@ -949,6 +945,36 @@ namespace Microsoft.AspNetCore.Hosting
} }
} }
[Fact]
public async Task WebHost_HttpContextUseAfterRequestEnd_Fails()
{
// Arrange
HttpContext capturedContext = null;
HttpRequest capturedRequest = null;
var requestDelegate = new RequestDelegate(httpContext =>
{
capturedContext = httpContext;
capturedRequest = httpContext.Request;
return Task.CompletedTask;
});
using (var host = CreateHost(requestDelegate))
{
// Act
await host.StartAsync();
// Assert
Assert.NotNull(capturedContext);
Assert.NotNull(capturedRequest);
Assert.Throws<ObjectDisposedException>(() => capturedContext.TraceIdentifier);
Assert.Throws<ObjectDisposedException>(() => capturedContext.Features.Get<IHttpRequestIdentifierFeature>());
Assert.Throws<ObjectDisposedException>(() => capturedRequest.Scheme);
}
}
public class CountStartup public class CountStartup
{ {
public static int ConfigureServicesCount; public static int ConfigureServicesCount;

View File

@ -27,6 +27,13 @@ namespace Microsoft.AspNetCore.Hosting.Server
public bool IsEnabled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public bool IsEnabled { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
} }
} }
namespace Microsoft.AspNetCore.Hosting.Server.Abstractions
{
public partial interface IHostContextContainer<TContext>
{
TContext HostContext { get; set; }
}
}
namespace Microsoft.AspNetCore.Hosting.Server.Features namespace Microsoft.AspNetCore.Hosting.Server.Features
{ {
public partial interface IServerAddressesFeature public partial interface IServerAddressesFeature

View File

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Hosting.Server.Abstractions
{
/// <summary>
/// When implemented by a Server allows an <see cref="IHttpApplication{TContext}"/> to pool and reuse
/// its <typeparamref name="TContext"/> between requests.
/// </summary>
/// <typeparam name="TContext">The <see cref="IHttpApplication{TContext}"/> Host context</typeparam>
public interface IHostContextContainer<TContext>
{
TContext HostContext { get; set; }
}
}

View File

@ -2,11 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.TestHost.Tests namespace Microsoft.AspNetCore.TestHost.Tests
@ -19,7 +17,9 @@ namespace Microsoft.AspNetCore.TestHost.Tests
[InlineData("http://localhost:81/connect", "localhost:81")] [InlineData("http://localhost:81/connect", "localhost:81")]
public async Task ConnectAsync_ShouldSetRequestProperties(string requestUri, string expectedHost) public async Task ConnectAsync_ShouldSetRequestProperties(string requestUri, string expectedHost)
{ {
HttpRequest capturedRequest = null; string capturedScheme = null;
string capturedHost = null;
string capturedPath = null;
using (var testServer = new TestServer(new WebHostBuilder() using (var testServer = new TestServer(new WebHostBuilder()
.Configure(app => .Configure(app =>
@ -28,7 +28,9 @@ namespace Microsoft.AspNetCore.TestHost.Tests
{ {
if (ctx.Request.Path.StartsWithSegments("/connect")) if (ctx.Request.Path.StartsWithSegments("/connect"))
{ {
capturedRequest = ctx.Request; capturedScheme = ctx.Request.Scheme;
capturedHost = ctx.Request.Host.Value;
capturedPath = ctx.Request.Path;
} }
return Task.FromResult(0); return Task.FromResult(0);
}); });
@ -40,7 +42,7 @@ namespace Microsoft.AspNetCore.TestHost.Tests
{ {
await client.ConnectAsync( await client.ConnectAsync(
uri: new Uri(requestUri), uri: new Uri(requestUri),
cancellationToken: default(CancellationToken)); cancellationToken: default);
} }
catch catch
{ {
@ -48,9 +50,9 @@ namespace Microsoft.AspNetCore.TestHost.Tests
} }
} }
Assert.Equal("http", capturedRequest.Scheme); Assert.Equal("http", capturedScheme);
Assert.Equal(expectedHost, capturedRequest.Host.Value); Assert.Equal(expectedHost, capturedHost);
Assert.Equal("/connect", capturedRequest.Path); Assert.Equal("/connect", capturedPath);
} }
} }
} }

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Http.Features
public FeatureReferences(IFeatureCollection collection) public FeatureReferences(IFeatureCollection collection)
{ {
Collection = collection; Collection = collection;
Cache = default(TCache); Cache = default;
Revision = collection.Revision; Revision = collection.Revision;
} }
@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Http.Features
Func<TState, TFeature> factory) where TFeature : class Func<TState, TFeature> factory) where TFeature : class
{ {
var flush = false; var flush = false;
var revision = Collection.Revision; var revision = Collection?.Revision ?? ContextDisposed();
if (Revision != revision) if (Revision != revision)
{ {
// Clear cached value to force call to UpdateCached // Clear cached value to force call to UpdateCached
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Http.Features
if (flush) if (flush)
{ {
// Collection detected as changed, clear cache // Collection detected as changed, clear cache
Cache = default(TCache); Cache = default;
} }
cached = Collection.Get<TFeature>(); cached = Collection.Get<TFeature>();
@ -108,5 +108,16 @@ namespace Microsoft.AspNetCore.Http.Features
public TFeature Fetch<TFeature>(ref TFeature cached, Func<IFeatureCollection, TFeature> factory) public TFeature Fetch<TFeature>(ref TFeature cached, Func<IFeatureCollection, TFeature> factory)
where TFeature : class => Fetch(ref cached, Collection, factory); where TFeature : class => Fetch(ref cached, Collection, factory);
private static int ContextDisposed()
{
ThrowContextDisposed();
return 0;
}
private static void ThrowContextDisposed()
{
throw new ObjectDisposedException(nameof(Collection), nameof(IFeatureCollection) + " has been disposed.");
}
} }
} }

View File

@ -54,12 +54,6 @@ namespace Microsoft.AspNetCore.Http
public void Initialize(Microsoft.AspNetCore.Http.Features.IFeatureCollection features) { } public void Initialize(Microsoft.AspNetCore.Http.Features.IFeatureCollection features) { }
public void Uninitialize() { } public void Uninitialize() { }
} }
public partial class DefaultHttpContextFactory : Microsoft.AspNetCore.Http.IHttpContextFactory
{
public DefaultHttpContextFactory(System.IServiceProvider serviceProvider) { }
public Microsoft.AspNetCore.Http.HttpContext Create(Microsoft.AspNetCore.Http.Features.IFeatureCollection featureCollection) { throw null; }
public void Dispose(Microsoft.AspNetCore.Http.HttpContext httpContext) { }
}
public partial class FormCollection : Microsoft.AspNetCore.Http.IFormCollection, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>>, System.Collections.IEnumerable public partial class FormCollection : Microsoft.AspNetCore.Http.IFormCollection, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, Microsoft.Extensions.Primitives.StringValues>>, System.Collections.IEnumerable
{ {
public static readonly Microsoft.AspNetCore.Http.FormCollection Empty; public static readonly Microsoft.AspNetCore.Http.FormCollection Empty;
@ -164,10 +158,6 @@ namespace Microsoft.AspNetCore.Http
public static void EnableBuffering(this Microsoft.AspNetCore.Http.HttpRequest request, int bufferThreshold, long bufferLimit) { } public static void EnableBuffering(this Microsoft.AspNetCore.Http.HttpRequest request, int bufferThreshold, long bufferLimit) { }
public static void EnableBuffering(this Microsoft.AspNetCore.Http.HttpRequest request, long bufferLimit) { } public static void EnableBuffering(this Microsoft.AspNetCore.Http.HttpRequest request, long bufferLimit) { }
} }
public partial interface IDefaultHttpContextContainer
{
Microsoft.AspNetCore.Http.DefaultHttpContext HttpContext { get; }
}
public partial class MiddlewareFactory : Microsoft.AspNetCore.Http.IMiddlewareFactory public partial class MiddlewareFactory : Microsoft.AspNetCore.Http.IMiddlewareFactory
{ {
public MiddlewareFactory(System.IServiceProvider serviceProvider) { } public MiddlewareFactory(System.IServiceProvider serviceProvider) { }

View File

@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Http
private IHttpRequestIdentifierFeature RequestIdentifierFeature => private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
_features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature); _features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature);
public override IFeatureCollection Features => _features.Collection; public override IFeatureCollection Features => _features.Collection ?? ContextDisposed();
public override HttpRequest Request => _request; public override HttpRequest Request => _request;
@ -169,6 +169,17 @@ namespace Microsoft.AspNetCore.Http
LifetimeFeature.Abort(); LifetimeFeature.Abort();
} }
private static IFeatureCollection ContextDisposed()
{
ThrowContextDisposed();
return null;
}
private static void ThrowContextDisposed()
{
throw new ObjectDisposedException(nameof(HttpContext), $"Request has finished and {nameof(HttpContext)} disposed.");
}
struct FeatureInterfaces struct FeatureInterfaces
{ {
public IItemsFeature Items; public IItemsFeature Items;

View File

@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Http
throw new ArgumentNullException(nameof(featureCollection)); throw new ArgumentNullException(nameof(featureCollection));
} }
var httpContext = CreateHttpContext(featureCollection); var httpContext = new DefaultHttpContext(featureCollection);
if (_httpContextAccessor != null) if (_httpContextAccessor != null)
{ {
_httpContextAccessor.HttpContext = httpContext; _httpContextAccessor.HttpContext = httpContext;
@ -66,16 +66,6 @@ namespace Microsoft.AspNetCore.Http
return httpContext; return httpContext;
} }
private static DefaultHttpContext CreateHttpContext(IFeatureCollection featureCollection)
{
if (featureCollection is IDefaultHttpContextContainer container)
{
return container.HttpContext;
}
return new DefaultHttpContext(featureCollection);
}
public void Dispose(HttpContext httpContext) public void Dispose(HttpContext httpContext)
{ {
if (_httpContextAccessor != null) if (_httpContextAccessor != null)

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.AspNetCore.Http
{
public interface IDefaultHttpContextContainer
{
DefaultHttpContext HttpContext { get; }
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Hosting.Server.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal sealed class Http1Connection<TContext> : Http1Connection, IHostContextContainer<TContext>
{
public Http1Connection(HttpConnectionContext context) : base(context) { }
TContext IHostContextContainer<TContext>.HostContext { get; set; }
}
}

View File

@ -25,7 +25,7 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal abstract partial class HttpProtocol : IDefaultHttpContextContainer, IHttpResponseControl internal abstract partial class HttpProtocol : IHttpResponseControl
{ {
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close"); private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive"); private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
@ -64,7 +64,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private long _responseBytesWritten; private long _responseBytesWritten;
private readonly HttpConnectionContext _context; private readonly HttpConnectionContext _context;
private DefaultHttpContext _httpContext;
private RouteValueDictionary _routeValues; private RouteValueDictionary _routeValues;
private Endpoint _endpoint; private Endpoint _endpoint;
@ -296,23 +295,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders(); protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders();
DefaultHttpContext IDefaultHttpContextContainer.HttpContext
{
get
{
if (_httpContext is null)
{
_httpContext = new DefaultHttpContext(this);
}
else
{
_httpContext.Initialize(this);
}
return _httpContext;
}
}
public void InitializeBodyControl(MessageBody messageBody) public void InitializeBodyControl(MessageBody messageBody)
{ {
if (_bodyControl == null) if (_bodyControl == null)
@ -409,8 +391,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_responseBytesWritten = 0; _responseBytesWritten = 0;
_httpContext?.Uninitialize();
OnReset(); OnReset();
} }

View File

@ -1,11 +1,13 @@
using System; // Copyright (c) .NET Foundation. All rights reserved.
using System.Collections.Generic; // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Abstractions;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
internal sealed class Http2Stream<TContext> : Http2Stream internal sealed class Http2Stream<TContext> : Http2Stream, IHostContextContainer<TContext>
{ {
private readonly IHttpApplication<TContext> _application; private readonly IHttpApplication<TContext> _application;
@ -19,5 +21,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// REVIEW: Should we store this in a field for easy debugging? // REVIEW: Should we store this in a field for easy debugging?
_ = ProcessRequestsAsync(_application); _ = ProcessRequestsAsync(_application);
} }
// Pooled Host context
TContext IHostContextContainer<TContext>.HostContext { get; set; }
} }
} }

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{ {
case HttpProtocols.Http1: case HttpProtocols.Http1:
// _http1Connection must be initialized before adding the connection to the connection manager // _http1Connection must be initialized before adding the connection to the connection manager
requestProcessor = _http1Connection = new Http1Connection(_context); requestProcessor = _http1Connection = new Http1Connection<TContext>(_context);
_protocolSelectionState = ProtocolSelectionState.Selected; _protocolSelectionState = ProtocolSelectionState.Selected;
break; break;
case HttpProtocols.Http2: case HttpProtocols.Http2: