diff --git a/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs b/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs index 8029c686..3011b168 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs @@ -36,6 +36,8 @@ public abstract class HttpContext : IDisposable public abstract ISession Session { get; set; } + public abstract string TraceIdentifier { get; set; } + public abstract void Abort(); public abstract void Dispose(); diff --git a/src/Microsoft.AspNet.Http.Features/FeatureReference.cs b/src/Microsoft.AspNet.Http.Features/FeatureReference.cs deleted file mode 100644 index 538297a3..00000000 --- a/src/Microsoft.AspNet.Http.Features/FeatureReference.cs +++ /dev/null @@ -1,38 +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 -{ - public struct FeatureReference - { - private T _feature; - private int _revision; - - private FeatureReference(T feature, int revision) - { - _feature = feature; - _revision = revision; - } - - public static readonly FeatureReference Default = new FeatureReference(default(T), -1); - - public T Fetch(IFeatureCollection features) - { - if (_revision == features.Revision) - { - return _feature; - } - _feature = (T)features[typeof(T)]; - _revision = features.Revision; - return _feature; - } - - public T Update(IFeatureCollection features, T feature) - { - features[typeof(T)] = feature; - _feature = feature; - _revision = features.Revision; - return feature; - } - } -} \ 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 4dcb8191..e138f98f 100644 --- a/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs +++ b/src/Microsoft.AspNet.Http/Authentication/DefaultAuthenticationManager.cs @@ -12,25 +12,44 @@ namespace Microsoft.AspNet.Http.Authentication.Internal { - public class DefaultAuthenticationManager : AuthenticationManager + public class DefaultAuthenticationManager : AuthenticationManager, IFeatureCache { private readonly IFeatureCollection _features; - private FeatureReference _authentication = FeatureReference.Default; - private FeatureReference _response = FeatureReference.Default; + private int _cachedFeaturesRevision = -1; + + private IHttpAuthenticationFeature _authentication; + private IHttpResponseFeature _response; public DefaultAuthenticationManager(IFeatureCollection features) { _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _authentication = null; + _response = null; + _cachedFeaturesRevision = _features.Revision; + } + } + private IHttpAuthenticationFeature HttpAuthenticationFeature { - get { return _authentication.Fetch(_features) ?? _authentication.Update(_features, new HttpAuthenticationFeature()); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + () => new HttpAuthenticationFeature(), + ref _authentication); + } } private IHttpResponseFeature HttpResponseFeature { - get { return _response.Fetch(_features); } + get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } } public override IEnumerable GetAuthenticationSchemes() diff --git a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs index 30a7ace3..c0fd4234 100644 --- a/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs +++ b/src/Microsoft.AspNet.Http/DefaultConnectionInfo.cs @@ -10,26 +10,51 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultConnectionInfo : ConnectionInfo + public class DefaultConnectionInfo : ConnectionInfo, IFeatureCache { private readonly IFeatureCollection _features; + private int _cachedFeaturesRevision = -1; - private FeatureReference _connection = FeatureReference.Default; - private FeatureReference _tlsConnection = FeatureReference.Default; + private IHttpConnectionFeature _connection; + private ITlsConnectionFeature _tlsConnection; public DefaultConnectionInfo(IFeatureCollection features) { _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _connection = null; + _tlsConnection = null; + _cachedFeaturesRevision = _features.Revision; + } + } + private IHttpConnectionFeature HttpConnectionFeature { - get { return _connection.Fetch(_features) ?? _connection.Update(_features, new HttpConnectionFeature()); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + () => new HttpConnectionFeature(), + ref _connection); + } } private ITlsConnectionFeature TlsConnectionFeature { - get { return _tlsConnection.Fetch(_features) ?? _tlsConnection.Update(_features, new TlsConnectionFeature()); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + () => new TlsConnectionFeature(), + ref _tlsConnection); + } } public override IPAddress RemoteIpAddress diff --git a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs index db0a4d27..151627eb 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs @@ -14,20 +14,22 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpContext : HttpContext + public class DefaultHttpContext : HttpContext, IFeatureCache { private readonly HttpRequest _request; private readonly HttpResponse _response; private readonly ConnectionInfo _connection; private readonly AuthenticationManager _authenticationManager; - private FeatureReference _items; - private FeatureReference _serviceProviders; - private FeatureReference _authentication; - private FeatureReference _lifetime; - private FeatureReference _session; + 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; public DefaultHttpContext() : this(new FeatureCollection()) @@ -43,37 +45,90 @@ public DefaultHttpContext(IFeatureCollection features) _response = new DefaultHttpResponse(this, features); _connection = new DefaultConnectionInfo(features); _authenticationManager = new DefaultAuthenticationManager(features); + } - _items = FeatureReference.Default; - _serviceProviders = FeatureReference.Default; - _authentication = FeatureReference.Default; - _lifetime = FeatureReference.Default; - _session = FeatureReference.Default; + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision !=_features.Revision) + { + _items = null; + _serviceProviders = null; + _authentication = null; + _lifetime = null; + _session = null; + _cachedFeaturesRevision = _features.Revision; + } } IItemsFeature ItemsFeature { - get { return _items.Fetch(_features) ?? _items.Update(_features, new ItemsFeature()); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + () => new ItemsFeature(), + ref _items); + } } IServiceProvidersFeature ServiceProvidersFeature { - get { return _serviceProviders.Fetch(_features) ?? _serviceProviders.Update(_features, new ServiceProvidersFeature()); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + () => new ServiceProvidersFeature(), + ref _serviceProviders); + } } private IHttpAuthenticationFeature HttpAuthenticationFeature { - get { return _authentication.Fetch(_features) ?? _authentication.Update(_features, new HttpAuthenticationFeature()); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + () => new HttpAuthenticationFeature(), + ref _authentication); + } } private IHttpRequestLifetimeFeature LifetimeFeature { - get { return _lifetime.Fetch(_features) ?? _lifetime.Update(_features, new HttpRequestLifetimeFeature()); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + () => new HttpRequestLifetimeFeature(), + ref _lifetime); + } } private ISessionFeature SessionFeature { - get { return _session.Fetch(_features); } + get { return FeatureHelpers.GetAndCache(this, _features, ref _session); } + set + { + _features.Set(value); + _session = value; + } + } + private IHttpRequestIdentifierFeature RequestIdentifierFeature + { + get { + return FeatureHelpers.GetOrCreate( + this, + _features, + () => new HttpRequestIdentifierFeature()); + } + set + { + _features.Set(value); + } } public override IFeatureCollection Features { get { return _features; } } @@ -125,6 +180,12 @@ public override CancellationToken RequestAborted set { LifetimeFeature.RequestAborted = value; } } + public override string TraceIdentifier + { + get { return RequestIdentifierFeature.TraceIdentifier; } + set { RequestIdentifierFeature.TraceIdentifier = value; } + } + public override ISession Session { get @@ -143,7 +204,7 @@ public override ISession Session if (feature == null) { feature = new DefaultSessionFeature(); - _session.Update(_features, feature); + SessionFeature = feature; } feature.Session = value; } diff --git a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs index 479ead6e..2bd87ba8 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpRequest.cs @@ -11,15 +11,16 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpRequest : HttpRequest + public class DefaultHttpRequest : HttpRequest, IFeatureCache { private readonly DefaultHttpContext _context; private readonly IFeatureCollection _features; + private int _cachedFeaturesRevision = -1; - private FeatureReference _request = FeatureReference.Default; - private FeatureReference _query = FeatureReference.Default; - private FeatureReference _form = FeatureReference.Default; - private FeatureReference _cookies = FeatureReference.Default; + private IHttpRequestFeature _request; + private IQueryFeature _query; + private IFormFeature _form; + private IRequestCookiesFeature _cookies; public DefaultHttpRequest(DefaultHttpContext context, IFeatureCollection features) { @@ -27,24 +28,58 @@ public DefaultHttpRequest(DefaultHttpContext context, IFeatureCollection feature _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _request = null; + _query = null; + _form = null; + _cookies = null; + _cachedFeaturesRevision = _features.Revision; + } + } + private IHttpRequestFeature HttpRequestFeature { - get { return _request.Fetch(_features); } + get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } } private IQueryFeature QueryFeature { - get { return _query.Fetch(_features) ?? _query.Update(_features, new QueryFeature(_features)); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + (f) => new QueryFeature(f), + ref _query); + } } private IFormFeature FormFeature { - get { return _form.Fetch(_features) ?? _form.Update(_features, new FormFeature(this)); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + this, + (r) => new FormFeature(r), + ref _form); + } } private IRequestCookiesFeature RequestCookiesFeature { - get { return _cookies.Fetch(_features) ?? _cookies.Update(_features, new RequestCookiesFeature(_features)); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + (f) => new RequestCookiesFeature(f), + ref _cookies); + } } public override HttpContext HttpContext { get { return _context; } } diff --git a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs index e5f09f7c..0d728fd3 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpResponse.cs @@ -10,12 +10,14 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultHttpResponse : HttpResponse + public class DefaultHttpResponse : HttpResponse, IFeatureCache { private readonly DefaultHttpContext _context; private readonly IFeatureCollection _features; - private FeatureReference _response = FeatureReference.Default; - private FeatureReference _cookies = FeatureReference.Default; + private int _cachedFeaturesRevision = -1; + + private IHttpResponseFeature _response; + private IResponseCookiesFeature _cookies; public DefaultHttpResponse(DefaultHttpContext context, IFeatureCollection features) { @@ -23,14 +25,31 @@ public DefaultHttpResponse(DefaultHttpContext context, IFeatureCollection featur _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _response = null; + _cookies = null; + _cachedFeaturesRevision = _features.Revision; + } + } + private IHttpResponseFeature HttpResponseFeature { - get { return _response.Fetch(_features); } + get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } } private IResponseCookiesFeature ResponseCookiesFeature { - get { return _cookies.Fetch(_features) ?? _cookies.Update(_features, new ResponseCookiesFeature(_features)); } + get + { + return FeatureHelpers.GetOrCreateAndCache( + this, + _features, + (f) => new ResponseCookiesFeature(f), + ref _cookies); + } } public override HttpContext HttpContext { get { return _context; } } diff --git a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs index 9493859f..454b0fa6 100644 --- a/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs +++ b/src/Microsoft.AspNet.Http/DefaultWebSocketManager.cs @@ -10,25 +10,37 @@ namespace Microsoft.AspNet.Http.Internal { - public class DefaultWebSocketManager : WebSocketManager + public class DefaultWebSocketManager : WebSocketManager, IFeatureCache { private IFeatureCollection _features; - private FeatureReference _request = FeatureReference.Default; - private FeatureReference _webSockets = FeatureReference.Default; + private int _cachedFeaturesRevision = -1; + + private IHttpRequestFeature _request; + private IHttpWebSocketFeature _webSockets; public DefaultWebSocketManager(IFeatureCollection features) { _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _request = null; + _webSockets = null; + _cachedFeaturesRevision = _features.Revision; + } + } + private IHttpRequestFeature HttpRequestFeature { - get { return _request.Fetch(_features); } + get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } } private IHttpWebSocketFeature WebSocketFeature { - get { return _webSockets.Fetch(_features); } + get { return FeatureHelpers.GetAndCache(this, _features, ref _webSockets); } } public override bool IsWebSocketRequest diff --git a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs new file mode 100644 index 00000000..dc4d3cea --- /dev/null +++ b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs @@ -0,0 +1,108 @@ +// 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 sealed class FeatureHelpers + { + public static T GetAndCache( + IFeatureCache cache, + IFeatureCollection features, + ref T cachedObject) + { + cache.CheckFeaturesRevision(); + + T obj = cachedObject; + if (obj == null) + { + obj = features.Get(); + cachedObject = obj; + } + return obj; + } + + public static T GetOrCreate( + IFeatureCache cache, + IFeatureCollection features, + Func factory) + { + 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) + { + cache.CheckFeaturesRevision(); + + T obj = cachedObject; + if (obj == null) + { + obj = features.Get(); + if (obj == null) + { + obj = factory(); + cachedObject = obj; + features.Set(obj); + } + } + return obj; + } + + public static T GetOrCreateAndCache( + IFeatureCache cache, + IFeatureCollection features, + Func factory, + ref T cachedObject) + { + cache.CheckFeaturesRevision(); + + T obj = cachedObject; + if (obj == null) + { + obj = features.Get(); + if (obj == null) + { + obj = factory(features); + cachedObject = obj; + features.Set(obj); + } + } + return obj; + } + + public static T GetOrCreateAndCache( + IFeatureCache cache, + IFeatureCollection features, + HttpRequest request, + Func factory, + ref T cachedObject) + { + cache.CheckFeaturesRevision(); + + T obj = cachedObject; + if (obj == null) + { + obj = features.Get(); + if (obj == null) + { + obj = factory(request); + cachedObject = obj; + features.Set(obj); + } + } + return obj; + } + } +} diff --git a/src/Microsoft.AspNet.Http/Features/HttpRequestIdentifierFeature.cs b/src/Microsoft.AspNet.Http/Features/HttpRequestIdentifierFeature.cs index 01dd9d0e..71f78614 100644 --- a/src/Microsoft.AspNet.Http/Features/HttpRequestIdentifierFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/HttpRequestIdentifierFeature.cs @@ -1,10 +1,64 @@ // 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; +using System.Threading; + namespace Microsoft.AspNet.Http.Features.Internal { public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature { - public string TraceIdentifier { get; set; } + // Base64 encoding - but in ascii sort order for easy text based sorting + private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + // Seed the _requestId for this application instance with + // the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001 + // for a roughly increasing _requestId over restarts + private static long _requestId = DateTime.UtcNow.Ticks; + + private string _id = null; + + public string TraceIdentifier + { + get + { + // Don't incur the cost of generating the request ID until it's asked for + if (_id == null) + { + _id = GenerateRequestId(Interlocked.Increment(ref _requestId)); + } + return _id; + } + set + { + _id = value; + } + } + + private static unsafe string GenerateRequestId(long id) + { + // The following routine is ~310% faster than calling long.ToString() on x64 + // and ~600% faster than calling long.ToString() on x86 in tight loops of 1 million+ iterations + // See: https://github.com/aspnet/Hosting/pull/385 + + // stackalloc to allocate array on stack rather than heap + char* charBuffer = stackalloc char[13]; + + charBuffer[0] = _encode32Chars[(int)(id >> 60) & 31]; + charBuffer[1] = _encode32Chars[(int)(id >> 55) & 31]; + charBuffer[2] = _encode32Chars[(int)(id >> 50) & 31]; + charBuffer[3] = _encode32Chars[(int)(id >> 45) & 31]; + charBuffer[4] = _encode32Chars[(int)(id >> 40) & 31]; + charBuffer[5] = _encode32Chars[(int)(id >> 35) & 31]; + charBuffer[6] = _encode32Chars[(int)(id >> 30) & 31]; + charBuffer[7] = _encode32Chars[(int)(id >> 25) & 31]; + charBuffer[8] = _encode32Chars[(int)(id >> 20) & 31]; + charBuffer[9] = _encode32Chars[(int)(id >> 15) & 31]; + charBuffer[10] = _encode32Chars[(int)(id >> 10) & 31]; + charBuffer[11] = _encode32Chars[(int)(id >> 5) & 31]; + charBuffer[12] = _encode32Chars[(int)id & 31]; + + // string ctor overload that takes char* + return new string(charBuffer, 0, 13); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs b/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs new file mode 100644 index 00000000..80db1ee3 --- /dev/null +++ b/src/Microsoft.AspNet.Http/Features/IFeatureCache.cs @@ -0,0 +1,10 @@ +// 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(); + } +} diff --git a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs index 5dd7ca91..4a275ec6 100644 --- a/src/Microsoft.AspNet.Http/Features/QueryFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/QueryFeature.cs @@ -9,10 +9,12 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class QueryFeature : IQueryFeature + public class QueryFeature : IQueryFeature, IFeatureCache { private readonly IFeatureCollection _features; - private FeatureReference _request = FeatureReference.Default; + private int _cachedFeaturesRevision = -1; + + private IHttpRequestFeature _request; private string _original; private IReadableStringCollection _parsedValues; @@ -46,6 +48,20 @@ public QueryFeature(IFeatureCollection features) _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _request = null; + _cachedFeaturesRevision = _features.Revision; + } + } + + private IHttpRequestFeature HttpRequestFeature + { + get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } + } + public IReadableStringCollection Query { get @@ -55,7 +71,7 @@ public IReadableStringCollection Query return _parsedValues ?? ReadableStringCollection.Empty; } - var current = _request.Fetch(_features).QueryString; + var current = HttpRequestFeature.QueryString; if (_parsedValues == null || !string.Equals(_original, current, StringComparison.Ordinal)) { _original = current; @@ -71,12 +87,12 @@ public IReadableStringCollection Query if (value == null) { _original = string.Empty; - _request.Fetch(_features).QueryString = string.Empty; + HttpRequestFeature.QueryString = string.Empty; } else { _original = QueryString.Create(_parsedValues).ToString(); - _request.Fetch(_features).QueryString = _original; + HttpRequestFeature.QueryString = _original; } } } diff --git a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs index 0eb44ef1..2a10f15a 100644 --- a/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/RequestCookiesFeature.cs @@ -10,10 +10,12 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class RequestCookiesFeature : IRequestCookiesFeature + public class RequestCookiesFeature : IRequestCookiesFeature, IFeatureCache { private readonly IFeatureCollection _features; - private readonly FeatureReference _request = FeatureReference.Default; + private int _cachedFeaturesRevision = -1; + + private IHttpRequestFeature _request; private StringValues _original; private IReadableStringCollection _parsedValues; @@ -43,6 +45,20 @@ public RequestCookiesFeature(IFeatureCollection features) _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _request = null; + _cachedFeaturesRevision = _features.Revision; + } + } + + private IHttpRequestFeature HttpRequestFeature + { + get { return FeatureHelpers.GetAndCache(this, _features, ref _request); } + } + public IReadableStringCollection Cookies { get @@ -52,7 +68,7 @@ public IReadableStringCollection Cookies return _parsedValues ?? ReadableStringCollection.Empty; } - var headers = _request.Fetch(_features).Headers; + var headers = HttpRequestFeature.Headers; StringValues current; if (!headers.TryGetValue(HeaderNames.Cookie, out current)) { @@ -81,7 +97,7 @@ public IReadableStringCollection Cookies { if (_parsedValues == null || _parsedValues.Count == 0) { - _request.Fetch(_features).Headers.Remove(HeaderNames.Cookie); + HttpRequestFeature.Headers.Remove(HeaderNames.Cookie); } else { @@ -94,7 +110,7 @@ public IReadableStringCollection Cookies } } _original = headers.ToArray(); - _request.Fetch(_features).Headers[HeaderNames.Cookie] = _original; + HttpRequestFeature.Headers[HeaderNames.Cookie] = _original; } } } diff --git a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs index 96504f1f..d56ba114 100644 --- a/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs +++ b/src/Microsoft.AspNet.Http/Features/ResponseCookiesFeature.cs @@ -5,10 +5,12 @@ namespace Microsoft.AspNet.Http.Features.Internal { - public class ResponseCookiesFeature : IResponseCookiesFeature + public class ResponseCookiesFeature : IResponseCookiesFeature, IFeatureCache { private readonly IFeatureCollection _features; - private readonly FeatureReference _request = FeatureReference.Default; + private int _cachedFeaturesRevision = -1; + + private IHttpResponseFeature _response; private IResponseCookies _cookiesCollection; public ResponseCookiesFeature(IFeatureCollection features) @@ -16,16 +18,29 @@ public ResponseCookiesFeature(IFeatureCollection features) _features = features; } + void IFeatureCache.CheckFeaturesRevision() + { + if (_cachedFeaturesRevision != _features.Revision) + { + _response = null; + _cachedFeaturesRevision = _features.Revision; + } + } + + private IHttpResponseFeature HttpResponseFeature + { + get { return FeatureHelpers.GetAndCache(this, _features, ref _response); } + } + public IResponseCookies Cookies { get { if (_cookiesCollection == null) { - var headers = _request.Fetch(_features).Headers; + var headers = HttpResponseFeature.Headers; _cookiesCollection = new ResponseCookies(new HeaderDictionary(headers)); } - return _cookiesCollection; } } diff --git a/src/Microsoft.AspNet.Http/project.json b/src/Microsoft.AspNet.Http/project.json index 27cb1107..6ea620d1 100644 --- a/src/Microsoft.AspNet.Http/project.json +++ b/src/Microsoft.AspNet.Http/project.json @@ -6,7 +6,8 @@ "url": "git://github.com/aspnet/httpabstractions" }, "compilationOptions": { - "warningsAsErrors": true + "warningsAsErrors": true, + "allowUnsafe": true }, "dependencies": { "Microsoft.AspNet.Http.Abstractions": "1.0.0-*", diff --git a/test/Microsoft.AspNet.Http.Tests/DeafultHttpRequestIdentifierFeatureTests.cs b/test/Microsoft.AspNet.Http.Tests/DeafultHttpRequestIdentifierFeatureTests.cs new file mode 100644 index 00000000..c8e68cf2 --- /dev/null +++ b/test/Microsoft.AspNet.Http.Tests/DeafultHttpRequestIdentifierFeatureTests.cs @@ -0,0 +1,44 @@ +// 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.Features.Internal; +using Xunit; + +namespace Microsoft.AspNet.Http.Tests +{ + public class DeafultHttpRequestIdentifierFeatureTests + { + [Fact] + public void TraceIdentifier_ReturnsId() + { + var feature = new HttpRequestIdentifierFeature(); + + var id = feature.TraceIdentifier; + + Assert.NotNull(id); + } + + [Fact] + public void TraceIdentifier_ReturnsStableId() + { + var feature = new HttpRequestIdentifierFeature(); + + var id1 = feature.TraceIdentifier; + var id2 = feature.TraceIdentifier; + + Assert.Equal(id1, id2); + } + + [Fact] + public void TraceIdentifier_ReturnsUniqueIdForDifferentInstances() + { + var feature1 = new HttpRequestIdentifierFeature(); + var feature2 = new HttpRequestIdentifierFeature(); + + var id1 = feature1.TraceIdentifier; + var id2 = feature2.TraceIdentifier; + + Assert.NotEqual(id1, id2); + } + } +} diff --git a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs index b8c9691e..3d7305b8 100644 --- a/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs +++ b/test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs @@ -119,6 +119,20 @@ public void GetItems_DefaultCollectionProvided() Assert.Same(item, context.Items["foo"]); } + [Fact] + public void GetItems_DefaultRequestIdentifierAvailable() + { + var context = new DefaultHttpContext(new FeatureCollection()); + Assert.Null(context.Features.Get()); + var traceIdentifier = context.TraceIdentifier; + Assert.NotNull(context.Features.Get()); + Assert.NotNull(traceIdentifier); + Assert.Same(traceIdentifier, context.TraceIdentifier); + + context.TraceIdentifier = "Hello"; + Assert.Same("Hello", context.TraceIdentifier); + } + [Fact] public void SetItems_NewCollectionUsed() {