From 432de7a15800f1e988ae7cf1b5b5f4ac5a5ef1e7 Mon Sep 17 00:00:00 2001 From: Chris R Date: Fri, 4 Mar 2016 15:11:35 -0800 Subject: [PATCH] #96 Add a session Id --- .../Properties/launchSettings.json | 25 ++++++ .../DistributedSession.cs | 83 ++++++++++++++++--- .../DistributedSessionStore.cs | 8 +- .../ISessionStore.cs | 2 +- .../LoggingExtensions.cs | 36 ++++++-- .../SessionTests.cs | 12 ++- 6 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 samples/SessionSample/Properties/launchSettings.json diff --git a/samples/SessionSample/Properties/launchSettings.json b/samples/SessionSample/Properties/launchSettings.json new file mode 100644 index 0000000000..bd71d7713a --- /dev/null +++ b/samples/SessionSample/Properties/launchSettings.json @@ -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" + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Session/DistributedSession.cs b/src/Microsoft.AspNetCore.Session/DistributedSession.cs index 2a06b62075..e9ae5867e2 100644 --- a/src/Microsoft.AspNetCore.Session/DistributedSession.cs +++ b/src/Microsoft.AspNetCore.Session/DistributedSession.cs @@ -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 _tryEstablishSession; private readonly IDictionary _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 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(); @@ -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 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) diff --git a/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs b/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs index e170d08df8..41200e9d7e 100644 --- a/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs +++ b/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs @@ -37,11 +37,11 @@ namespace Microsoft.AspNetCore.Session } } - public ISession Create(string sessionId, TimeSpan idleTimeout, Func tryEstablishSession, bool isNewSessionKey) + public ISession Create(string sessionKey, TimeSpan idleTimeout, Func 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); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Session/ISessionStore.cs b/src/Microsoft.AspNetCore.Session/ISessionStore.cs index 17a3261b97..0b48648c8a 100644 --- a/src/Microsoft.AspNetCore.Session/ISessionStore.cs +++ b/src/Microsoft.AspNetCore.Session/ISessionStore.cs @@ -10,6 +10,6 @@ namespace Microsoft.AspNetCore.Session { bool IsAvailable { get; } - ISession Create(string sessionId, TimeSpan idleTimeout, Func tryEstablishSession, bool isNewSessionKey); + ISession Create(string sessionKey, TimeSpan idleTimeout, Func tryEstablishSession, bool isNewSessionKey); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs index d9cc0b8654..1dcc93e709 100644 --- a/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Session/LoggingExtensions.cs @@ -9,7 +9,9 @@ namespace Microsoft.Extensions.Logging { private static Action _errorClosingTheSession; private static Action _accessingExpiredSession; - private static Action _sessionStarted; + private static Action _sessionStarted; + private static Action _sessionLoaded; + private static Action _sessionStored; static LoggingExtensions() { @@ -20,11 +22,19 @@ namespace Microsoft.Extensions.Logging _accessingExpiredSession = LoggerMessage.Define( eventId: 2, logLevel: LogLevel.Warning, - formatString: "Accessing expired session {SessionId}"); - _sessionStarted = LoggerMessage.Define( + formatString: "Accessing expired session; Key:{sessionKey}"); + _sessionStarted = LoggerMessage.Define( eventId: 3, logLevel: LogLevel.Information, - formatString: "Session {SessionId} started"); + formatString: "Session started; Key:{sessionKey}, Id:{sessionId}"); + _sessionLoaded = LoggerMessage.Define( + eventId: 4, + logLevel: LogLevel.Debug, + formatString: "Session loaded; Key:{sessionKey}, Id:{sessionId}, Count:{count}"); + _sessionStored = LoggerMessage.Define( + 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); } } } diff --git a/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs b/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs index 8e224f050e..e30f541584 100644 --- a/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs +++ b/test/Microsoft.AspNetCore.Session.Tests/SessionTests.cs @@ -262,9 +262,11 @@ namespace Microsoft.AspNetCore.Session var sessionLogMessages = sink.Writes.OnlyMessagesFromSource().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().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); } }