From cc549e253c4d946d993ca80e8fd31c4ef6231386 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 6 Dec 2015 23:18:41 +0000 Subject: [PATCH] Allow feature cache to be updated+invalidated --- .../DefaultAuthenticationManager.cs | 31 ++-- .../DefaultConnectionInfo.cs | 26 ++- .../DefaultHttpContext.cs | 49 ++++-- .../DefaultHttpRequest.cs | 30 +++- .../DefaultHttpResponse.cs | 26 ++- .../DefaultWebSocketManager.cs | 24 ++- .../Features/FeatureHelpers.cs | 15 +- .../Features/IFeatureCache.cs | 1 + .../Features/QueryFeature.cs | 8 +- .../Features/RequestCookiesFeature.cs | 8 +- .../Features/ResponseCookiesFeature.cs | 8 +- .../DefaultHttpContextTests.cs | 151 ++++++++++++++++++ 12 files changed, 330 insertions(+), 47 deletions(-) diff --git a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs index 545f3d03..ba239a6c 100644 --- a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs +++ b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs @@ -14,27 +14,43 @@ namespace Microsoft.AspNet.Http.Authentication.Internal { public class DefaultAuthenticationManager : AuthenticationManager, IFeatureCache { - private readonly IFeatureCollection _features; + private IFeatureCollection _features; private int _cachedFeaturesRevision = -1; private IHttpAuthenticationFeature _authentication; - private IHttpResponseFeature _response; public DefaultAuthenticationManager(IFeatureCollection features) { _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() { if (_cachedFeaturesRevision != _features.Revision) { - _authentication = null; - _response = null; - _cachedFeaturesRevision = _features.Revision; + ResetFeatures(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + + public void UpdateFeatures(IFeatureCollection features) + { + _features = features; + ResetFeatures(); + } + + private void ResetFeatures() + { + _authentication = null; + + ((IFeatureCache)this).SetFeaturesRevision(); + } + private IHttpAuthenticationFeature HttpAuthenticationFeature { get @@ -47,11 +63,6 @@ private IHttpAuthenticationFeature HttpAuthenticationFeature } } - private IHttpResponseFeature HttpResponseFeature - { - get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } - } - public override IEnumerable GetAuthenticationSchemes() { var handler = HttpAuthenticationFeature.Handler; diff --git a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs index c0fd4234..66fdbbe9 100644 --- a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs +++ b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Http.Internal { public class DefaultConnectionInfo : ConnectionInfo, IFeatureCache { - private readonly IFeatureCollection _features; + private IFeatureCollection _features; private int _cachedFeaturesRevision = -1; private IHttpConnectionFeature _connection; @@ -21,18 +21,36 @@ public class DefaultConnectionInfo : ConnectionInfo, IFeatureCache public DefaultConnectionInfo(IFeatureCollection features) { _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() { if (_cachedFeaturesRevision != _features.Revision) { - _connection = null; - _tlsConnection = null; - _cachedFeaturesRevision = _features.Revision; + ResetFeatures(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + + public void UpdateFeatures(IFeatureCollection features) + { + _features = features; + ResetFeatures(); + } + + private void ResetFeatures() + { + _connection = null; + _tlsConnection = null; + + ((IFeatureCache)this).SetFeaturesRevision(); + } + private IHttpConnectionFeature HttpConnectionFeature { get diff --git a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs index f4c60fb9..9fcfd968 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs @@ -16,17 +16,18 @@ namespace Microsoft.AspNet.Http.Internal { public class DefaultHttpContext : HttpContext, IFeatureCache { - private readonly HttpRequest _request; - private readonly HttpResponse _response; - private ConnectionInfo _connection; - private AuthenticationManager _authenticationManager; + private readonly DefaultHttpRequest _request; + private readonly DefaultHttpResponse _response; + + 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 WebSocketManager _websockets; private IFeatureCollection _features; private int _cachedFeaturesRevision = -1; @@ -36,6 +37,7 @@ public DefaultHttpContext() { _features.Set(new HttpRequestFeature()); _features.Set(new HttpResponseFeature()); + ((IFeatureCache)this).SetFeaturesRevision(); } public DefaultHttpContext(IFeatureCollection features) @@ -43,21 +45,46 @@ 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) { - _items = null; - _serviceProviders = null; - _authentication = null; - _lifetime = null; - _session = null; - _cachedFeaturesRevision = _features.Revision; + ResetFeatures(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + + public void UpdateFeatures(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(); + } + IItemsFeature ItemsFeature { get diff --git a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index d11e75bc..caa34099 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Http.Internal public class DefaultHttpRequest : HttpRequest, IFeatureCache { private readonly DefaultHttpContext _context; - private readonly IFeatureCollection _features; + private IFeatureCollection _features; private int _cachedFeaturesRevision = -1; private IHttpRequestFeature _request; @@ -26,20 +26,38 @@ public DefaultHttpRequest(DefaultHttpContext context, IFeatureCollection feature { _context = context; _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() { if (_cachedFeaturesRevision != _features.Revision) { - _request = null; - _query = null; - _form = null; - _cookies = null; - _cachedFeaturesRevision = _features.Revision; + ResetFeatures(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + + public void UpdateFeatures(IFeatureCollection features) + { + _features = features; + ResetFeatures(); + } + + private void ResetFeatures() + { + _request = null; + _query = null; + _form = null; + _cookies = null; + + ((IFeatureCache)this).SetFeaturesRevision(); + } + private IHttpRequestFeature HttpRequestFeature { get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index f239261b..99783ce7 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Http.Internal public class DefaultHttpResponse : HttpResponse, IFeatureCache { private readonly DefaultHttpContext _context; - private readonly IFeatureCollection _features; + private IFeatureCollection _features; private int _cachedFeaturesRevision = -1; private IHttpResponseFeature _response; @@ -23,18 +23,36 @@ public DefaultHttpResponse(DefaultHttpContext context, IFeatureCollection featur { _context = context; _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() { if (_cachedFeaturesRevision != _features.Revision) { - _response = null; - _cookies = null; - _cachedFeaturesRevision = _features.Revision; + ResetFeatures(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + + public void UpdateFeatures(IFeatureCollection features) + { + _features = features; + ResetFeatures(); + } + + private void ResetFeatures() + { + _response = null; + _cookies = null; + + ((IFeatureCache)this).SetFeaturesRevision(); + } + private IHttpResponseFeature HttpResponseFeature { get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } diff --git a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs index 454b0fa6..d7b388fb 100644 --- a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs +++ b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs @@ -21,18 +21,36 @@ public class DefaultWebSocketManager : WebSocketManager, IFeatureCache public DefaultWebSocketManager(IFeatureCollection features) { _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() { if (_cachedFeaturesRevision != _features.Revision) { - _request = null; - _webSockets = null; - _cachedFeaturesRevision = _features.Revision; + ResetFeatures(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + + public void UpdateFeatures(IFeatureCollection features) + { + _features = features; + ResetFeatures(); + } + + private void ResetFeatures() + { + _request = null; + _webSockets = null; + + ((IFeatureCache)this).SetFeaturesRevision(); + } + private IHttpRequestFeature HttpRequestFeature { get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } diff --git a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs index ca6d914a..86b033fd 100644 --- a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs +++ b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs @@ -56,9 +56,10 @@ public static T GetOrCreateAndCache( if (obj == null) { obj = factory(); - cachedObject = obj; - features.Set(obj); } + cachedObject = obj; + features.Set(obj); + cache.SetFeaturesRevision(); } return obj; } @@ -79,9 +80,10 @@ public static T GetOrCreateAndCache( if (obj == null) { obj = factory(features); - cachedObject = obj; - features.Set(obj); } + cachedObject = obj; + features.Set(obj); + cache.SetFeaturesRevision(); } return obj; } @@ -103,9 +105,10 @@ public static T GetOrCreateAndCache( if (obj == null) { obj = factory(request); - cachedObject = obj; - features.Set(obj); } + 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 index 80db1ee3..14a220d8 100644 --- a/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs +++ b/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs @@ -6,5 +6,6 @@ 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 e17e6125..6d404a54 100644 --- a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs @@ -35,6 +35,7 @@ public QueryFeature(IFeatureCollection features) } _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() @@ -42,10 +43,15 @@ void IFeatureCache.CheckFeaturesRevision() if (_cachedFeaturesRevision != _features.Revision) { _request = null; - _cachedFeaturesRevision = _features.Revision; + ((IFeatureCache)this).SetFeaturesRevision(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + private IHttpRequestFeature HttpRequestFeature { get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } diff --git a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs index e759f1d2..811b3e83 100644 --- a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs @@ -37,6 +37,7 @@ public RequestCookiesFeature(IFeatureCollection features) } _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() @@ -44,10 +45,15 @@ void IFeatureCache.CheckFeaturesRevision() if (_cachedFeaturesRevision != _features.Revision) { _request = null; - _cachedFeaturesRevision = _features.Revision; + ((IFeatureCache)this).SetFeaturesRevision(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + private IHttpRequestFeature HttpRequestFeature { get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } diff --git a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs index dd7607a7..caea7e4e 100644 --- a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs @@ -16,6 +16,7 @@ public class ResponseCookiesFeature : IResponseCookiesFeature, IFeatureCache public ResponseCookiesFeature(IFeatureCollection features) { _features = features; + ((IFeatureCache)this).SetFeaturesRevision(); } void IFeatureCache.CheckFeaturesRevision() @@ -23,10 +24,15 @@ void IFeatureCache.CheckFeaturesRevision() if (_cachedFeaturesRevision != _features.Revision) { _response = null; - _cachedFeaturesRevision = _features.Revision; + ((IFeatureCache)this).SetFeaturesRevision(); } } + void IFeatureCache.SetFeaturesRevision() + { + _cachedFeaturesRevision = _features.Revision; + } + private IHttpResponseFeature HttpResponseFeature { get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs index 3d7305b8..01fbd73d 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs @@ -4,8 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.WebSockets; +using System.Reflection; using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNet.Http.Authentication.Internal; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features.Internal; using Xunit; @@ -146,6 +149,138 @@ 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()); + + var newFeatures = new FeatureCollection(); + context.UpdateFeatures(newFeatures); + + Assert.Equal(0, newFeatures.Count()); + + 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(); + 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); + + Assert.NotEqual(0, newFeatures.Count()); + } + + void TestCachedFeaturesAreNull(Type type, object value, IFeatureCollection features) + { + + var fields = type + .GetFields(BindingFlags.NonPublic | BindingFlags.Instance) + .Where(f => f.FieldType.GetTypeInfo().IsInterface); + + foreach (var field in fields) + { + if (field.FieldType == typeof(IFeatureCollection)) + { + Assert.Same(features, field.GetValue(value)); + } + else + { + Assert.Null(field.GetValue(value)); + } + } + } + + void TestCachedFeaturesAreSet(Type type, object value, IFeatureCollection features) + { + var properties = type + .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.PropertyType.GetTypeInfo().IsInterface); + + TestFeatureProperties(value, features, properties); + + var fields = type + .GetFields(BindingFlags.NonPublic | BindingFlags.Instance) + .Where(f => f.FieldType.GetTypeInfo().IsInterface); + + foreach (var field in fields) + { + if (field.FieldType == typeof(IFeatureCollection)) + { + Assert.Same(features, field.GetValue(value)); + } + else + { + var v = field.GetValue(value); + Assert.Same(features[field.FieldType], v); + Assert.NotNull(v); + } + } + + } + + private static void TestFeatureProperties(object value, IFeatureCollection features, IEnumerable properties) + { + foreach (var property in properties) + { + if (property.PropertyType == typeof(IFeatureCollection)) + { + Assert.Same(features, property.GetValue(value)); + } + else + { + if (property.Name.Contains("Feature")) + { + var v = property.GetValue(value); + Assert.Same(features[property.PropertyType], v); + Assert.NotNull(v); + } + } + } + } private HttpContext CreateContext() { @@ -195,5 +330,21 @@ private class BlahSessionFeature : ISessionFeature { public ISession Session { get; set; } } + + private class TestHttpWebSocketFeature : IHttpWebSocketFeature + { + public bool IsWebSocketRequest + { + get + { + throw new NotImplementedException(); + } + } + + public Task AcceptAsync(WebSocketAcceptContext context) + { + throw new NotImplementedException(); + } + } } } \ No newline at end of file