#96 Add a session Id

This commit is contained in:
Chris R 2016-03-04 15:11:35 -08:00
parent 7c60cd5fa6
commit 432de7a158
6 changed files with 136 additions and 30 deletions

View File

@ -0,0 +1,25 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:2481/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"Hosting:Environment": "Development"
}
},
"web": {
"commandName": "web",
"environmentVariables": {
"Hosting:Environment": "Development"
}
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
@ -15,11 +16,14 @@ namespace Microsoft.AspNetCore.Session
{
public class DistributedSession : ISession
{
private const byte SerializationRevision = 1;
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
private const int IdByteCount = 16;
private const byte SerializationRevision = 2;
private const int KeyLengthLimit = ushort.MaxValue;
private readonly IDistributedCache _cache;
private readonly string _sessionId;
private readonly string _sessionKey;
private readonly TimeSpan _idleTimeout;
private readonly Func<bool> _tryEstablishSession;
private readonly IDictionary<EncodedKey, byte[]> _store;
@ -27,10 +31,12 @@ namespace Microsoft.AspNetCore.Session
private bool _isModified;
private bool _loaded;
private bool _isNewSessionKey;
private string _sessionId;
private byte[] _sessionIdBytes;
public DistributedSession(
IDistributedCache cache,
string sessionId,
string sessionKey,
TimeSpan idleTimeout,
Func<bool> tryEstablishSession,
ILoggerFactory loggerFactory,
@ -41,9 +47,9 @@ namespace Microsoft.AspNetCore.Session
throw new ArgumentNullException(nameof(cache));
}
if (string.IsNullOrEmpty(sessionId))
if (string.IsNullOrEmpty(sessionKey))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(sessionId));
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(sessionKey));
}
if (tryEstablishSession == null)
@ -57,7 +63,7 @@ namespace Microsoft.AspNetCore.Session
}
_cache = cache;
_sessionId = sessionId;
_sessionKey = sessionKey;
_idleTimeout = idleTimeout;
_tryEstablishSession = tryEstablishSession;
_store = new Dictionary<EncodedKey, byte[]>();
@ -65,6 +71,33 @@ namespace Microsoft.AspNetCore.Session
_isNewSessionKey = isNewSessionKey;
}
public string Id
{
get
{
Load(); // TODO: Silent failure
if (_sessionId == null)
{
_sessionId = new Guid(IdBytes).ToString();
}
return _sessionId;
}
}
private byte[] IdBytes
{
get
{
Load(); // TODO: Silent failure
if (_sessionIdBytes == null)
{
_sessionIdBytes = new byte[IdByteCount];
CryptoRandom.GetBytes(_sessionIdBytes);
}
return _sessionIdBytes;
}
}
public IEnumerable<string> Keys
{
get
@ -122,7 +155,16 @@ namespace Microsoft.AspNetCore.Session
{
if (!_loaded)
{
LoadAsync().GetAwaiter().GetResult();
var data = _cache.Get(_sessionKey);
if (data != null)
{
Deserialize(new MemoryStream(data));
}
else if (!_isNewSessionKey)
{
_logger.AccessingExpiredSession(_sessionKey);
}
_loaded = true;
}
}
@ -132,14 +174,14 @@ namespace Microsoft.AspNetCore.Session
{
if (!_loaded)
{
var data = await _cache.GetAsync(_sessionId);
var data = await _cache.GetAsync(_sessionKey);
if (data != null)
{
Deserialize(new MemoryStream(data));
}
else if (!_isNewSessionKey)
{
_logger.AccessingExpiredSession(_sessionId);
_logger.AccessingExpiredSession(_sessionKey);
}
_loaded = true;
}
@ -149,29 +191,35 @@ namespace Microsoft.AspNetCore.Session
{
if (_isModified)
{
var data = await _cache.GetAsync(_sessionId);
var data = await _cache.GetAsync(_sessionKey);
if (_logger.IsEnabled(LogLevel.Information) && data == null)
{
_logger.SessionStarted(_sessionId);
_logger.SessionStarted(_sessionKey, Id);
}
_isModified = false;
var stream = new MemoryStream();
Serialize(stream);
await _cache.SetAsync(
_sessionId,
_sessionKey,
stream.ToArray(),
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.SessionStored(_sessionKey, Id, _store.Count);
}
}
else
{
await _cache.RefreshAsync(_sessionId);
await _cache.RefreshAsync(_sessionKey);
}
}
// Format:
// Serialization revision: 1 byte, range 0-255
// Entry count: 3 bytes, range 0-16,777,215
// SessionId: IdByteCount bytes (16)
// foreach entry:
// key name byte length: 2 bytes, range 0-65,535
// UTF-8 encoded key name byte[]
@ -181,6 +229,7 @@ namespace Microsoft.AspNetCore.Session
{
output.WriteByte(SerializationRevision);
SerializeNumAs3Bytes(output, _store.Count);
output.Write(IdBytes, 0, IdByteCount);
foreach (var entry in _store)
{
@ -203,6 +252,8 @@ namespace Microsoft.AspNetCore.Session
}
int expectedEntries = DeserializeNumFrom3Bytes(content);
_sessionIdBytes = ReadBytes(content, IdByteCount);
for (int i = 0; i < expectedEntries; i++)
{
int keyLength = DeserializeNumFrom2Bytes(content);
@ -210,6 +261,12 @@ namespace Microsoft.AspNetCore.Session
int dataLength = DeserializeNumFrom4Bytes(content);
_store[key] = ReadBytes(content, dataLength);
}
if (_logger.IsEnabled(LogLevel.Debug))
{
_sessionId = new Guid(_sessionIdBytes).ToString();
_logger.SessionLoaded(_sessionKey, _sessionId, expectedEntries);
}
}
private void SerializeNumAs2Bytes(Stream output, int num)

View File

@ -37,11 +37,11 @@ namespace Microsoft.AspNetCore.Session
}
}
public ISession Create(string sessionId, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
public ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
{
if (string.IsNullOrEmpty(sessionId))
if (string.IsNullOrEmpty(sessionKey))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(sessionId));
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(sessionKey));
}
if (tryEstablishSession == null)
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Session
throw new ArgumentNullException(nameof(tryEstablishSession));
}
return new DistributedSession(_cache, sessionId, idleTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
return new DistributedSession(_cache, sessionKey, idleTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
}
}
}

View File

@ -10,6 +10,6 @@ namespace Microsoft.AspNetCore.Session
{
bool IsAvailable { get; }
ISession Create(string sessionId, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey);
ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey);
}
}

View File

@ -9,7 +9,9 @@ namespace Microsoft.Extensions.Logging
{
private static Action<ILogger, Exception> _errorClosingTheSession;
private static Action<ILogger, string, Exception> _accessingExpiredSession;
private static Action<ILogger, string, Exception> _sessionStarted;
private static Action<ILogger, string, string, Exception> _sessionStarted;
private static Action<ILogger, string, string, int, Exception> _sessionLoaded;
private static Action<ILogger, string, string, int, Exception> _sessionStored;
static LoggingExtensions()
{
@ -20,11 +22,19 @@ namespace Microsoft.Extensions.Logging
_accessingExpiredSession = LoggerMessage.Define<string>(
eventId: 2,
logLevel: LogLevel.Warning,
formatString: "Accessing expired session {SessionId}");
_sessionStarted = LoggerMessage.Define<string>(
formatString: "Accessing expired session; Key:{sessionKey}");
_sessionStarted = LoggerMessage.Define<string, string>(
eventId: 3,
logLevel: LogLevel.Information,
formatString: "Session {SessionId} started");
formatString: "Session started; Key:{sessionKey}, Id:{sessionId}");
_sessionLoaded = LoggerMessage.Define<string, string, int>(
eventId: 4,
logLevel: LogLevel.Debug,
formatString: "Session loaded; Key:{sessionKey}, Id:{sessionId}, Count:{count}");
_sessionStored = LoggerMessage.Define<string, string, int>(
eventId: 5,
logLevel: LogLevel.Debug,
formatString: "Session stored; Key:{sessionKey}, Id:{sessionId}, Count:{count}");
}
public static void ErrorClosingTheSession(this ILogger logger, Exception exception)
@ -32,14 +42,24 @@ namespace Microsoft.Extensions.Logging
_errorClosingTheSession(logger, exception);
}
public static void AccessingExpiredSession(this ILogger logger, string sessionId)
public static void AccessingExpiredSession(this ILogger logger, string sessionKey)
{
_accessingExpiredSession(logger, sessionId, null);
_accessingExpiredSession(logger, sessionKey, null);
}
public static void SessionStarted(this ILogger logger, string sessionId)
public static void SessionStarted(this ILogger logger, string sessionKey, string sessionId)
{
_sessionStarted(logger, sessionId, null);
_sessionStarted(logger, sessionKey, sessionId, null);
}
public static void SessionLoaded(this ILogger logger, string sessionKey, string sessionId, int count)
{
_sessionLoaded(logger, sessionKey, sessionId, count, null);
}
public static void SessionStored(this ILogger logger, string sessionKey, string sessionId, int count)
{
_sessionStored(logger, sessionKey, sessionId, count, null);
}
}
}

View File

@ -262,9 +262,11 @@ namespace Microsoft.AspNetCore.Session
var sessionLogMessages = sink.Writes.OnlyMessagesFromSource<DistributedSession>().ToArray();
Assert.Single(sessionLogMessages);
Assert.Equal(2, sessionLogMessages.Length);
Assert.Contains("started", sessionLogMessages[0].State.ToString());
Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel);
Assert.Contains("stored", sessionLogMessages[1].State.ToString());
Assert.Equal(LogLevel.Debug, sessionLogMessages[1].LogLevel);
}
}
@ -315,11 +317,13 @@ namespace Microsoft.AspNetCore.Session
var sessionLogMessages = sink.Writes.OnlyMessagesFromSource<DistributedSession>().ToArray();
Assert.Equal(2, sessionLogMessages.Length);
Assert.Equal(3, sessionLogMessages.Length);
Assert.Contains("started", sessionLogMessages[0].State.ToString());
Assert.Contains("expired", sessionLogMessages[1].State.ToString());
Assert.Contains("stored", sessionLogMessages[1].State.ToString());
Assert.Contains("expired", sessionLogMessages[2].State.ToString());
Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel);
Assert.Equal(LogLevel.Warning, sessionLogMessages[1].LogLevel);
Assert.Equal(LogLevel.Debug, sessionLogMessages[1].LogLevel);
Assert.Equal(LogLevel.Warning, sessionLogMessages[2].LogLevel);
}
}