Skip to content

Commit 688ad19

Browse files
authored
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.
1 parent b4c9ca1 commit 688ad19

10 files changed

+687
-8
lines changed

src/Http/Http/src/HttpContextFactory.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using Microsoft.AspNetCore.Http.Features;
6+
using Microsoft.AspNetCore.Http.Internal;
67
using Microsoft.Extensions.Options;
78

89
namespace Microsoft.AspNetCore.Http
@@ -35,7 +36,7 @@ public HttpContext Create(IFeatureCollection featureCollection)
3536
throw new ArgumentNullException(nameof(featureCollection));
3637
}
3738

38-
var httpContext = new DefaultHttpContext(featureCollection);
39+
var httpContext = CreateHttpContext(featureCollection);
3940
if (_httpContextAccessor != null)
4041
{
4142
_httpContextAccessor.HttpContext = httpContext;
@@ -47,6 +48,16 @@ public HttpContext Create(IFeatureCollection featureCollection)
4748
return httpContext;
4849
}
4950

51+
private static HttpContext CreateHttpContext(IFeatureCollection featureCollection)
52+
{
53+
if (featureCollection is IHttpContextContainer container)
54+
{
55+
return container.HttpContext;
56+
}
57+
58+
return new ReusableHttpContext(featureCollection);
59+
}
60+
5061
public void Dispose(HttpContext httpContext)
5162
{
5263
if (_httpContextAccessor != null)
@@ -55,4 +66,4 @@ public void Dispose(HttpContext httpContext)
5566
}
5667
}
5768
}
58-
}
69+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Microsoft.AspNetCore.Http
6+
{
7+
public interface IHttpContextContainer
8+
{
9+
HttpContext HttpContext { get; }
10+
}
11+
}

src/Http/Http/src/Internal/DefaultHttpRequest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,4 @@ struct FeatureInterfaces
171171
public IRouteValuesFeature RouteValues;
172172
}
173173
}
174-
}
174+
}

