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:
parent
4ac6a4ad35
commit
65ca72c420
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) { }
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http
|
|
||||||
{
|
|
||||||
public interface IDefaultHttpContextContainer
|
|
||||||
{
|
|
||||||
DefaultHttpContext HttpContext { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue