#96 Add a session Id
This commit is contained in:
parent
7c60cd5fa6
commit
432de7a158
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
@ -15,11 +16,14 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
public class DistributedSession : ISession
|
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 const int KeyLengthLimit = ushort.MaxValue;
|
||||||
|
|
||||||
private readonly IDistributedCache _cache;
|
private readonly IDistributedCache _cache;
|
||||||
private readonly string _sessionId;
|
private readonly string _sessionKey;
|
||||||
private readonly TimeSpan _idleTimeout;
|
private readonly TimeSpan _idleTimeout;
|
||||||
private readonly Func<bool> _tryEstablishSession;
|
private readonly Func<bool> _tryEstablishSession;
|
||||||
private readonly IDictionary<EncodedKey, byte[]> _store;
|
private readonly IDictionary<EncodedKey, byte[]> _store;
|
||||||
|
|
@ -27,10 +31,12 @@ namespace Microsoft.AspNetCore.Session
|
||||||
private bool _isModified;
|
private bool _isModified;
|
||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
private bool _isNewSessionKey;
|
private bool _isNewSessionKey;
|
||||||
|
private string _sessionId;
|
||||||
|
private byte[] _sessionIdBytes;
|
||||||
|
|
||||||
public DistributedSession(
|
public DistributedSession(
|
||||||
IDistributedCache cache,
|
IDistributedCache cache,
|
||||||
string sessionId,
|
string sessionKey,
|
||||||
TimeSpan idleTimeout,
|
TimeSpan idleTimeout,
|
||||||
Func<bool> tryEstablishSession,
|
Func<bool> tryEstablishSession,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
|
|
@ -41,9 +47,9 @@ namespace Microsoft.AspNetCore.Session
|
||||||
throw new ArgumentNullException(nameof(cache));
|
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)
|
if (tryEstablishSession == null)
|
||||||
|
|
@ -57,7 +63,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_sessionId = sessionId;
|
_sessionKey = sessionKey;
|
||||||
_idleTimeout = idleTimeout;
|
_idleTimeout = idleTimeout;
|
||||||
_tryEstablishSession = tryEstablishSession;
|
_tryEstablishSession = tryEstablishSession;
|
||||||
_store = new Dictionary<EncodedKey, byte[]>();
|
_store = new Dictionary<EncodedKey, byte[]>();
|
||||||
|
|
@ -65,6 +71,33 @@ namespace Microsoft.AspNetCore.Session
|
||||||
_isNewSessionKey = isNewSessionKey;
|
_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
|
public IEnumerable<string> Keys
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -122,7 +155,16 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
if (!_loaded)
|
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)
|
if (!_loaded)
|
||||||
{
|
{
|
||||||
var data = await _cache.GetAsync(_sessionId);
|
var data = await _cache.GetAsync(_sessionKey);
|
||||||
if (data != null)
|
if (data != null)
|
||||||
{
|
{
|
||||||
Deserialize(new MemoryStream(data));
|
Deserialize(new MemoryStream(data));
|
||||||
}
|
}
|
||||||
else if (!_isNewSessionKey)
|
else if (!_isNewSessionKey)
|
||||||
{
|
{
|
||||||
_logger.AccessingExpiredSession(_sessionId);
|
_logger.AccessingExpiredSession(_sessionKey);
|
||||||
}
|
}
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
}
|
}
|
||||||
|
|
@ -149,29 +191,35 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
if (_isModified)
|
if (_isModified)
|
||||||
{
|
{
|
||||||
var data = await _cache.GetAsync(_sessionId);
|
var data = await _cache.GetAsync(_sessionKey);
|
||||||
if (_logger.IsEnabled(LogLevel.Information) && data == null)
|
if (_logger.IsEnabled(LogLevel.Information) && data == null)
|
||||||
{
|
{
|
||||||
_logger.SessionStarted(_sessionId);
|
_logger.SessionStarted(_sessionKey, Id);
|
||||||
}
|
}
|
||||||
_isModified = false;
|
_isModified = false;
|
||||||
|
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
Serialize(stream);
|
Serialize(stream);
|
||||||
await _cache.SetAsync(
|
await _cache.SetAsync(
|
||||||
_sessionId,
|
_sessionKey,
|
||||||
stream.ToArray(),
|
stream.ToArray(),
|
||||||
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));
|
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_logger.SessionStored(_sessionKey, Id, _store.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _cache.RefreshAsync(_sessionId);
|
await _cache.RefreshAsync(_sessionKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format:
|
// Format:
|
||||||
// Serialization revision: 1 byte, range 0-255
|
// Serialization revision: 1 byte, range 0-255
|
||||||
// Entry count: 3 bytes, range 0-16,777,215
|
// Entry count: 3 bytes, range 0-16,777,215
|
||||||
|
// SessionId: IdByteCount bytes (16)
|
||||||
// foreach entry:
|
// foreach entry:
|
||||||
// key name byte length: 2 bytes, range 0-65,535
|
// key name byte length: 2 bytes, range 0-65,535
|
||||||
// UTF-8 encoded key name byte[]
|
// UTF-8 encoded key name byte[]
|
||||||
|
|
@ -181,6 +229,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
output.WriteByte(SerializationRevision);
|
output.WriteByte(SerializationRevision);
|
||||||
SerializeNumAs3Bytes(output, _store.Count);
|
SerializeNumAs3Bytes(output, _store.Count);
|
||||||
|
output.Write(IdBytes, 0, IdByteCount);
|
||||||
|
|
||||||
foreach (var entry in _store)
|
foreach (var entry in _store)
|
||||||
{
|
{
|
||||||
|
|
@ -203,6 +252,8 @@ namespace Microsoft.AspNetCore.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
int expectedEntries = DeserializeNumFrom3Bytes(content);
|
int expectedEntries = DeserializeNumFrom3Bytes(content);
|
||||||
|
_sessionIdBytes = ReadBytes(content, IdByteCount);
|
||||||
|
|
||||||
for (int i = 0; i < expectedEntries; i++)
|
for (int i = 0; i < expectedEntries; i++)
|
||||||
{
|
{
|
||||||
int keyLength = DeserializeNumFrom2Bytes(content);
|
int keyLength = DeserializeNumFrom2Bytes(content);
|
||||||
|
|
@ -210,6 +261,12 @@ namespace Microsoft.AspNetCore.Session
|
||||||
int dataLength = DeserializeNumFrom4Bytes(content);
|
int dataLength = DeserializeNumFrom4Bytes(content);
|
||||||
_store[key] = ReadBytes(content, dataLength);
|
_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)
|
private void SerializeNumAs2Bytes(Stream output, int num)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
if (tryEstablishSession == null)
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
throw new ArgumentNullException(nameof(tryEstablishSession));
|
throw new ArgumentNullException(nameof(tryEstablishSession));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DistributedSession(_cache, sessionId, idleTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
|
return new DistributedSession(_cache, sessionKey, idleTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,6 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
bool IsAvailable { get; }
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,9 @@ namespace Microsoft.Extensions.Logging
|
||||||
{
|
{
|
||||||
private static Action<ILogger, Exception> _errorClosingTheSession;
|
private static Action<ILogger, Exception> _errorClosingTheSession;
|
||||||
private static Action<ILogger, string, Exception> _accessingExpiredSession;
|
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()
|
static LoggingExtensions()
|
||||||
{
|
{
|
||||||
|
|
@ -20,11 +22,19 @@ namespace Microsoft.Extensions.Logging
|
||||||
_accessingExpiredSession = LoggerMessage.Define<string>(
|
_accessingExpiredSession = LoggerMessage.Define<string>(
|
||||||
eventId: 2,
|
eventId: 2,
|
||||||
logLevel: LogLevel.Warning,
|
logLevel: LogLevel.Warning,
|
||||||
formatString: "Accessing expired session {SessionId}");
|
formatString: "Accessing expired session; Key:{sessionKey}");
|
||||||
_sessionStarted = LoggerMessage.Define<string>(
|
_sessionStarted = LoggerMessage.Define<string, string>(
|
||||||
eventId: 3,
|
eventId: 3,
|
||||||
logLevel: LogLevel.Information,
|
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)
|
public static void ErrorClosingTheSession(this ILogger logger, Exception exception)
|
||||||
|
|
@ -32,14 +42,24 @@ namespace Microsoft.Extensions.Logging
|
||||||
_errorClosingTheSession(logger, exception);
|
_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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -262,9 +262,11 @@ namespace Microsoft.AspNetCore.Session
|
||||||
|
|
||||||
var sessionLogMessages = sink.Writes.OnlyMessagesFromSource<DistributedSession>().ToArray();
|
var sessionLogMessages = sink.Writes.OnlyMessagesFromSource<DistributedSession>().ToArray();
|
||||||
|
|
||||||
Assert.Single(sessionLogMessages);
|
Assert.Equal(2, sessionLogMessages.Length);
|
||||||
Assert.Contains("started", sessionLogMessages[0].State.ToString());
|
Assert.Contains("started", sessionLogMessages[0].State.ToString());
|
||||||
Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel);
|
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();
|
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("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.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue