Reuse HttpContext object per HTTP/1 connection and HTTP/2 stream (#6424)
- Today in Kestrel, we reuse the IFeatureCollection per connection and per Http2Stream. This PR aims to take advantage of that same technique and affinitize the HttpContext and friends so that they are only allocated per connection. - ReusableHttpContext and friends mimic the functionality of DefaultHttpContext but is sealed and has no overridable methods. - Introduce IHttpContextContainer which allows servers to cache the HttpContext and friends across requests.
This commit is contained in:
parent
b4c9ca12b4
commit
688ad19170
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
|
|
@ -35,7 +36,7 @@ namespace Microsoft.AspNetCore.Http
|
|||
throw new ArgumentNullException(nameof(featureCollection));
|
||||
}
|
||||
|
||||
var httpContext = new DefaultHttpContext(featureCollection);
|
||||
var httpContext = CreateHttpContext(featureCollection);
|
||||
if (_httpContextAccessor != null)
|
||||
{
|
||||
_httpContextAccessor.HttpContext = httpContext;
|
||||
|
|
@ -47,6 +48,16 @@ namespace Microsoft.AspNetCore.Http
|
|||
return httpContext;
|
||||
}
|
||||
|
||||
private static HttpContext CreateHttpContext(IFeatureCollection featureCollection)
|
||||
{
|
||||
if (featureCollection is IHttpContextContainer container)
|
||||
{
|
||||
return container.HttpContext;
|
||||
}
|
||||
|
||||
return new ReusableHttpContext(featureCollection);
|
||||
}
|
||||
|
||||
public void Dispose(HttpContext httpContext)
|
||||
{
|
||||
if (_httpContextAccessor != null)
|
||||
|
|
@ -55,4 +66,4 @@ namespace Microsoft.AspNetCore.Http
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http
|
||||
{
|
||||
public interface IHttpContextContainer
|
||||
{
|
||||
HttpContext HttpContext { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -171,4 +171,4 @@ namespace Microsoft.AspNetCore.Http.Internal
|
|||
public IRouteValuesFeature RouteValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,4 +136,4 @@ namespace Microsoft.AspNetCore.Http.Internal
|
|||
public IResponseCookiesFeature Cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Internal
|
||||
{
|
||||
public sealed class ReusableConnectionInfo : ConnectionInfo
|
||||
{
|
||||
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
|
||||
private readonly static Func<IFeatureCollection, IHttpConnectionFeature> _newHttpConnectionFeature = f => new HttpConnectionFeature();
|
||||
private readonly static Func<IFeatureCollection, ITlsConnectionFeature> _newTlsConnectionFeature = f => new TlsConnectionFeature();
|
||||
|
||||
private FeatureReferences<FeatureInterfaces> _features;
|
||||
|
||||
public ReusableConnectionInfo(IFeatureCollection features)
|
||||
{
|
||||
Initialize(features);
|
||||
}
|
||||
|
||||
public void Initialize(IFeatureCollection features)
|
||||
{
|
||||
_features = new FeatureReferences<FeatureInterfaces>(features);
|
||||
}
|
||||
|
||||
public void Uninitialize()
|
||||
{
|
||||
_features = default(FeatureReferences<FeatureInterfaces>);
|
||||
}
|
||||
|
||||
private IHttpConnectionFeature HttpConnectionFeature =>
|
||||
_features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature);
|
||||
|
||||
private ITlsConnectionFeature TlsConnectionFeature =>
|
||||
_features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Id
|
||||
{
|
||||
get { return HttpConnectionFeature.ConnectionId; }
|
||||
set { HttpConnectionFeature.ConnectionId = value; }
|
||||
}
|
||||
|
||||
public override IPAddress RemoteIpAddress
|
||||
{
|
||||
get { return HttpConnectionFeature.RemoteIpAddress; }
|
||||
set { HttpConnectionFeature.RemoteIpAddress = value; }
|
||||
}
|
||||
|
||||
public override int RemotePort
|
||||
{
|
||||
get { return HttpConnectionFeature.RemotePort; }
|
||||
set { HttpConnectionFeature.RemotePort = value; }
|
||||
}
|
||||
|
||||
public override IPAddress LocalIpAddress
|
||||
{
|
||||
get { return HttpConnectionFeature.LocalIpAddress; }
|
||||
set { HttpConnectionFeature.LocalIpAddress = value; }
|
||||
}
|
||||
|
||||
public override int LocalPort
|
||||
{
|
||||
get { return HttpConnectionFeature.LocalPort; }
|
||||
set { HttpConnectionFeature.LocalPort = value; }
|
||||
}
|
||||
|
||||
public override X509Certificate2 ClientCertificate
|
||||
{
|
||||
get { return TlsConnectionFeature.ClientCertificate; }
|
||||
set { TlsConnectionFeature.ClientCertificate = value; }
|
||||
}
|
||||
|
||||
public override Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return TlsConnectionFeature.GetClientCertificateAsync(cancellationToken);
|
||||
}
|
||||
|
||||
struct FeatureInterfaces
|
||||
{
|
||||
public IHttpConnectionFeature Connection;
|
||||
public ITlsConnectionFeature TlsConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Http.Features.Authentication;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Internal
|
||||
{
|
||||
public sealed class ReusableHttpContext : HttpContext
|
||||
{
|
||||
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
|
||||
private readonly static Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
|
||||
private readonly static Func<IFeatureCollection, IServiceProvidersFeature> _newServiceProvidersFeature = f => new ServiceProvidersFeature();
|
||||
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
|
||||
private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
|
||||
private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
|
||||
private readonly static Func<IFeatureCollection, ISessionFeature> _nullSessionFeature = f => null;
|
||||
private readonly static Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();
|
||||
|
||||
private FeatureReferences<FeatureInterfaces> _features;
|
||||
|
||||
private ReusableHttpRequest _request;
|
||||
private ReusableHttpResponse _response;
|
||||
|
||||
private ReusableConnectionInfo _connection;
|
||||
private ReusableWebSocketManager _websockets;
|
||||
|
||||
public ReusableHttpContext(IFeatureCollection features)
|
||||
{
|
||||
_features = new FeatureReferences<FeatureInterfaces>(features);
|
||||
_request = new ReusableHttpRequest(this);
|
||||
_response = new ReusableHttpResponse(this);
|
||||
}
|
||||
|
||||
public void Initialize(IFeatureCollection features)
|
||||
{
|
||||
_features = new FeatureReferences<FeatureInterfaces>(features);
|
||||
_request.Initialize(this);
|
||||
_response.Initialize(this);
|
||||
_connection?.Initialize(features);
|
||||
_websockets?.Initialize(features);
|
||||
}
|
||||
|
||||
public void Uninitialize()
|
||||
{
|
||||
_features = default;
|
||||
|
||||
_request.Uninitialize();
|
||||
_response.Uninitialize();
|
||||
_connection?.Uninitialize();
|
||||
_websockets?.Uninitialize();
|
||||
}
|
||||
|
||||
private IItemsFeature ItemsFeature =>
|
||||
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
|
||||
|
||||
private IServiceProvidersFeature ServiceProvidersFeature =>
|
||||
_features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
|
||||
|
||||
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
|
||||
_features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
|
||||
|
||||
private IHttpRequestLifetimeFeature LifetimeFeature =>
|
||||
_features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature);
|
||||
|
||||
private ISessionFeature SessionFeature =>
|
||||
_features.Fetch(ref _features.Cache.Session, _newSessionFeature);
|
||||
|
||||
private ISessionFeature SessionFeatureOrNull =>
|
||||
_features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
|
||||
|
||||
|
||||
private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
|
||||
_features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature);
|
||||
|
||||
public override IFeatureCollection Features => _features.Collection;
|
||||
|
||||
public override HttpRequest Request => _request;
|
||||
|
||||
public override HttpResponse Response => _response;
|
||||
|
||||
public override ConnectionInfo Connection => _connection ?? (_connection = new ReusableConnectionInfo(_features.Collection));
|
||||
|
||||
[Obsolete("This is obsolete and will be removed in a future version. The recommended alternative is to use Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions. See https://go.microsoft.com/fwlink/?linkid=845470.")]
|
||||
public override AuthenticationManager Authentication => throw new NotSupportedException();
|
||||
|
||||
public override WebSocketManager WebSockets => _websockets ?? (_websockets = new ReusableWebSocketManager(_features.Collection));
|
||||
|
||||
|
||||
public override ClaimsPrincipal User
|
||||
{
|
||||
get
|
||||
{
|
||||
var user = HttpAuthenticationFeature.User;
|
||||
if (user == null)
|
||||
{
|
||||
user = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
HttpAuthenticationFeature.User = user;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
set { HttpAuthenticationFeature.User = value; }
|
||||
}
|
||||
|
||||
public override IDictionary<object, object> Items
|
||||
{
|
||||
get { return ItemsFeature.Items; }
|
||||
set { ItemsFeature.Items = value; }
|
||||
}
|
||||
|
||||
public override IServiceProvider RequestServices
|
||||
{
|
||||
get { return ServiceProvidersFeature.RequestServices; }
|
||||
set { ServiceProvidersFeature.RequestServices = value; }
|
||||
}
|
||||
|
||||
public override CancellationToken RequestAborted
|
||||
{
|
||||
get { return LifetimeFeature.RequestAborted; }
|
||||
set { LifetimeFeature.RequestAborted = value; }
|
||||
}
|
||||
|
||||
public override string TraceIdentifier
|
||||
{
|
||||
get { return RequestIdentifierFeature.TraceIdentifier; }
|
||||
set { RequestIdentifierFeature.TraceIdentifier = value; }
|
||||
}
|
||||
|
||||
public override ISession Session
|
||||
{
|
||||
get
|
||||
{
|
||||
var feature = SessionFeatureOrNull;
|
||||
if (feature == null)
|
||||
{
|
||||
throw new InvalidOperationException("Session has not been configured for this application " +
|
||||
"or request.");
|
||||
}
|
||||
return feature.Session;
|
||||
}
|
||||
set
|
||||
{
|
||||
SessionFeature.Session = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Abort()
|
||||
{
|
||||
LifetimeFeature.Abort();
|
||||
}
|
||||
|
||||
struct FeatureInterfaces
|
||||
{
|
||||
public IItemsFeature Items;
|
||||
public IServiceProvidersFeature ServiceProviders;
|
||||
public IHttpAuthenticationFeature Authentication;
|
||||
public IHttpRequestLifetimeFeature Lifetime;
|
||||
public ISessionFeature Session;
|
||||
public IHttpRequestIdentifierFeature RequestIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Internal
|
||||
{
|
||||
public sealed class ReusableHttpRequest : HttpRequest
|
||||
{
|
||||
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
|
||||
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
|
||||
private readonly static Func<IFeatureCollection, IQueryFeature> _newQueryFeature = f => new QueryFeature(f);
|
||||
private readonly static Func<HttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r);
|
||||
private readonly static Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);
|
||||
private readonly static Func<IFeatureCollection, IRouteValuesFeature> _newRouteValuesFeature = f => new RouteValuesFeature();
|
||||
|
||||
private HttpContext _context;
|
||||
private FeatureReferences<FeatureInterfaces> _features;
|
||||
|
||||
public ReusableHttpRequest(HttpContext context)
|
||||
{
|
||||
Initialize(context);
|
||||
}
|
||||
|
||||
public void Initialize(HttpContext context)
|
||||
{
|
||||
_context = context;
|
||||
_features = new FeatureReferences<FeatureInterfaces>(context.Features);
|
||||
}
|
||||
|
||||
public void Uninitialize()
|
||||
{
|
||||
_context = null;
|
||||
_features = default;
|
||||
}
|
||||
|
||||
public override HttpContext HttpContext => _context;
|
||||
|
||||
private IHttpRequestFeature HttpRequestFeature =>
|
||||
_features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
|
||||
|
||||
private IQueryFeature QueryFeature =>
|
||||
_features.Fetch(ref _features.Cache.Query, _newQueryFeature);
|
||||
|
||||
private IFormFeature FormFeature =>
|
||||
_features.Fetch(ref _features.Cache.Form, this, _newFormFeature);
|
||||
|
||||
private IRequestCookiesFeature RequestCookiesFeature =>
|
||||
_features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature);
|
||||
|
||||
private IRouteValuesFeature RouteValuesFeature =>
|
||||
_features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature);
|
||||
|
||||
public override PathString PathBase
|
||||
{
|
||||
get { return new PathString(HttpRequestFeature.PathBase); }
|
||||
set { HttpRequestFeature.PathBase = value.Value; }
|
||||
}
|
||||
|
||||
public override PathString Path
|
||||
{
|
||||
get { return new PathString(HttpRequestFeature.Path); }
|
||||
set { HttpRequestFeature.Path = value.Value; }
|
||||
}
|
||||
|
||||
public override QueryString QueryString
|
||||
{
|
||||
get { return new QueryString(HttpRequestFeature.QueryString); }
|
||||
set { HttpRequestFeature.QueryString = value.Value; }
|
||||
}
|
||||
|
||||
public override long? ContentLength
|
||||
{
|
||||
get { return Headers.ContentLength; }
|
||||
set { Headers.ContentLength = value; }
|
||||
}
|
||||
|
||||
public override Stream Body
|
||||
{
|
||||
get { return HttpRequestFeature.Body; }
|
||||
set { HttpRequestFeature.Body = value; }
|
||||
}
|
||||
|
||||
public override string Method
|
||||
{
|
||||
get { return HttpRequestFeature.Method; }
|
||||
set { HttpRequestFeature.Method = value; }
|
||||
}
|
||||
|
||||
public override string Scheme
|
||||
{
|
||||
get { return HttpRequestFeature.Scheme; }
|
||||
set { HttpRequestFeature.Scheme = value; }
|
||||
}
|
||||
|
||||
public override bool IsHttps
|
||||
{
|
||||
get { return string.Equals(Constants.Https, Scheme, StringComparison.OrdinalIgnoreCase); }
|
||||
set { Scheme = value ? Constants.Https : Constants.Http; }
|
||||
}
|
||||
|
||||
public override HostString Host
|
||||
{
|
||||
get { return HostString.FromUriComponent(Headers["Host"]); }
|
||||
set { Headers["Host"] = value.ToUriComponent(); }
|
||||
}
|
||||
|
||||
public override IQueryCollection Query
|
||||
{
|
||||
get { return QueryFeature.Query; }
|
||||
set { QueryFeature.Query = value; }
|
||||
}
|
||||
|
||||
public override string Protocol
|
||||
{
|
||||
get { return HttpRequestFeature.Protocol; }
|
||||
set { HttpRequestFeature.Protocol = value; }
|
||||
}
|
||||
|
||||
public override IHeaderDictionary Headers
|
||||
{
|
||||
get { return HttpRequestFeature.Headers; }
|
||||
}
|
||||
|
||||
public override IRequestCookieCollection Cookies
|
||||
{
|
||||
get { return RequestCookiesFeature.Cookies; }
|
||||
set { RequestCookiesFeature.Cookies = value; }
|
||||
}
|
||||
|
||||
public override string ContentType
|
||||
{
|
||||
get { return Headers[HeaderNames.ContentType]; }
|
||||
set { Headers[HeaderNames.ContentType] = value; }
|
||||
}
|
||||
|
||||
public override bool HasFormContentType
|
||||
{
|
||||
get { return FormFeature.HasFormContentType; }
|
||||
}
|
||||
|
||||
public override IFormCollection Form
|
||||
{
|
||||
get { return FormFeature.ReadForm(); }
|
||||
set { FormFeature.Form = value; }
|
||||
}
|
||||
|
||||
public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return FormFeature.ReadFormAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public override RouteValueDictionary RouteValues
|
||||
{
|
||||
get { return RouteValuesFeature.RouteValues; }
|
||||
set { RouteValuesFeature.RouteValues = value; }
|
||||
}
|
||||
|
||||
struct FeatureInterfaces
|
||||
{
|
||||
public IHttpRequestFeature Request;
|
||||
public IQueryFeature Query;
|
||||
public IFormFeature Form;
|
||||
public IRequestCookiesFeature Cookies;
|
||||
public IRouteValuesFeature RouteValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Internal
|
||||
{
|
||||
public sealed class ReusableHttpResponse : HttpResponse
|
||||
{
|
||||
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
|
||||
private readonly static Func<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null;
|
||||
private readonly static Func<IFeatureCollection, IResponseCookiesFeature> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f);
|
||||
|
||||
private HttpContext _context;
|
||||
private FeatureReferences<FeatureInterfaces> _features;
|
||||
|
||||
public ReusableHttpResponse(HttpContext context)
|
||||
{
|
||||
Initialize(context);
|
||||
}
|
||||
|
||||
public void Initialize(HttpContext context)
|
||||
{
|
||||
_context = context;
|
||||
_features = new FeatureReferences<FeatureInterfaces>(context.Features);
|
||||
}
|
||||
|
||||
public void Uninitialize()
|
||||
{
|
||||
_context = null;
|
||||
_features = default;
|
||||
}
|
||||
|
||||
private IHttpResponseFeature HttpResponseFeature =>
|
||||
_features.Fetch(ref _features.Cache.Response, _nullResponseFeature);
|
||||
|
||||
private IResponseCookiesFeature ResponseCookiesFeature =>
|
||||
_features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature);
|
||||
|
||||
|
||||
public override HttpContext HttpContext { get { return _context; } }
|
||||
|
||||
public override int StatusCode
|
||||
{
|
||||
get { return HttpResponseFeature.StatusCode; }
|
||||
set { HttpResponseFeature.StatusCode = value; }
|
||||
}
|
||||
|
||||
public override IHeaderDictionary Headers
|
||||
{
|
||||
get { return HttpResponseFeature.Headers; }
|
||||
}
|
||||
|
||||
public override Stream Body
|
||||
{
|
||||
get { return HttpResponseFeature.Body; }
|
||||
set { HttpResponseFeature.Body = value; }
|
||||
}
|
||||
|
||||
public override long? ContentLength
|
||||
{
|
||||
get { return Headers.ContentLength; }
|
||||
set { Headers.ContentLength = value; }
|
||||
}
|
||||
|
||||
public override string ContentType
|
||||
{
|
||||
get
|
||||
{
|
||||
return Headers[HeaderNames.ContentType];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
HttpResponseFeature.Headers.Remove(HeaderNames.ContentType);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpResponseFeature.Headers[HeaderNames.ContentType] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IResponseCookies Cookies
|
||||
{
|
||||
get { return ResponseCookiesFeature.Cookies; }
|
||||
}
|
||||
|
||||
public override bool HasStarted
|
||||
{
|
||||
get { return HttpResponseFeature.HasStarted; }
|
||||
}
|
||||
|
||||
public override void OnStarting(Func<object, Task> callback, object state)
|
||||
{
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
HttpResponseFeature.OnStarting(callback, state);
|
||||
}
|
||||
|
||||
public override void OnCompleted(Func<object, Task> callback, object state)
|
||||
{
|
||||
if (callback == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callback));
|
||||
}
|
||||
|
||||
HttpResponseFeature.OnCompleted(callback, state);
|
||||
}
|
||||
|
||||
public override void Redirect(string location, bool permanent)
|
||||
{
|
||||
if (permanent)
|
||||
{
|
||||
HttpResponseFeature.StatusCode = 301;
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpResponseFeature.StatusCode = 302;
|
||||
}
|
||||
|
||||
Headers[HeaderNames.Location] = location;
|
||||
}
|
||||
|
||||
struct FeatureInterfaces
|
||||
{
|
||||
public IHttpResponseFeature Response;
|
||||
public IResponseCookiesFeature Cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http.Internal
|
||||
{
|
||||
public sealed class ReusableWebSocketManager : WebSocketManager
|
||||
{
|
||||
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
|
||||
private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
|
||||
private readonly static Func<IFeatureCollection, IHttpWebSocketFeature> _nullWebSocketFeature = f => null;
|
||||
|
||||
private FeatureReferences<FeatureInterfaces> _features;
|
||||
|
||||
public ReusableWebSocketManager(IFeatureCollection features)
|
||||
{
|
||||
Initialize(features);
|
||||
}
|
||||
|
||||
public void Initialize(IFeatureCollection features)
|
||||
{
|
||||
_features = new FeatureReferences<FeatureInterfaces>(features);
|
||||
}
|
||||
|
||||
public void Uninitialize()
|
||||
{
|
||||
_features = default;
|
||||
}
|
||||
|
||||
private IHttpRequestFeature HttpRequestFeature =>
|
||||
_features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
|
||||
|
||||
private IHttpWebSocketFeature WebSocketFeature =>
|
||||
_features.Fetch(ref _features.Cache.WebSockets, _nullWebSocketFeature);
|
||||
|
||||
public override bool IsWebSocketRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
return WebSocketFeature != null && WebSocketFeature.IsWebSocketRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public override IList<string> WebSocketRequestedProtocols
|
||||
{
|
||||
get
|
||||
{
|
||||
return ParsingHelpers.GetHeaderSplit(HttpRequestFeature.Headers, HeaderNames.WebSocketSubProtocols);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<WebSocket> AcceptWebSocketAsync(string subProtocol)
|
||||
{
|
||||
if (WebSocketFeature == null)
|
||||
{
|
||||
throw new NotSupportedException("WebSockets are not supported");
|
||||
}
|
||||
return WebSocketFeature.AcceptAsync(new WebSocketAcceptContext() { SubProtocol = subProtocol });
|
||||
}
|
||||
|
||||
struct FeatureInterfaces
|
||||
{
|
||||
public IHttpRequestFeature Request;
|
||||
public IHttpWebSocketFeature WebSockets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,13 +17,14 @@ using Microsoft.AspNetCore.Connections;
|
|||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||
{
|
||||
public abstract partial class HttpProtocol : IHttpResponseControl
|
||||
public abstract partial class HttpProtocol : IHttpContextContainer, IHttpResponseControl
|
||||
{
|
||||
private static readonly byte[] _bytesConnectionClose = Encoding.ASCII.GetBytes("\r\nConnection: close");
|
||||
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
|
||||
|
|
@ -63,6 +64,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
private long _responseBytesWritten;
|
||||
|
||||
private readonly HttpConnectionContext _context;
|
||||
private ReusableHttpContext _httpContext;
|
||||
|
||||
protected string _methodText = null;
|
||||
private string _scheme = null;
|
||||
|
|
@ -275,6 +277,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
protected HttpResponseHeaders HttpResponseHeaders { get; } = new HttpResponseHeaders();
|
||||
|
||||
HttpContext IHttpContextContainer.HttpContext
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_httpContext is null)
|
||||
{
|
||||
_httpContext = new ReusableHttpContext(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_httpContext.Initialize(this);
|
||||
}
|
||||
|
||||
return _httpContext;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeStreams(MessageBody messageBody)
|
||||
{
|
||||
if (_streams == null)
|
||||
|
|
@ -359,6 +378,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
_responseBytesWritten = 0;
|
||||
|
||||
_httpContext?.Uninitialize();
|
||||
|
||||
OnReset();
|
||||
}
|
||||
|
||||
|
|
@ -535,14 +556,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
|
||||
InitializeStreams(messageBody);
|
||||
|
||||
var httpContext = application.CreateContext(this);
|
||||
var context = application.CreateContext(this);
|
||||
|
||||
try
|
||||
{
|
||||
KestrelEventSource.Log.RequestStart(this);
|
||||
|
||||
// Run the application code for this request
|
||||
await application.ProcessRequestAsync(httpContext);
|
||||
await application.ProcessRequestAsync(context);
|
||||
|
||||
if (!_requestAborted)
|
||||
{
|
||||
|
|
@ -609,7 +630,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
|||
await FireOnCompleted();
|
||||
}
|
||||
|
||||
application.DisposeContext(httpContext, _applicationException);
|
||||
application.DisposeContext(context, _applicationException);
|
||||
|
||||
// Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
|
||||
if (!_requestAborted && _requestRejectedException == null && !messageBody.IsEmpty)
|
||||
|
|
|
|||
Loading…
Reference in New Issue