From e01a05d21439815370a6b82682839390b7c57a24 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 15 Oct 2015 19:56:41 +0100 Subject: [PATCH] Move RequestIdentifierFeature to HttpContext Rebased #435 Allow lazier instantiation Expose TraceIdentifier on Httpcontext Also resolves #412 Add tests --- .../HttpContext.cs | 2 + .../DefaultHttpContext.cs | 15 +++++ .../Features/FeatureHelpers.cs | 15 +++++ .../Features/HttpRequestIdentifierFeature.cs | 56 ++++++++++++++++++- src/Microsoft.AspNet.Http/project.json | 3 +- .../DefaultHttpContextTests.cs | 14 +++++ .../HttpRequestIdentifierFeatureTests.cs | 44 +++++++++++++++ 7 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 test/Microsoft.AspNet.Http.Tests/HttpRequestIdentifierFeatureTests.cs diff --git a/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs b/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs index 7a7a35e5..191d0a5d 100644 --- a/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs +++ b/src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs @@ -34,6 +34,8 @@ public abstract class HttpContext public abstract CancellationToken RequestAborted { get; set; } + public abstract string TraceIdentifier { get; set; } + public abstract ISession Session { get; set; } public abstract void Abort(); diff --git a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs index b3e92ee2..ffc6acc6 100644 --- a/src/Microsoft.AspNet.Http/DefaultHttpContext.cs +++ b/src/Microsoft.AspNet.Http/DefaultHttpContext.cs @@ -118,6 +118,15 @@ private ISessionFeature SessionFeature } } + private IHttpRequestIdentifierFeature RequestIdentifierFeature + { + get { + return FeatureHelpers.GetOrCreate( + _features, + () => new HttpRequestIdentifierFeature()); + } + } + public override IFeatureCollection Features { get { return _features; } } public override HttpRequest Request { get { return _request; } } @@ -167,6 +176,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 diff --git a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs index 142e0e2a..fc59b00e 100644 --- a/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs +++ b/src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs @@ -23,6 +23,21 @@ public static T GetAndCache( return obj; } + public static T GetOrCreate( + 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, 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/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/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() { diff --git a/test/Microsoft.AspNet.Http.Tests/HttpRequestIdentifierFeatureTests.cs b/test/Microsoft.AspNet.Http.Tests/HttpRequestIdentifierFeatureTests.cs new file mode 100644 index 00000000..c117a48d --- /dev/null +++ b/test/Microsoft.AspNet.Http.Tests/HttpRequestIdentifierFeatureTests.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 HttpRequestIdentifierFeatureTests + { + [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); + } + } +} \ No newline at end of file