Handle cache unreliability #99
This commit is contained in:
parent
dabd28a5d9
commit
d61c5100c9
|
|
@ -12,13 +12,13 @@
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"Hosting:Environment": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SessionSample": {
|
"SessionSample": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "http://localhost:5000/",
|
"launchUrl": "http://localhost:5000",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ namespace SessionSample
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
// Adds a default in-memory implementation of IDistributedCache
|
||||||
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
// Uncomment the following line to use the Microsoft SQL Server implementation of IDistributedCache.
|
// Uncomment the following line to use the Microsoft SQL Server implementation of IDistributedCache.
|
||||||
// Note that this would require setting up the session state database.
|
// Note that this would require setting up the session state database.
|
||||||
//services.AddSqlServerCache(o =>
|
//services.AddSqlServerCache(o =>
|
||||||
|
|
@ -32,10 +35,6 @@ namespace SessionSample
|
||||||
// This will override any previously registered IDistributedCache service.
|
// This will override any previously registered IDistributedCache service.
|
||||||
//services.AddSingleton<IDistributedCache, RedisCache>();
|
//services.AddSingleton<IDistributedCache, RedisCache>();
|
||||||
#endif
|
#endif
|
||||||
// Adds a default in-memory implementation of IDistributedCache
|
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
|
||||||
|
|
||||||
services.AddSession(o =>
|
services.AddSession(o =>
|
||||||
{
|
{
|
||||||
o.IdleTimeout = TimeSpan.FromSeconds(10);
|
o.IdleTimeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
|
@ -26,10 +25,11 @@ namespace Microsoft.AspNetCore.Session
|
||||||
private readonly string _sessionKey;
|
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 ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private IDictionary<EncodedKey, byte[]> _store;
|
||||||
private bool _isModified;
|
private bool _isModified;
|
||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
|
private bool _isAvailable;
|
||||||
private bool _isNewSessionKey;
|
private bool _isNewSessionKey;
|
||||||
private string _sessionId;
|
private string _sessionId;
|
||||||
private byte[] _sessionIdBytes;
|
private byte[] _sessionIdBytes;
|
||||||
|
|
@ -71,11 +71,20 @@ namespace Microsoft.AspNetCore.Session
|
||||||
_isNewSessionKey = isNewSessionKey;
|
_isNewSessionKey = isNewSessionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsAvailable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
return _isAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string Id
|
public string Id
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
Load(); // TODO: Silent failure
|
Load();
|
||||||
if (_sessionId == null)
|
if (_sessionId == null)
|
||||||
{
|
{
|
||||||
_sessionId = new Guid(IdBytes).ToString();
|
_sessionId = new Guid(IdBytes).ToString();
|
||||||
|
|
@ -88,8 +97,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
Load(); // TODO: Silent failure
|
if (IsAvailable && _sessionIdBytes == null)
|
||||||
if (_sessionIdBytes == null)
|
|
||||||
{
|
{
|
||||||
_sessionIdBytes = new byte[IdByteCount];
|
_sessionIdBytes = new byte[IdByteCount];
|
||||||
CryptoRandom.GetBytes(_sessionIdBytes);
|
CryptoRandom.GetBytes(_sessionIdBytes);
|
||||||
|
|
@ -102,14 +110,14 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
Load(); // TODO: Silent failure
|
Load();
|
||||||
return _store.Keys.Select(key => key.KeyString);
|
return _store.Keys.Select(key => key.KeyString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetValue(string key, out byte[] value)
|
public bool TryGetValue(string key, out byte[] value)
|
||||||
{
|
{
|
||||||
Load(); // TODO: Silent failure
|
Load();
|
||||||
return _store.TryGetValue(new EncodedKey(key), out value);
|
return _store.TryGetValue(new EncodedKey(key), out value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,22 +128,24 @@ namespace Microsoft.AspNetCore.Session
|
||||||
throw new ArgumentNullException(nameof(value));
|
throw new ArgumentNullException(nameof(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
var encodedKey = new EncodedKey(key);
|
if (IsAvailable)
|
||||||
if (encodedKey.KeyBytes.Length > KeyLengthLimit)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(key),
|
var encodedKey = new EncodedKey(key);
|
||||||
Resources.FormatException_KeyLengthIsExceeded(KeyLengthLimit));
|
if (encodedKey.KeyBytes.Length > KeyLengthLimit)
|
||||||
}
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(key),
|
||||||
|
Resources.FormatException_KeyLengthIsExceeded(KeyLengthLimit));
|
||||||
|
}
|
||||||
|
|
||||||
Load();
|
if (!_tryEstablishSession())
|
||||||
if (!_tryEstablishSession())
|
{
|
||||||
{
|
throw new InvalidOperationException(Resources.Exception_InvalidSessionEstablishment);
|
||||||
throw new InvalidOperationException(Resources.Exception_InvalidSessionEstablishment);
|
}
|
||||||
|
_isModified = true;
|
||||||
|
byte[] copy = new byte[value.Length];
|
||||||
|
Buffer.BlockCopy(src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length);
|
||||||
|
_store[encodedKey] = copy;
|
||||||
}
|
}
|
||||||
_isModified = true;
|
|
||||||
byte[] copy = new byte[value.Length];
|
|
||||||
Buffer.BlockCopy(src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length);
|
|
||||||
_store[encodedKey] = copy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(string key)
|
public void Remove(string key)
|
||||||
|
|
@ -155,21 +165,35 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
if (!_loaded)
|
if (!_loaded)
|
||||||
{
|
{
|
||||||
var data = _cache.Get(_sessionKey);
|
try
|
||||||
if (data != null)
|
|
||||||
{
|
{
|
||||||
Deserialize(new MemoryStream(data));
|
var data = _cache.Get(_sessionKey);
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
Deserialize(new MemoryStream(data));
|
||||||
|
}
|
||||||
|
else if (!_isNewSessionKey)
|
||||||
|
{
|
||||||
|
_logger.AccessingExpiredSession(_sessionKey);
|
||||||
|
}
|
||||||
|
_isAvailable = true;
|
||||||
}
|
}
|
||||||
else if (!_isNewSessionKey)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.AccessingExpiredSession(_sessionKey);
|
_logger.SessionCacheReadException(_sessionKey, exception);
|
||||||
|
_isAvailable = false;
|
||||||
|
_sessionId = string.Empty;
|
||||||
|
_sessionIdBytes = null;
|
||||||
|
_store = new NoOpSessionStore();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loaded = true;
|
||||||
}
|
}
|
||||||
_loaded = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should throw if called directly, but most other places it should fail silently
|
// This will throw if called directly and a failure occurs. The user is expected to handle the failures.
|
||||||
// (e.g. TryGetValue should just return null).
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
{
|
{
|
||||||
if (!_loaded)
|
if (!_loaded)
|
||||||
|
|
@ -183,6 +207,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
_logger.AccessingExpiredSession(_sessionKey);
|
_logger.AccessingExpiredSession(_sessionKey);
|
||||||
}
|
}
|
||||||
|
_isAvailable = true;
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,24 +216,32 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
if (_isModified)
|
if (_isModified)
|
||||||
{
|
{
|
||||||
var data = await _cache.GetAsync(_sessionKey);
|
if (_logger.IsEnabled(LogLevel.Information))
|
||||||
if (_logger.IsEnabled(LogLevel.Information) && data == null)
|
|
||||||
{
|
{
|
||||||
_logger.SessionStarted(_sessionKey, Id);
|
try
|
||||||
|
{
|
||||||
|
var data = await _cache.GetAsync(_sessionKey);
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
_logger.SessionStarted(_sessionKey, Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.SessionCacheReadException(_sessionKey, exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_isModified = false;
|
|
||||||
|
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
Serialize(stream);
|
Serialize(stream);
|
||||||
|
|
||||||
await _cache.SetAsync(
|
await _cache.SetAsync(
|
||||||
_sessionKey,
|
_sessionKey,
|
||||||
stream.ToArray(),
|
stream.ToArray(),
|
||||||
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));
|
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
_isModified = false;
|
||||||
{
|
_logger.SessionStored(_sessionKey, Id, _store.Count);
|
||||||
_logger.SessionStored(_sessionKey, Id, _store.Count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -245,7 +278,6 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
if (content == null || content.ReadByte() != SerializationRevision)
|
if (content == null || content.ReadByte() != SerializationRevision)
|
||||||
{
|
{
|
||||||
// TODO: Throw?
|
|
||||||
// Replace the un-readable format.
|
// Replace the un-readable format.
|
||||||
_isModified = true;
|
_isModified = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -333,77 +365,5 @@ namespace Microsoft.AspNetCore.Session
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys are stored in their utf-8 encoded state.
|
|
||||||
// This saves us from de-serializing and re-serializing every key on every request.
|
|
||||||
private class EncodedKey
|
|
||||||
{
|
|
||||||
private string _keyString;
|
|
||||||
private int? _hashCode;
|
|
||||||
|
|
||||||
internal EncodedKey(string key)
|
|
||||||
{
|
|
||||||
_keyString = key;
|
|
||||||
KeyBytes = Encoding.UTF8.GetBytes(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncodedKey(byte[] key)
|
|
||||||
{
|
|
||||||
KeyBytes = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal string KeyString
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_keyString == null)
|
|
||||||
{
|
|
||||||
_keyString = Encoding.UTF8.GetString(KeyBytes, 0, KeyBytes.Length);
|
|
||||||
}
|
|
||||||
return _keyString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal byte[] KeyBytes { get; private set; }
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
var otherKey = obj as EncodedKey;
|
|
||||||
if (otherKey == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (KeyBytes.Length != otherKey.KeyBytes.Length)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_hashCode.HasValue && otherKey._hashCode.HasValue
|
|
||||||
&& _hashCode.Value != otherKey._hashCode.Value)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < KeyBytes.Length; i++)
|
|
||||||
{
|
|
||||||
if (KeyBytes[i] != otherKey.KeyBytes[i])
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
if (!_hashCode.HasValue)
|
|
||||||
{
|
|
||||||
_hashCode = SipHash.GetHashCode(KeyBytes);
|
|
||||||
}
|
|
||||||
return _hashCode.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return KeyString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,14 +29,6 @@ namespace Microsoft.AspNetCore.Session
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAvailable
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return true; // TODO:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
|
public ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(sessionKey))
|
if (string.IsNullOrEmpty(sessionKey))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Session
|
||||||
|
{
|
||||||
|
// Keys are stored in their utf-8 encoded state.
|
||||||
|
// This saves us from de-serializing and re-serializing every key on every request.
|
||||||
|
internal class EncodedKey
|
||||||
|
{
|
||||||
|
private string _keyString;
|
||||||
|
private int? _hashCode;
|
||||||
|
|
||||||
|
internal EncodedKey(string key)
|
||||||
|
{
|
||||||
|
_keyString = key;
|
||||||
|
KeyBytes = Encoding.UTF8.GetBytes(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncodedKey(byte[] key)
|
||||||
|
{
|
||||||
|
KeyBytes = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string KeyString
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_keyString == null)
|
||||||
|
{
|
||||||
|
_keyString = Encoding.UTF8.GetString(KeyBytes, 0, KeyBytes.Length);
|
||||||
|
}
|
||||||
|
return _keyString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal byte[] KeyBytes { get; private set; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var otherKey = obj as EncodedKey;
|
||||||
|
if (otherKey == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (KeyBytes.Length != otherKey.KeyBytes.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_hashCode.HasValue && otherKey._hashCode.HasValue
|
||||||
|
&& _hashCode.Value != otherKey._hashCode.Value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < KeyBytes.Length; i++)
|
||||||
|
{
|
||||||
|
if (KeyBytes[i] != otherKey.KeyBytes[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
if (!_hashCode.HasValue)
|
||||||
|
{
|
||||||
|
_hashCode = SipHash.GetHashCode(KeyBytes);
|
||||||
|
}
|
||||||
|
return _hashCode.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return KeyString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,6 @@ namespace Microsoft.AspNetCore.Session
|
||||||
{
|
{
|
||||||
public interface ISessionStore
|
public interface ISessionStore
|
||||||
{
|
{
|
||||||
bool IsAvailable { get; }
|
|
||||||
|
|
||||||
ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey);
|
ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ namespace Microsoft.Extensions.Logging
|
||||||
private static Action<ILogger, string, 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> _sessionLoaded;
|
||||||
private static Action<ILogger, string, string, int, Exception> _sessionStored;
|
private static Action<ILogger, string, string, int, Exception> _sessionStored;
|
||||||
|
private static Action<ILogger, string, Exception> _sessionCacheReadException;
|
||||||
private static Action<ILogger, Exception> _errorUnprotectingCookie;
|
private static Action<ILogger, Exception> _errorUnprotectingCookie;
|
||||||
|
|
||||||
static LoggingExtensions()
|
static LoggingExtensions()
|
||||||
|
|
@ -23,7 +24,7 @@ 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; Key:{sessionKey}");
|
formatString: "Accessing expired session, Key:{sessionKey}");
|
||||||
_sessionStarted = LoggerMessage.Define<string, string>(
|
_sessionStarted = LoggerMessage.Define<string, string>(
|
||||||
eventId: 3,
|
eventId: 3,
|
||||||
logLevel: LogLevel.Information,
|
logLevel: LogLevel.Information,
|
||||||
|
|
@ -36,8 +37,12 @@ namespace Microsoft.Extensions.Logging
|
||||||
eventId: 5,
|
eventId: 5,
|
||||||
logLevel: LogLevel.Debug,
|
logLevel: LogLevel.Debug,
|
||||||
formatString: "Session stored; Key:{sessionKey}, Id:{sessionId}, Count:{count}");
|
formatString: "Session stored; Key:{sessionKey}, Id:{sessionId}, Count:{count}");
|
||||||
_errorUnprotectingCookie = LoggerMessage.Define(
|
_sessionCacheReadException = LoggerMessage.Define<string>(
|
||||||
eventId: 6,
|
eventId: 6,
|
||||||
|
logLevel: LogLevel.Error,
|
||||||
|
formatString: "Session cache read exception, Key:{sessionKey}");
|
||||||
|
_errorUnprotectingCookie = LoggerMessage.Define(
|
||||||
|
eventId: 7,
|
||||||
logLevel: LogLevel.Warning,
|
logLevel: LogLevel.Warning,
|
||||||
formatString: "Error unprotecting the session cookie.");
|
formatString: "Error unprotecting the session cookie.");
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +72,11 @@ namespace Microsoft.Extensions.Logging
|
||||||
_sessionStored(logger, sessionKey, sessionId, count, null);
|
_sessionStored(logger, sessionKey, sessionId, count, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SessionCacheReadException(this ILogger logger, string sessionKey, Exception exception)
|
||||||
|
{
|
||||||
|
_sessionCacheReadException(logger, sessionKey, exception);
|
||||||
|
}
|
||||||
|
|
||||||
public static void ErrorUnprotectingSessionCookie(this ILogger logger, Exception exception)
|
public static void ErrorUnprotectingSessionCookie(this ILogger logger, Exception exception)
|
||||||
{
|
{
|
||||||
_errorUnprotectingCookie(logger, exception);
|
_errorUnprotectingCookie(logger, exception);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Session
|
||||||
|
{
|
||||||
|
internal class NoOpSessionStore : IDictionary<EncodedKey, byte[]>
|
||||||
|
{
|
||||||
|
public byte[] this[EncodedKey key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count { get; } = 0;
|
||||||
|
|
||||||
|
public bool IsReadOnly { get; } = false;
|
||||||
|
|
||||||
|
public ICollection<EncodedKey> Keys { get; } = new EncodedKey[0];
|
||||||
|
|
||||||
|
public ICollection<byte[]> Values { get; } = new byte[0][];
|
||||||
|
|
||||||
|
public void Add(KeyValuePair<EncodedKey, byte[]> item) { }
|
||||||
|
|
||||||
|
public void Add(EncodedKey key, byte[] value) { }
|
||||||
|
|
||||||
|
public void Clear() { }
|
||||||
|
|
||||||
|
public bool Contains(KeyValuePair<EncodedKey, byte[]> item) => false;
|
||||||
|
|
||||||
|
public bool ContainsKey(EncodedKey key) => false;
|
||||||
|
|
||||||
|
public void CopyTo(KeyValuePair<EncodedKey, byte[]>[] array, int arrayIndex) { }
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<EncodedKey, byte[]>> GetEnumerator() => Enumerable.Empty<KeyValuePair<EncodedKey, byte[]>>().GetEnumerator();
|
||||||
|
|
||||||
|
public bool Remove(KeyValuePair<EncodedKey, byte[]> item) => false;
|
||||||
|
|
||||||
|
public bool Remove(EncodedKey key) => false;
|
||||||
|
|
||||||
|
public bool TryGetValue(EncodedKey key, out byte[] value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,7 +40,6 @@ namespace Microsoft.AspNetCore.Session
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
@ -72,7 +71,6 @@ namespace Microsoft.AspNetCore.Session
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
@ -111,9 +109,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -166,9 +162,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
.ConfigureServices(
|
.ConfigureServices(
|
||||||
services =>
|
services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -219,9 +213,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -258,10 +250,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
||||||
|
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -310,10 +299,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
||||||
|
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession(o => o.IdleTimeout = TimeSpan.FromMilliseconds(30));
|
services.AddSession(o => o.IdleTimeout = TimeSpan.FromMilliseconds(30));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -373,10 +359,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddSingleton(typeof(ILoggerFactory), new NullLoggerFactory());
|
services.AddSingleton(typeof(ILoggerFactory), new NullLoggerFactory());
|
||||||
|
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession(o => o.IdleTimeout = TimeSpan.FromMinutes(20));
|
services.AddSession(o => o.IdleTimeout = TimeSpan.FromMinutes(20));
|
||||||
services.Configure<MemoryCacheOptions>(o => o.Clock = clock);
|
services.Configure<MemoryCacheOptions>(o => o.Clock = clock);
|
||||||
});
|
});
|
||||||
|
|
@ -426,9 +409,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -471,9 +452,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -502,9 +481,7 @@ namespace Microsoft.AspNetCore.Session
|
||||||
})
|
})
|
||||||
.ConfigureServices(services =>
|
.ConfigureServices(services =>
|
||||||
{
|
{
|
||||||
services.AddMemoryCache();
|
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -516,6 +493,132 @@ namespace Microsoft.AspNetCore.Session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SessionLogsCacheReadException()
|
||||||
|
{
|
||||||
|
var sink = new TestSink();
|
||||||
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseSession();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
byte[] value;
|
||||||
|
Assert.False(context.Session.TryGetValue("key", out value));
|
||||||
|
Assert.Equal(null, value);
|
||||||
|
Assert.Equal(string.Empty, context.Session.Id);
|
||||||
|
Assert.False(context.Session.Keys.Any());
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
||||||
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
||||||
|
{
|
||||||
|
DisableGet = true
|
||||||
|
});
|
||||||
|
services.AddSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var response = await client.GetAsync(string.Empty);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var sessionLogMessages = sink.Writes.OnlyMessagesFromSource<DistributedSession>().ToArray();
|
||||||
|
|
||||||
|
Assert.Equal(1, sessionLogMessages.Length);
|
||||||
|
Assert.Contains("Session cache read exception", sessionLogMessages[0].State.ToString());
|
||||||
|
Assert.Equal(LogLevel.Error, sessionLogMessages[0].LogLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SessionLogsCacheWriteException()
|
||||||
|
{
|
||||||
|
var sink = new TestSink();
|
||||||
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseSession();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
context.Session.SetInt32("key", 0);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
||||||
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
||||||
|
{
|
||||||
|
DisableSetAsync = true
|
||||||
|
});
|
||||||
|
services.AddSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var response = await client.GetAsync(string.Empty);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var sessionLogMessages = sink.Writes.OnlyMessagesFromSource<DistributedSession>().ToArray();
|
||||||
|
|
||||||
|
Assert.Equal(1, sessionLogMessages.Length);
|
||||||
|
Assert.Contains("Session started", sessionLogMessages[0].State.ToString());
|
||||||
|
Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel);
|
||||||
|
|
||||||
|
var sessionMiddlewareLogMessages = sink.Writes.OnlyMessagesFromSource<SessionMiddleware>().ToArray();
|
||||||
|
Assert.Equal(1, sessionMiddlewareLogMessages.Length);
|
||||||
|
Assert.Contains("Error closing the session.", sessionMiddlewareLogMessages[0].State.ToString());
|
||||||
|
Assert.Equal(LogLevel.Error, sessionMiddlewareLogMessages[0].LogLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SessionLogsCacheRefreshException()
|
||||||
|
{
|
||||||
|
var sink = new TestSink();
|
||||||
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||||
|
var builder = new WebHostBuilder()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UseSession();
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
// The middleware calls context.Session.CommitAsync() once per request
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
||||||
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
||||||
|
{
|
||||||
|
DisableRefreshAsync = true
|
||||||
|
});
|
||||||
|
services.AddSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var response = await client.GetAsync(string.Empty);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var sessionLogMessages = sink.Writes.OnlyMessagesFromSource<SessionMiddleware>().ToArray();
|
||||||
|
|
||||||
|
Assert.Equal(1, sessionLogMessages.Length);
|
||||||
|
Assert.Contains("Error closing the session.", sessionLogMessages[0].State.ToString());
|
||||||
|
Assert.Equal(LogLevel.Error, sessionLogMessages[0].LogLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TestClock : ISystemClock
|
private class TestClock : ISystemClock
|
||||||
{
|
{
|
||||||
public TestClock()
|
public TestClock()
|
||||||
|
|
@ -531,46 +634,47 @@ namespace Microsoft.AspNetCore.Session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDistributedCache : IDistributedCache
|
private class UnreliableCache : IDistributedCache
|
||||||
{
|
{
|
||||||
|
private readonly MemoryDistributedCache _cache;
|
||||||
|
|
||||||
|
public bool DisableGet { get; set; }
|
||||||
|
public bool DisableSetAsync { get; set; }
|
||||||
|
public bool DisableRefreshAsync { get; set; }
|
||||||
|
|
||||||
|
public UnreliableCache(IMemoryCache memoryCache)
|
||||||
|
{
|
||||||
|
_cache = new MemoryDistributedCache(memoryCache);
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] Get(string key)
|
public byte[] Get(string key)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (DisableGet)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
return _cache.Get(key);
|
||||||
}
|
}
|
||||||
|
public Task<byte[]> GetAsync(string key) => _cache.GetAsync(key);
|
||||||
public Task<byte[]> GetAsync(string key)
|
public void Refresh(string key) => _cache.Refresh(key);
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Refresh(string key)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task RefreshAsync(string key)
|
public Task RefreshAsync(string key)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (DisableRefreshAsync)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
return _cache.RefreshAsync(key);
|
||||||
}
|
}
|
||||||
|
public void Remove(string key) => _cache.Remove(key);
|
||||||
public void Remove(string key)
|
public Task RemoveAsync(string key) => _cache.RemoveAsync(key);
|
||||||
{
|
public void Set(string key, byte[] value, DistributedCacheEntryOptions options) => _cache.Set(key, value, options);
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task RemoveAsync(string key)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options)
|
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (DisableSetAsync)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
return _cache.SetAsync(key, value, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue