Skip to content

Commit

Permalink
Move RequestIdentifierFeature to HttpContext to allow lighter caching
Browse files Browse the repository at this point in the history
Expose TraceIdentifier on Httpcontext
Also resolves aspnet#412

Add tests
  • Loading branch information
benaadams committed Oct 10, 2015
1 parent fdb3d54 commit 911bd5e
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/Microsoft.AspNet.Http.Abstractions/HttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
19 changes: 19 additions & 0 deletions src/Microsoft.AspNet.Http/DefaultHttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ private ISessionFeature SessionFeature
_session = value;
}
}
private IHttpRequestIdentifierFeature RequestIdentifierFeature
{
get {
return FeatureHelpers.GetOrCreate<IHttpRequestIdentifierFeature>(
this,
_features,
() => new DefaultHttpRequestIdentifierFeature());
}
set
{
_features.Set(value);
}
}

public override IFeatureCollection Features { get { return _features; } }

Expand Down Expand Up @@ -167,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
Expand Down
64 changes: 64 additions & 0 deletions src/Microsoft.AspNet.Http/DefaultHttpRequestIdentifierFeature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +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 DefaultHttpRequestIdentifierFeature : HttpRequestIdentifierFeature, IHttpRequestIdentifierFeature
{
// 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 override 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);
}
}
}
15 changes: 15 additions & 0 deletions src/Microsoft.AspNet.Http/Features/FeatureHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ public static T GetAndCache<T>(
return obj;
}

public static T GetOrCreate<T>(
IFeatureCache cache,
IFeatureCollection features,
Func<T> factory)
{
T obj = features.Get<T>();
if (obj == null)
{
obj = factory();
features.Set(obj);
}

return obj;
}

public static T GetOrCreateAndCache<T>(
IFeatureCache cache,
IFeatureCollection features,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace Microsoft.AspNet.Http.Features.Internal
{
public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature
{
public string TraceIdentifier { get; set; }
public virtual string TraceIdentifier { get; set; }
}
}
3 changes: 2 additions & 1 deletion src/Microsoft.AspNet.Http/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -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-*",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 DefaultHttpRequestIdentifierFeature();

var id = feature.TraceIdentifier;

Assert.NotNull(id);
}

[Fact]
public void TraceIdentifier_ReturnsStableId()
{
var feature = new DefaultHttpRequestIdentifierFeature();

var id1 = feature.TraceIdentifier;
var id2 = feature.TraceIdentifier;

Assert.Equal(id1, id2);
}

[Fact]
public void TraceIdentifier_ReturnsUniqueIdForDifferentInstances()
{
var feature1 = new DefaultHttpRequestIdentifierFeature();
var feature2 = new DefaultHttpRequestIdentifierFeature();

var id1 = feature1.TraceIdentifier;
var id2 = feature2.TraceIdentifier;

Assert.NotEqual(id1, id2);
}
}
}
14 changes: 14 additions & 0 deletions test/Microsoft.AspNet.Http.Tests/DefaultHttpContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IHttpRequestIdentifierFeature>());
var traceIdentifier = context.TraceIdentifier;
Assert.NotNull(context.Features.Get<IHttpRequestIdentifierFeature>());
Assert.NotNull(traceIdentifier);
Assert.Same(traceIdentifier, context.TraceIdentifier);

context.TraceIdentifier = "Hello";
Assert.Same("Hello", context.TraceIdentifier);
}

[Fact]
public void SetItems_NewCollectionUsed()
{
Expand Down

0 comments on commit 911bd5e

Please sign in to comment.