diff --git a/samples/SampleApp/PooledHttpContext.cs b/samples/SampleApp/PooledHttpContext.cs new file mode 100644 index 00000000..7736085e --- /dev/null +++ b/samples/SampleApp/PooledHttpContext.cs @@ -0,0 +1,54 @@ +// 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.AspNet.Http; +using Microsoft.AspNet.Http.Features; +using Microsoft.AspNet.Http.Internal; + +namespace SampleApp +{ + public class PooledHttpContext : DefaultHttpContext + { + DefaultHttpRequest _pooledHttpRequest; + DefaultHttpResponse _pooledHttpResponse; + + public PooledHttpContext(IFeatureCollection featureCollection) : + base(featureCollection) + { + } + + protected override HttpRequest InitializeHttpRequest() + { + if (_pooledHttpRequest != null) + { + _pooledHttpRequest.Initialize(this, Features); + return _pooledHttpRequest; + } + + return new DefaultHttpRequest(this, Features); + } + + protected override void UninitializeHttpRequest(HttpRequest instance) + { + _pooledHttpRequest = instance as DefaultHttpRequest; + _pooledHttpRequest?.Uninitialize(); + } + + protected override HttpResponse InitializeHttpResponse() + { + if (_pooledHttpResponse != null) + { + _pooledHttpResponse.Initialize(this, Features); + return _pooledHttpResponse; + } + + return new DefaultHttpResponse(this, Features); + } + + protected override void UninitializeHttpResponse(HttpResponse instance) + { + _pooledHttpResponse = instance as DefaultHttpResponse; + _pooledHttpResponse?.Uninitialize(); + } + } +} \ No newline at end of file diff --git a/samples/SampleApp/PooledHttpContextFactory.cs b/samples/SampleApp/PooledHttpContextFactory.cs new file mode 100644 index 00000000..62955d90 --- /dev/null +++ b/samples/SampleApp/PooledHttpContextFactory.cs @@ -0,0 +1,65 @@ +// 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 System.Collections.Generic; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Features; + +namespace SampleApp +{ + public class PooledHttpContextFactory : IHttpContextFactory + { + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly Stack _pool = new Stack(); + + public PooledHttpContextFactory(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public HttpContext Create(IFeatureCollection featureCollection) + { + PooledHttpContext httpContext = null; + lock (_pool) + { + if (_pool.Count != 0) + { + httpContext = _pool.Pop(); + } + } + + if (httpContext == null) + { + httpContext = new PooledHttpContext(featureCollection); + } + else + { + httpContext.Initialize(featureCollection); + } + + if (_httpContextAccessor != null) + { + _httpContextAccessor.HttpContext = httpContext; + } + return httpContext; + } + + public void Dispose(HttpContext httpContext) + { + if (_httpContextAccessor != null) + { + _httpContextAccessor.HttpContext = null; + } + + var pooled = httpContext as PooledHttpContext; + if (pooled != null) + { + pooled.Uninitialize(); + lock (_pool) + { + _pool.Push(pooled); + } + } + } + } +} diff --git a/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs b/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs new file mode 100644 index 00000000..30d7c363 --- /dev/null +++ b/src/Microsoft.AspNet.Http.Features/FeatureReferences.cs @@ -0,0 +1,62 @@ +// 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 System; + +namespace Microsoft.AspNet.Http.Features +{ + public struct FeatureReferences + { + public FeatureReferences(IFeatureCollection collection) + { + Collection = collection; + Cache = default(TCache); + Revision = collection.Revision; + } + + public IFeatureCollection Collection { get; private set; } + public int Revision { get; private set; } + + // cache is a public field because the code calling Fetch must + // be able to pass ref values that "dot through" the TCache struct memory, + // if it was a Property then that getter would return a copy of the memory + // preventing the use of "ref" + public TCache Cache; + + public TFeature Fetch( + ref TFeature cached, + TState state, + Func factory) + { + var cleared = false; + if (Revision != Collection.Revision) + { + cleared = true; + Cache = default(TCache); + Revision = Collection.Revision; + } + + var feature = cached; + if (feature == null) + { + feature = Collection.Get(); + if (feature == null) + { + feature = factory(state); + + Collection.Set(feature); + if (!cleared) + { + Cache = default(TCache); + } + Revision = Collection.Revision; + } + cached = feature; + } + return feature; + } + + public TFeature Fetch(ref TFeature cached, Func factory) => + Fetch(ref cached, Collection, factory); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs index ba239a6c..0c8db874 100644 --- a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs +++ b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs @@ -12,56 +12,27 @@ namespace Microsoft.AspNet.Http.Authentication.Internal { - public class DefaultAuthenticationManager : AuthenticationManager, IFeatureCache + public class DefaultAuthenticationManager : AuthenticationManager { - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpAuthenticationFeature _authentication; + private FeatureReferences _features; public DefaultAuthenticationManager(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; + Initialize(features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Initialize(IFeatureCollection features) { - _features = features; - ResetFeatures(); + _features = new FeatureReferences(features); } - private void ResetFeatures() + public virtual void Uninitialize() { - _authentication = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _features = default(FeatureReferences); } - private IHttpAuthenticationFeature HttpAuthenticationFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpAuthenticationFeature(), - ref _authentication); - } - } + private IHttpAuthenticationFeature HttpAuthenticationFeature => + _features.Fetch(ref _features.Cache, f => new HttpAuthenticationFeature()); public override IEnumerable GetAuthenticationSchemes() { diff --git a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs index 66fdbbe9..dd19f7d2 100644 --- a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs +++ b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs @@ -10,70 +10,30 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultConnectionInfo : ConnectionInfo, IFeatureCache + public class DefaultConnectionInfo : ConnectionInfo { - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpConnectionFeature _connection; - private ITlsConnectionFeature _tlsConnection; + private FeatureReferences _features; public DefaultConnectionInfo(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } + Initialize(features); } - void IFeatureCache.SetFeaturesRevision() + public virtual void Initialize( IFeatureCollection features) { - _cachedFeaturesRevision = _features.Revision; + _features = new FeatureReferences(features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Uninitialize() { - _features = features; - ResetFeatures(); + _features = default(FeatureReferences); } - private void ResetFeatures() - { - _connection = null; - _tlsConnection = null; + private IHttpConnectionFeature HttpConnectionFeature => + _features.Fetch(ref _features.Cache.Connection, f => new HttpConnectionFeature()); - ((IFeatureCache)this).SetFeaturesRevision(); - } - - private IHttpConnectionFeature HttpConnectionFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpConnectionFeature(), - ref _connection); - } - } - - private ITlsConnectionFeature TlsConnectionFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new TlsConnectionFeature(), - ref _tlsConnection); - } - } + private ITlsConnectionFeature TlsConnectionFeature=> + _features.Fetch(ref _features.Cache.TlsConnection, f => new TlsConnectionFeature()); public override IPAddress RemoteIpAddress { @@ -115,5 +75,11 @@ public override X509Certificate2 ClientCertificate { return TlsConnectionFeature.GetClientCertificateAsync(cancellationToken); } + + struct FeatureInterfaces + { + public IHttpConnectionFeature Connection; + public ITlsConnectionFeature TlsConnection; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs index 9fcfd968..9cc4211c 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs @@ -14,173 +14,99 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpContext : HttpContext, IFeatureCache + public class DefaultHttpContext : HttpContext { - private readonly DefaultHttpRequest _request; - private readonly DefaultHttpResponse _response; + private FeatureReferences _features; - private DefaultAuthenticationManager _authenticationManager; - private DefaultConnectionInfo _connection; - private DefaultWebSocketManager _websockets; - - private IItemsFeature _items; - private IServiceProvidersFeature _serviceProviders; - private IHttpAuthenticationFeature _authentication; - private IHttpRequestLifetimeFeature _lifetime; - private ISessionFeature _session; - - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; + private HttpRequest _request; + private HttpResponse _response; + private AuthenticationManager _authenticationManager; + private ConnectionInfo _connection; + private WebSocketManager _websockets; public DefaultHttpContext() : this(new FeatureCollection()) { - _features.Set(new HttpRequestFeature()); - _features.Set(new HttpResponseFeature()); - ((IFeatureCache)this).SetFeaturesRevision(); + Features.Set(new HttpRequestFeature()); + Features.Set(new HttpResponseFeature()); } public DefaultHttpContext(IFeatureCollection features) { - _features = features; - _request = new DefaultHttpRequest(this, features); - _response = new DefaultHttpResponse(this, features); - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision !=_features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; + Initialize(features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Initialize(IFeatureCollection features) { - _features = features; - ResetFeatures(); - - _request.UpdateFeatures(features); - _response.UpdateFeatures(features); - - _authenticationManager?.UpdateFeatures(features); - _connection?.UpdateFeatures(features); - _websockets?.UpdateFeatures(features); - } - - private void ResetFeatures() - { - _items = null; - _serviceProviders = null; - _authentication = null; - _lifetime = null; - _session = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _features = new FeatureReferences(features); + _request = InitializeHttpRequest(); + _response = InitializeHttpResponse(); } - IItemsFeature ItemsFeature + public virtual void Uninitialize() { - get + _features = default(FeatureReferences); + if (_request != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new ItemsFeature(), - ref _items); + UninitializeHttpRequest(_request); + _request = null; } - } - - IServiceProvidersFeature ServiceProvidersFeature - { - get + if (_response != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new ServiceProvidersFeature(), - ref _serviceProviders); + UninitializeHttpResponse(_response); + _response = null; } - } - - private IHttpAuthenticationFeature HttpAuthenticationFeature - { - get + if (_authenticationManager != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpAuthenticationFeature(), - ref _authentication); + UninitializeAuthenticationManager(_authenticationManager); + _authenticationManager = null; } - } - - private IHttpRequestLifetimeFeature LifetimeFeature - { - get + if (_connection != null) { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - () => new HttpRequestLifetimeFeature(), - ref _lifetime); + UninitializeConnectionInfo(_connection); + _connection = null; } - } - - private ISessionFeature SessionFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _session); } - set + if (_websockets != null) { - _features.Set(value); - _session = value; + UninitializeWebSocketManager(_websockets); + _websockets = null; } } + + private IItemsFeature ItemsFeature => + _features.Fetch(ref _features.Cache.Items, f => new ItemsFeature()); - private IHttpRequestIdentifierFeature RequestIdentifierFeature - { - get { - return FeatureHelpers.GetOrCreate( - _features, - () => new HttpRequestIdentifierFeature()); - } - } + private IServiceProvidersFeature ServiceProvidersFeature => + _features.Fetch(ref _features.Cache.ServiceProviders, f => new ServiceProvidersFeature()); - public override IFeatureCollection Features { get { return _features; } } + private IHttpAuthenticationFeature HttpAuthenticationFeature => + _features.Fetch(ref _features.Cache.Authentication, f => new HttpAuthenticationFeature()); - public override HttpRequest Request { get { return _request; } } + private IHttpRequestLifetimeFeature LifetimeFeature => + _features.Fetch(ref _features.Cache.Lifetime, f => new HttpRequestLifetimeFeature()); - public override HttpResponse Response { get { return _response; } } + private ISessionFeature SessionFeature => + _features.Fetch(ref _features.Cache.Session, f => new DefaultSessionFeature()); - public override ConnectionInfo Connection - { - get - { - if (_connection == null) - { - _connection = new DefaultConnectionInfo(_features); - } - return _connection; - } - } + private ISessionFeature SessionFeatureOrNull => + _features.Fetch(ref _features.Cache.Session, f => null); + + + private IHttpRequestIdentifierFeature RequestIdentifierFeature => + _features.Fetch(ref _features.Cache.RequestIdentifier, f => new HttpRequestIdentifierFeature()); + + public override IFeatureCollection Features => _features.Collection; + + public override HttpRequest Request => _request; + + public override HttpResponse Response => _response; + + public override ConnectionInfo Connection => _connection ?? (_connection = InitializeConnectionInfo()); + + public override AuthenticationManager Authentication => _authenticationManager ?? (_authenticationManager = InitializeAuthenticationManager()); + + public override WebSocketManager WebSockets => _websockets ?? (_websockets = InitializeWebSocketManager()); - public override AuthenticationManager Authentication - { - get - { - if (_authenticationManager == null) - { - _authenticationManager = new DefaultAuthenticationManager(_features); - } - return _authenticationManager; - } - } public override ClaimsPrincipal User { @@ -225,7 +151,7 @@ public override ISession Session { get { - var feature = SessionFeature; + var feature = SessionFeatureOrNull; if (feature == null) { throw new InvalidOperationException("Session has not been configured for this application " + @@ -235,31 +161,41 @@ public override ISession Session } set { - var feature = SessionFeature; - if (feature == null) - { - feature = new DefaultSessionFeature(); - SessionFeature = feature; - } - feature.Session = value; + SessionFeature.Session = value; } } - public override WebSocketManager WebSockets - { - get - { - if (_websockets == null) - { - _websockets = new DefaultWebSocketManager(_features); - } - return _websockets; - } - } + public override void Abort() { LifetimeFeature.Abort(); } + + + protected virtual HttpRequest InitializeHttpRequest() => new DefaultHttpRequest(this, Features); + protected virtual void UninitializeHttpRequest(HttpRequest instance) { } + + protected virtual HttpResponse InitializeHttpResponse() => new DefaultHttpResponse(this, Features); + protected virtual void UninitializeHttpResponse(HttpResponse instance) { } + + protected virtual ConnectionInfo InitializeConnectionInfo() => new DefaultConnectionInfo(Features); + protected virtual void UninitializeConnectionInfo(ConnectionInfo instance) { } + + protected virtual AuthenticationManager InitializeAuthenticationManager() => new DefaultAuthenticationManager(Features); + protected virtual void UninitializeAuthenticationManager(AuthenticationManager instance) { } + + protected virtual WebSocketManager InitializeWebSocketManager() => new DefaultWebSocketManager(Features); + protected virtual void UninitializeWebSocketManager(WebSocketManager instance) { } + + struct FeatureInterfaces + { + public IItemsFeature Items; + public IServiceProvidersFeature ServiceProviders; + public IHttpAuthenticationFeature Authentication; + public IHttpRequestLifetimeFeature Lifetime; + public ISessionFeature Session; + public IHttpRequestIdentifierFeature RequestIdentifier; + } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index caa34099..5d3a2875 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -11,96 +11,41 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpRequest : HttpRequest, IFeatureCache + public class DefaultHttpRequest : HttpRequest { - private readonly DefaultHttpContext _context; - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; + private HttpContext _context; + private FeatureReferences _features; - private IHttpRequestFeature _request; - private IQueryFeature _query; - private IFormFeature _form; - private IRequestCookiesFeature _cookies; - - public DefaultHttpRequest(DefaultHttpContext context, IFeatureCollection features) - { - _context = context; - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() + public DefaultHttpRequest(HttpContext context, IFeatureCollection features) { - _cachedFeaturesRevision = _features.Revision; + Initialize(context, features); } - public void UpdateFeatures(IFeatureCollection features) + public virtual void Initialize(HttpContext context, IFeatureCollection features) { - _features = features; - ResetFeatures(); + _context = context; + _features = new FeatureReferences(features); } - private void ResetFeatures() + public virtual void Uninitialize() { - _request = null; - _query = null; - _form = null; - _cookies = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _context = null; + _features = default(FeatureReferences); } - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } + public override HttpContext HttpContext => _context; - private IQueryFeature QueryFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - (f) => new QueryFeature(f), - ref _query); - } - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache.Request, f => null); - private IFormFeature FormFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - this, - (r) => new FormFeature(r), - ref _form); - } - } + private IQueryFeature QueryFeature => + _features.Fetch(ref _features.Cache.Query, f => new QueryFeature(f)); - private IRequestCookiesFeature RequestCookiesFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - (f) => new RequestCookiesFeature(f), - ref _cookies); - } - } + private IFormFeature FormFeature => + _features.Fetch(ref _features.Cache.Form, this, f => new FormFeature(f)); - public override HttpContext HttpContext { get { return _context; } } + private IRequestCookiesFeature RequestCookiesFeature => + _features.Fetch(ref _features.Cache.Cookies, f => new RequestCookiesFeature(f)); public override PathString PathBase { @@ -206,5 +151,13 @@ public override Task ReadFormAsync(CancellationToken cancellati { return FormFeature.ReadFormAsync(cancellationToken); } + + struct FeatureInterfaces + { + public IHttpRequestFeature Request; + public IQueryFeature Query; + public IFormFeature Form; + public IRequestCookiesFeature Cookies; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index 99783ce7..a9cb49cb 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -10,65 +10,34 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpResponse : HttpResponse, IFeatureCache + public class DefaultHttpResponse : HttpResponse { - private readonly DefaultHttpContext _context; - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; + private HttpContext _context; + private FeatureReferences _features; - private IHttpResponseFeature _response; - private IResponseCookiesFeature _cookies; - - public DefaultHttpResponse(DefaultHttpContext context, IFeatureCollection features) + public DefaultHttpResponse(HttpContext context, IFeatureCollection features) { - _context = context; - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); + Initialize(context, features); } - void IFeatureCache.CheckFeaturesRevision() + public virtual void Initialize(HttpContext context, IFeatureCollection features) { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; - } - - public void UpdateFeatures(IFeatureCollection features) - { - _features = features; - ResetFeatures(); + _context = context; + _features = new FeatureReferences(features); } - private void ResetFeatures() + public virtual void Uninitialize() { - _response = null; - _cookies = null; - - ((IFeatureCache)this).SetFeaturesRevision(); + _context = null; + _features = default(FeatureReferences); } - private IHttpResponseFeature HttpResponseFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } - } + private IHttpResponseFeature HttpResponseFeature => + _features.Fetch(ref _features.Cache.Response, f => null); - private IResponseCookiesFeature ResponseCookiesFeature - { - get - { - return FeatureHelpers.GetOrCreateAndCache( - this, - _features, - (f) => new ResponseCookiesFeature(f), - ref _cookies); - } - } + private IResponseCookiesFeature ResponseCookiesFeature => + _features.Fetch(ref _features.Cache.Cookies, f => new ResponseCookiesFeature(f)); + public override HttpContext HttpContext { get { return _context; } } @@ -163,5 +132,11 @@ public override void Redirect(string location, bool permanent) Headers[HeaderNames.Location] = location; } + + struct FeatureInterfaces + { + public IHttpResponseFeature Response; + public IResponseCookiesFeature Cookies; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs index d7b388fb..8beff4db 100644 --- a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs +++ b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs @@ -10,56 +10,30 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultWebSocketManager : WebSocketManager, IFeatureCache + public class DefaultWebSocketManager : WebSocketManager { - private IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpRequestFeature _request; - private IHttpWebSocketFeature _webSockets; + private FeatureReferences _features; public DefaultWebSocketManager(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); + Initialize(features); } - void IFeatureCache.CheckFeaturesRevision() + public virtual void Initialize(IFeatureCollection features) { - if (_cachedFeaturesRevision != _features.Revision) - { - ResetFeatures(); - } + _features = new FeatureReferences(features); } - void IFeatureCache.SetFeaturesRevision() + public virtual void Uninitialize() { - _cachedFeaturesRevision = _features.Revision; + _features = default(FeatureReferences); } - public void UpdateFeatures(IFeatureCollection features) - { - _features = features; - ResetFeatures(); - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache.Request, f => null); - private void ResetFeatures() - { - _request = null; - _webSockets = null; - - ((IFeatureCache)this).SetFeaturesRevision(); - } - - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } - - private IHttpWebSocketFeature WebSocketFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _webSockets); } - } + private IHttpWebSocketFeature WebSocketFeature => + _features.Fetch(ref _features.Cache.WebSockets, f => null); public override bool IsWebSocketRequest { @@ -85,5 +59,11 @@ public override Task AcceptWebSocketAsync(string subProtocol) } return WebSocketFeature.AcceptAsync(new WebSocketAcceptContext() { SubProtocol = subProtocol }); } + + struct FeatureInterfaces + { + public IHttpRequestFeature Request; + public IHttpWebSocketFeature WebSockets; + } } } diff --git a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs deleted file mode 100644 index 86b033fd..00000000 --- a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs +++ /dev/null @@ -1,116 +0,0 @@ -// 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 System; - -namespace Microsoft.AspNet.Http.Features -{ - internal static class FeatureHelpers - { - public static T GetAndCache( - IFeatureCache cache, - IFeatureCollection features, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - cachedObject = obj; - } - return obj; - } - - public static T GetOrCreate( - IFeatureCollection features, - Func factory) - where T : class - { - T obj = features.Get(); - if (obj == null) - { - obj = factory(); - features.Set(obj); - } - - return obj; - } - - - public static T GetOrCreateAndCache( - IFeatureCache cache, - IFeatureCollection features, - Func factory, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - if (obj == null) - { - obj = factory(); - } - cachedObject = obj; - features.Set(obj); - cache.SetFeaturesRevision(); - } - return obj; - } - - public static T GetOrCreateAndCache( - IFeatureCache cache, - IFeatureCollection features, - Func factory, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - if (obj == null) - { - obj = factory(features); - } - cachedObject = obj; - features.Set(obj); - cache.SetFeaturesRevision(); - } - return obj; - } - - public static T GetOrCreateAndCache( - IFeatureCache cache, - IFeatureCollection features, - HttpRequest request, - Func factory, - ref T cachedObject) - where T : class - { - cache.CheckFeaturesRevision(); - - T obj = cachedObject; - if (obj == null) - { - obj = features.Get(); - if (obj == null) - { - obj = factory(request); - } - cachedObject = obj; - features.Set(obj); - cache.SetFeaturesRevision(); - } - return obj; - } - } -} diff --git a/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs b/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs deleted file mode 100644 index 14a220d8..00000000 --- a/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs +++ /dev/null @@ -1,11 +0,0 @@ -// 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.AspNet.Http.Features -{ - internal interface IFeatureCache - { - void CheckFeaturesRevision(); - void SetFeaturesRevision(); - } -} diff --git a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs index 6d404a54..73450ad5 100644 --- a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs @@ -7,12 +7,9 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class QueryFeature : IQueryFeature, IFeatureCache + public class QueryFeature : IQueryFeature { - private readonly IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpRequestFeature _request; + private FeatureReferences _features; private string _original; private IQueryCollection _parsedValues; @@ -34,34 +31,17 @@ public QueryFeature(IFeatureCollection features) throw new ArgumentNullException(nameof(features)); } - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - _request = null; - ((IFeatureCache)this).SetFeaturesRevision(); - } + _features = new FeatureReferences(features); } - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; - } - - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache, f => null); public IQueryCollection Query { get { - if (_features == null) + if (_features.Collection == null) { if (_parsedValues == null) { @@ -91,7 +71,7 @@ public IQueryCollection Query set { _parsedValues = value; - if (_features != null) + if (_features.Collection != null) { if (value == null) { diff --git a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs index 811b3e83..4fb087e9 100644 --- a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs @@ -9,13 +9,9 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class RequestCookiesFeature : IRequestCookiesFeature, IFeatureCache + public class RequestCookiesFeature : IRequestCookiesFeature { - private readonly IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpRequestFeature _request; - + private FeatureReferences _features; private StringValues _original; private IRequestCookieCollection _parsedValues; @@ -36,34 +32,17 @@ public RequestCookiesFeature(IFeatureCollection features) throw new ArgumentNullException(nameof(features)); } - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - _request = null; - ((IFeatureCache)this).SetFeaturesRevision(); - } - } - - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; + _features = new FeatureReferences(features); } - private IHttpRequestFeature HttpRequestFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } - } + private IHttpRequestFeature HttpRequestFeature => + _features.Fetch(ref _features.Cache, f => null); public IRequestCookieCollection Cookies { get { - if (_features == null) + if (_features.Collection == null) { if (_parsedValues == null) { @@ -91,7 +70,7 @@ public IRequestCookieCollection Cookies { _parsedValues = value; _original = StringValues.Empty; - if (_features != null) + if (_features.Collection != null) { if (_parsedValues == null || _parsedValues.Count == 0) { diff --git a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs index caea7e4e..0c87dc28 100644 --- a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs @@ -5,38 +5,18 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class ResponseCookiesFeature : IResponseCookiesFeature, IFeatureCache + public class ResponseCookiesFeature : IResponseCookiesFeature { - private readonly IFeatureCollection _features; - private int _cachedFeaturesRevision = -1; - - private IHttpResponseFeature _response; + private FeatureReferences _features; private IResponseCookies _cookiesCollection; public ResponseCookiesFeature(IFeatureCollection features) { - _features = features; - ((IFeatureCache)this).SetFeaturesRevision(); - } - - void IFeatureCache.CheckFeaturesRevision() - { - if (_cachedFeaturesRevision != _features.Revision) - { - _response = null; - ((IFeatureCache)this).SetFeaturesRevision(); - } + _features = new FeatureReferences(features); } - void IFeatureCache.SetFeaturesRevision() - { - _cachedFeaturesRevision = _features.Revision; - } - - private IHttpResponseFeature HttpResponseFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } - } + private IHttpResponseFeature HttpResponseFeature => + _features.Fetch(ref _features.Cache, f => null); public IResponseCookies Cookies { diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs index 01fbd73d..74bda1a6 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs @@ -149,93 +149,88 @@ public void SetItems_NewCollectionUsed() items["foo"] = item; Assert.Same(item, context.Items["foo"]); } - + [Fact] public void UpdateFeatures_ClearsCachedFeatures() { var features = new FeatureCollection(); - - var context = new DefaultHttpContext(features); - - var request = (DefaultHttpRequest)context.Request; - var response = (DefaultHttpResponse)context.Response; - - var authentication = (DefaultAuthenticationManager)context.Authentication; - var connection = (DefaultConnectionInfo)context.Connection; - var websockets = (DefaultWebSocketManager)context.WebSockets; - - Assert.Equal(0, features.Count()); - - TestCachedFeaturesAreNull(context.GetType(), context, features); - TestCachedFeaturesAreNull(request.GetType(), request, features); - TestCachedFeaturesAreNull(response.GetType(), response, features); - TestCachedFeaturesAreNull(authentication.GetType(), authentication, features); - TestCachedFeaturesAreNull(connection.GetType(), connection, features); - TestCachedFeaturesAreNull(websockets.GetType(), websockets, features); - - context.Session = new TestSession(); features.Set(new HttpRequestFeature()); features.Set(new HttpResponseFeature()); features.Set(new TestHttpWebSocketFeature()); - TestCachedFeaturesAreSet(context.GetType(), context, features); - TestCachedFeaturesAreSet(request.GetType(), request, features); - TestCachedFeaturesAreSet(response.GetType(), response, features); - TestCachedFeaturesAreSet(authentication.GetType(), authentication, features); - TestCachedFeaturesAreSet(connection.GetType(), connection, features); - TestCachedFeaturesAreSet(websockets.GetType(), websockets, features); - - Assert.NotEqual(0, features.Count()); + // featurecollection is set. all cached interfaces are null. + var context = new DefaultHttpContext(features); + TestAllCachedFeaturesAreNull(context, features); + Assert.Equal(3, features.Count()); - var newFeatures = new FeatureCollection(); - context.UpdateFeatures(newFeatures); + // getting feature properties populates feature collection with defaults + TestAllCachedFeaturesAreSet(context, features); + Assert.NotEqual(3, features.Count()); - Assert.Equal(0, newFeatures.Count()); + // featurecollection is null. and all cached interfaces are null. + // only top level is tested because child objects are inaccessible. + context.Uninitialize(); + TestCachedFeaturesAreNull(context, null); - TestCachedFeaturesAreNull(context.GetType(), context, newFeatures); - TestCachedFeaturesAreNull(request.GetType(), request, newFeatures); - TestCachedFeaturesAreNull(response.GetType(), response, newFeatures); - TestCachedFeaturesAreNull(authentication.GetType(), authentication, newFeatures); - TestCachedFeaturesAreNull(connection.GetType(), connection, newFeatures); - TestCachedFeaturesAreNull(websockets.GetType(), websockets, newFeatures); - context.Session = new TestSession(); + var newFeatures = new FeatureCollection(); newFeatures.Set(new HttpRequestFeature()); newFeatures.Set(new HttpResponseFeature()); newFeatures.Set(new TestHttpWebSocketFeature()); - TestCachedFeaturesAreSet(context.GetType(), context, newFeatures); - TestCachedFeaturesAreSet(request.GetType(), request, newFeatures); - TestCachedFeaturesAreSet(response.GetType(), response, newFeatures); - TestCachedFeaturesAreSet(authentication.GetType(), authentication, newFeatures); - TestCachedFeaturesAreSet(connection.GetType(), connection, newFeatures); - TestCachedFeaturesAreSet(websockets.GetType(), websockets, newFeatures); + // featurecollection is set to newFeatures. all cached interfaces are null. + context.Initialize(newFeatures); + TestAllCachedFeaturesAreNull(context, newFeatures); + Assert.Equal(3, newFeatures.Count()); - Assert.NotEqual(0, newFeatures.Count()); + // getting feature properties populates new feature collection with defaults + TestAllCachedFeaturesAreSet(context, newFeatures); + Assert.NotEqual(3, newFeatures.Count()); } - void TestCachedFeaturesAreNull(Type type, object value, IFeatureCollection features) + void TestAllCachedFeaturesAreNull(HttpContext context, IFeatureCollection features) { + TestCachedFeaturesAreNull(context, features); + TestCachedFeaturesAreNull(context.Request, features); + TestCachedFeaturesAreNull(context.Response, features); + TestCachedFeaturesAreNull(context.Authentication, features); + TestCachedFeaturesAreNull(context.Connection, features); + TestCachedFeaturesAreNull(context.WebSockets, features); + } - var fields = type + void TestCachedFeaturesAreNull(object value, IFeatureCollection features) + { + var type = value.GetType(); + + var field = type .GetFields(BindingFlags.NonPublic | BindingFlags.Instance) - .Where(f => f.FieldType.GetTypeInfo().IsInterface); + .Single(f => + f.FieldType.GetTypeInfo().IsGenericType && + f.FieldType.GetGenericTypeDefinition() == typeof(FeatureReferences<>)); - foreach (var field in fields) - { - if (field.FieldType == typeof(IFeatureCollection)) - { - Assert.Same(features, field.GetValue(value)); - } - else - { - Assert.Null(field.GetValue(value)); - } - } + var boxedExpectedStruct = features == null ? + Activator.CreateInstance(field.FieldType) : + Activator.CreateInstance(field.FieldType, features); + + var boxedActualStruct = field.GetValue(value); + + Assert.Equal(boxedExpectedStruct, boxedActualStruct); } - void TestCachedFeaturesAreSet(Type type, object value, IFeatureCollection features) + void TestAllCachedFeaturesAreSet(HttpContext context, IFeatureCollection features) { + TestCachedFeaturesAreSet(context, features); + TestCachedFeaturesAreSet(context.Request, features); + TestCachedFeaturesAreSet(context.Response, features); + TestCachedFeaturesAreSet(context.Authentication, features); + TestCachedFeaturesAreSet(context.Connection, features); + TestCachedFeaturesAreSet(context.WebSockets, features); + } + + void TestCachedFeaturesAreSet(object value, IFeatureCollection features) + { + var type = value.GetType(); + var properties = type .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType.GetTypeInfo().IsInterface);