src/Http/Http/src/Internal/DefaultHttpResponse.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,4 @@ struct FeatureInterfaces
136136
public IResponseCookiesFeature Cookies;
137137
}
138138
}
139-
}
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
using System.Security.Cryptography.X509Certificates;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Http.Features;
9+
10+
namespace Microsoft.AspNetCore.Http.Internal
11+
{
12+
public sealed class ReusableConnectionInfo : ConnectionInfo
13+
{
14+
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
15+
private readonly static Func<IFeatureCollection, IHttpConnectionFeature> _newHttpConnectionFeature = f => new HttpConnectionFeature();
16+
private readonly static Func<IFeatureCollection, ITlsConnectionFeature> _newTlsConnectionFeature = f => new TlsConnectionFeature();
17+
18+
private FeatureReferences<FeatureInterfaces> _features;
19+
20+
public ReusableConnectionInfo(IFeatureCollection features)
21+
{
22+
Initialize(features);
23+
}
24+
25+
public void Initialize(IFeatureCollection features)
26+
{
27+
_features = new FeatureReferences<FeatureInterfaces>(features);
28+
}
29+
30+
public void Uninitialize()
31+
{
32+
_features = default(FeatureReferences<FeatureInterfaces>);
33+
}
34+
35+
private IHttpConnectionFeature HttpConnectionFeature =>
36+
_features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature);
37+
38+
private ITlsConnectionFeature TlsConnectionFeature =>
39+
_features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature);
40+
41+
/// <inheritdoc />
42+
public override string Id
43+
{
44+
get { return HttpConnectionFeature.ConnectionId; }
45+
set { HttpConnectionFeature.ConnectionId = value; }
46+
}
47+
48+
public override IPAddress RemoteIpAddress
49+
{
50+
get { return HttpConnectionFeature.RemoteIpAddress; }
51+
set { HttpConnectionFeature.RemoteIpAddress = value; }
52+
}
53+
54+
public override int RemotePort
55+
{
56+
get { return HttpConnectionFeature.RemotePort; }
57+
set { HttpConnectionFeature.RemotePort = value; }
58+
}
59+
60+
public override IPAddress LocalIpAddress
61+
{
62+
get { return HttpConnectionFeature.LocalIpAddress; }
63+
set { HttpConnectionFeature.LocalIpAddress = value; }
64+
}
65+
66+
public override int LocalPort
67+
{
68+
get { return HttpConnectionFeature.LocalPort; }
69+
set { HttpConnectionFeature.LocalPort = value; }
70+
}
71+
72+
public override X509Certificate2 ClientCertificate
73+
{
74+
get { return TlsConnectionFeature.ClientCertificate; }
75+
set { TlsConnectionFeature.ClientCertificate = value; }
76+
}
77+
78+
public override Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken = default)
79+
{
80+
return TlsConnectionFeature.GetClientCertificateAsync(cancellationToken);
81+
}
82+
83+
struct FeatureInterfaces
84+
{
85+
public IHttpConnectionFeature Connection;
86+
public ITlsConnectionFeature TlsConnection;
87+
}
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Security.Claims;
4+
using System.Text;
5+
using System.Threading;
6+
using Microsoft.AspNetCore.Http.Authentication;
7+
using Microsoft.AspNetCore.Http.Features;
8+
using Microsoft.AspNetCore.Http.Features.Authentication;
9+
10+
namespace Microsoft.AspNetCore.Http.Internal
11+
{
12+
public sealed class ReusableHttpContext : HttpContext
13+
{
14+
// Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
15+
private readonly static Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
16+
private readonly static Func<IFeatureCollection, IServiceProvidersFeature> _newServiceProvidersFeature = f => new ServiceProvidersFeature();
17+
private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
18+
private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
19+
private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
20+
private readonly static Func<IFeatureCollection, ISessionFeature> _nullSessionFeature = f => null;
21+
private readonly static Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();
22+
23+
private FeatureReferences<FeatureInterfaces> _features;
24+
25+
private ReusableHttpRequest _request;
26+
private ReusableHttpResponse _response;
27+
28+
private ReusableConnectionInfo _connection;
29+
private ReusableWebSocketManager _websockets;
30+
31+
public ReusableHttpContext(IFeatureCollection features)
32+
{
33+
_features = new FeatureReferences<FeatureInterfaces>(features);
34+
_request = new ReusableHttpRequest(this);
35+
_response = new ReusableHttpResponse(this);
36+
}
37+
38+
public void Initialize(IFeatureCollection features)
39+
{
40+
_features = new FeatureReferences<FeatureInterfaces>(features);
41+
_request.Initialize(this);
42+
_response.Initialize(this);
43+
_connection?.Initialize(features);
44+
_websockets?.Initialize(features);
45+
}
46+
47+
public void Uninitialize()
48+
{
49+
_features = default;
50+
51+
_request.Uninitialize();
52+
_response.Uninitialize();
53+
_connection?.Uninitialize();
54+
_websockets?.Uninitialize();
55+
}
56+
57+
private IItemsFeature ItemsFeature =>
58+
_features.Fetch(ref _features.Cache.Items, _newItemsFeature);
59+
60+
private IServiceProvidersFeature ServiceProvidersFeature =>
61+
_features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
62+
63+
private IHttpAuthenticationFeature HttpAuthenticationFeature =>
64+
_features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
65+
66+
private IHttpRequestLifetimeFeature LifetimeFeature =>
67+
_features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature);
68+
69+
private ISessionFeature SessionFeature =>
70+
_features.Fetch(ref _features.Cache.Session, _newSessionFeature);
71+
72+
private ISessionFeature SessionFeatureOrNull =>
73+
_features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
74+
75+
76+
private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
77+
_features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature);
78+
79+
public override IFeatureCollection Features => _features.Collection;
80+
81+
public override HttpRequest Request => _request;
82+
83+
public override HttpResponse Response => _response;
84+
85+
public override ConnectionInfo Connection => _connection ?? (_connection = new ReusableConnectionInfo(_features.Collection));
86+
87+
[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.")]
88+
public override AuthenticationManager Authentication => throw new NotSupportedException();
89+
90+
public override WebSocketManager WebSockets => _websockets ?? (_websockets = new ReusableWebSocketManager(_features.Collection));
91+
92+
93+
public override ClaimsPrincipal User
94+
{
95+
get
96+
{
97+
var user = HttpAuthenticationFeature.User;
98+
if (user == null)
99+
{
100+
user = new ClaimsPrincipal(new ClaimsIdentity());
101+
HttpAuthenticationFeature.User = user;
102+
}
103+
return user;
104+
}
105+
set { HttpAuthenticationFeature.User = value; }
106+
}
107+
108+
public override IDictionary<object, object> Items
109+
{
110+
get { return ItemsFeature.Items; }
111+
set { ItemsFeature.Items = value; }
112+
}
113+
114+
public override IServiceProvider RequestServices
115+
{
116+
get { return ServiceProvidersFeature.RequestServices; }
117+
set { ServiceProvidersFeature.RequestServices = value; }
118+
}
119+
120+
public override CancellationToken RequestAborted
121+
{
122+
get { return LifetimeFeature.RequestAborted; }
123+
set { LifetimeFeature.RequestAborted = value; }
124+
}
125+
126+
public override string TraceIdentifier
127+
{
128+
get { return RequestIdentifierFeature.TraceIdentifier; }
129+
set { RequestIdentifierFeature.TraceIdentifier = value; }
130+
}
131+
132+
public override ISession Session
133+
{
134+
get
135+
{
136+
var feature = SessionFeatureOrNull;
137+
if (feature == null)
138+
{
139+
throw new InvalidOperationException("Session has not been configured for this application " +
140+
"or request.");
141+
}
142+
return feature.Session;
143+
}
144+
set
145+
{
146+
SessionFeature.Session = value;
147+
}
148+
}
149+
150+
public override void Abort()
151+
{
152+
LifetimeFeature.Abort();
153+
}
154+
155+
struct FeatureInterfaces
156+
{
157+
public IItemsFeature Items;
158+
public IServiceProvidersFeature ServiceProviders;
159+
public IHttpAuthenticationFeature Authentication;
160+
public IHttpRequestLifetimeFeature Lifetime;
161+
public ISessionFeature Session;
162+
public IHttpRequestIdentifierFeature RequestIdentifier;
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)