Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

restore ISerializer ability, "safely" #218

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c0e9294
restore ISerializer ability, "safely"
zachrybaker Dec 18, 2023
34e33cf
make suggested changes
stanleysmall-microsoft Jan 12, 2024
4b45e2d
whitespace changes
stanleysmall-microsoft Jan 12, 2024
4661de2
update all the packages
stanleysmall-microsoft Jan 12, 2024
8f0eeec
make philo requested changes
stanleysmall-microsoft Jan 12, 2024
078989d
remove sessiondatatype from keygenerator
stanleysmall-microsoft Jan 12, 2024
a709789
update SE redis again
stanleysmall-microsoft Jan 19, 2024
e185256
add unit tests for interface and implementation
stanleysmall-microsoft Jan 25, 2024
adc6940
split ProviderConfiguration into two implementations
stanleysmall-microsoft Jan 30, 2024
6a0bd99
refactor so code is not repeated
stanleysmall-microsoft Jan 30, 2024
e1c6585
Merge branch 'Azure:main' into custom-serialization
zachrybaker Mar 11, 2024
1cd58b8
update packages
stanleysmall-microsoft Jun 5, 2024
5dba4dc
simplify code in middleware tests
stanleysmall-microsoft Jun 5, 2024
dbb3c48
update whitespace
stanleysmall-microsoft Jun 5, 2024
e46df4c
add a functional test that provides its own ISessionStateSerializer
stanleysmall-microsoft Jun 5, 2024
a53ecaf
remove changes for output cache
stanleysmall-microsoft Jun 5, 2024
2a65e87
add copyright statement to files
stanleysmall-microsoft Jun 5, 2024
835f42c
Update xunit so tests run
stanleysmall-microsoft Jun 5, 2024
f966758
Change config name to redisSerializerType
stanleysmall-microsoft Jun 5, 2024
88ca0f4
Added a serialization suffix for the serializer type
stanleysmall-microsoft Jun 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/OutputCacheProvider/RedisOutputCacheConnectionWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using StackExchange.Redis;
using System;
using System.IO;
using System.Web.Caching;
Expand All @@ -15,9 +16,9 @@ internal class RedisOutputCacheConnectionWrapper : IOutputCacheConnection
private static object lockForSharedConnection = new object();

internal IRedisClientConnection redisConnection;
private ProviderConfiguration configuration;
private OutputCacheProviderConfiguration configuration;

public RedisOutputCacheConnectionWrapper(ProviderConfiguration configuration)
public RedisOutputCacheConnectionWrapper(OutputCacheProviderConfiguration configuration)
{
this.configuration = configuration;

Expand Down Expand Up @@ -119,5 +120,11 @@ private object DeserializeOutputCacheEntry(byte[] serializedOutputCacheEntry)
return null;
}
}

public byte[] GetOutputCacheDataFromResult(object rowDataFromRedis)
{
RedisResult rowDataAsRedisResult = (RedisResult)rowDataFromRedis;
return (byte[])rowDataAsRedisResult;
}
}
}
4 changes: 2 additions & 2 deletions src/OutputCacheProvider/RedisOutputCacheProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.Web.Redis

public class RedisOutputCacheProvider : OutputCacheProviderAsync
{
internal static ProviderConfiguration configuration;
internal static OutputCacheProviderConfiguration configuration;
internal static object configurationCreationLock = new object();
internal IOutputCacheConnection cache;

Expand Down Expand Up @@ -42,7 +42,7 @@ public override void Initialize(string name, System.Collections.Specialized.Name
{
if (configuration == null)
{
configuration = ProviderConfiguration.ProviderConfigurationForOutputCache(config);
configuration = new OutputCacheProviderConfiguration(config);
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions src/OutputCacheProvider/RedisOutputCacheProviderConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using System;
using System.Collections.Specialized;
using static Microsoft.Web.Redis.ProviderConfigurationExtension;

namespace Microsoft.Web.Redis
{
internal class OutputCacheProviderConfiguration : IProviderConfiguration
{
public TimeSpan RequestTimeout { get; set; }
public TimeSpan SessionTimeout { get; set; }
public int Port { get; set; }
public string Host { get; set; }
public string AccessKey { get; set; }
public TimeSpan RetryTimeout { get; set; }
public bool ThrowOnError { get; set; }
public bool UseSsl { get; set; }
public int DatabaseId { get; set; }
public string ApplicationName { get; set; }
public int ConnectionTimeoutInMilliSec { get; set; }
public int OperationTimeoutInMilliSec { get; set; }
public string ConnectionString { get; set; }

internal OutputCacheProviderConfiguration(NameValueCollection config)
{
GetIProviderConfiguration(config, this);

// No retry login for output cache provider
RetryTimeout = TimeSpan.Zero;

// Session state specific attribute which are not applicable to output cache
ThrowOnError = true;
RequestTimeout = TimeSpan.Zero;
SessionTimeout = TimeSpan.Zero;

LogUtility.LogInfo($"Host: {Host}, Port: {Port}, UseSsl: {UseSsl}, DatabaseId: {DatabaseId}, ApplicationName: {ApplicationName}");
}
}
}
43 changes: 43 additions & 0 deletions src/RedisSessionStateProvider/DefaultSessionStateSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using System.IO;
using System.Web.SessionState;

namespace Microsoft.Web.RedisSessionStateProvider
{
/// <summary>
/// Provides methods to serialize and deserialize session state data using binary serialization.
/// This is the default implementation of the ISessionDataSerializer class.
/// The implementation uses the serialization and deserialization methods provided by the SessionStateItemCollection class.
/// </summary>
internal class DefaultSessionStateSerializer : ISessionStateSerializer
{
/// <summary>
/// Deserializes the session state data.
/// </summary>
/// <param name="data">The serialized session state data as a byte array.</param>
/// <returns>The deserialized session state data.</returns>
public SessionStateItemCollection Deserialize(byte[] data)
{
MemoryStream ms = new MemoryStream(data);
BinaryReader reader = new BinaryReader(ms);
return SessionStateItemCollection.Deserialize(reader);
}

/// <summary>
/// Serializes the session state data.
/// </summary>
/// <param name="data">The session state data to serialize.</param>
/// <returns>The serialized session state data as a byte array.</returns>
public byte[] Serialize(SessionStateItemCollection data)
{
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter(ms);
data.Serialize(writer);
writer.Close();
return ms.ToArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Microsoft.Web.Redis
{
internal interface ICacheConnection
internal interface ISessionStateConnection
{
KeyGenerator Keys { get; set; }
void Set(ISessionStateItemCollection data, int sessionTimeout);
Expand Down
25 changes: 25 additions & 0 deletions src/RedisSessionStateProvider/ISessionStateSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using System.Web.SessionState;

/// <summary>
/// Provides methods to serialize and deserialize session state data.
/// </summary>
public interface ISessionStateSerializer
{
/// <summary>
/// Serializes the session state data.
/// </summary>
/// <param name="data">The session state data to serialize.</param>
/// <returns>The serialized session state data as a byte array.</returns>
byte[] Serialize(SessionStateItemCollection data);

/// <summary>
/// Deserializes the session state data.
/// </summary>
/// <param name="data">The serialized session state data as a byte array.</param>
/// <returns>The deserialized session state data.</returns>
SessionStateItemCollection Deserialize(byte[] data);
}
14 changes: 6 additions & 8 deletions src/RedisSessionStateProvider/KeyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using System;

namespace Microsoft.Web.Redis
{
internal class KeyGenerator
Expand All @@ -14,24 +12,24 @@ internal class KeyGenerator
public string LockKey { get; private set; }
public string InternalKey { get; private set; }

private void GenerateKeys(string id, string app)
private void GenerateKeys(string id, string app, string serializationSuffix)
{
this.id = id;
DataKey = $"{{{app}_{id}}}_SessionStateItemCollection";
DataKey = $"{{{app}_{id}}}_SessionStateItemCollection{serializationSuffix}";
LockKey = $"{{{app}_{id}}}_WriteLock";
InternalKey = $"{{{app}_{id}}}_SessionTimeout";
}

public KeyGenerator(string sessionId, string applicationName)
public KeyGenerator(string sessionId, string applicationName, string serializationSuffix)
{
GenerateKeys(sessionId, applicationName);
GenerateKeys(sessionId, applicationName, serializationSuffix);
}

public void RegenerateKeyStringIfIdModified(string sessionId, string applicationName)
public void RegenerateKeyStringIfIdModified(string sessionId, string applicationName, string serializationSuffix)
{
if (!sessionId.Equals(this.id))
{
GenerateKeys(sessionId, applicationName);
GenerateKeys(sessionId, applicationName, serializationSuffix);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Web.SessionState;

namespace Microsoft.Web.Redis
{
internal class RedisConnectionWrapper : ICacheConnection
internal class RedisSessionStateConnectionWrapper : ISessionStateConnection
{
internal static RedisSharedConnection sharedConnection;
private static object lockForSharedConnection = new object();

public KeyGenerator Keys { set; get; }

internal IRedisClientConnection redisConnection;
private ProviderConfiguration configuration;
private SessionStateProviderConfiguration configuration;

public RedisConnectionWrapper(ProviderConfiguration configuration, string id)
public RedisSessionStateConnectionWrapper(SessionStateProviderConfiguration configuration, string id)
{
this.configuration = configuration;
Keys = new KeyGenerator(id, configuration.ApplicationName);
Keys = new KeyGenerator(id, configuration.ApplicationName, configuration.SerializationSuffixForKeys);

// only single object of RedisSharedConnection will be created and then reused
if (sharedConnection == null)
Expand Down Expand Up @@ -127,11 +128,8 @@ internal byte[] SerializeSessionStateItemCollection(ISessionStateItemCollection
{
return null;
}
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter(ms);
((SessionStateItemCollection)sessionStateItemCollection).Serialize(writer);
writer.Close();
return ms.ToArray();

return configuration.SessionStateSerializer.Serialize((SessionStateItemCollection)sessionStateItemCollection);
}

public void Set(ISessionStateItemCollection data, int sessionTimeout)
Expand All @@ -149,10 +147,10 @@ public void Set(ISessionStateItemCollection data, int sessionTimeout)
/*-------Start of Lock set operation-----------------------------------------------------------------------------------------------------------------------------------------------*/

// KEYS = { write-lock-id, data-id, internal-id }
// ARGV = { write-lock-value-that-we-want-to-set, request-timout }
// ARGV = { write-lock-value-that-we-want-to-set, request-timeout }
// lockValue = 1) (Initially) write lock value that we want to set (ARGV[1]) if we get lock successfully this will return as retArray[1]
// 2) If another write lock exists than its lock value from cache
// retArray = {lockValue , session data if lock was taken successfully, session timeout value if exists, wheather lock was taken or not}
// retArray = {lockValue , session data if lock was taken successfully, session timeout value if exists, whether lock was taken or not}
private static readonly string writeLockAndGetDataScript = (@"
local retArray = {}
local lockValue = ARGV[1]
Expand Down Expand Up @@ -199,7 +197,7 @@ public bool TryTakeWriteLockAndGetData(DateTime lockTime, int lockTimeout, out o
if (!isLocked && lockId.ToString().Equals(expectedLockId))
{
ret = true;
data = redisConnection.GetSessionData(rowDataFromRedis);
data = GetSessionData(rowDataFromRedis);
}
return ret;
}
Expand Down Expand Up @@ -247,7 +245,7 @@ public bool TryCheckWriteLockAndGetData(out object lockId, out ISessionStateItem
// If lockId = "" means no lock exists and we got data from store.
lockId = null;
ret = true;
data = redisConnection.GetSessionData(rowDataFromRedis);
data = GetSessionData(rowDataFromRedis);
}
return ret;
}
Expand Down Expand Up @@ -371,5 +369,44 @@ public void TryUpdateAndReleaseLock(object lockId, ISessionStateItemCollection d
}

/*-------End of TryUpdateIfLockIdMatch operation-----------------------------------------------------------------------------------------------------------------------------------------------*/

internal SessionStateItemCollection DeserializeSessionStateItemCollection(RedisResult serializedSessionStateItemCollection)
{
try
{
var bytes = (byte[])serializedSessionStateItemCollection;
if (bytes is null)
{
return null;
}
return configuration.SessionStateSerializer.Deserialize(bytes);

}
catch
{
return null;
}
}

public virtual ISessionStateItemCollection GetSessionData(object rowDataFromRedis)
{

RedisResult rowDataAsRedisResult = (RedisResult)rowDataFromRedis;
RedisResult[] lockScriptReturnValueArray = (RedisResult[])rowDataAsRedisResult;
Debug.Assert(lockScriptReturnValueArray != null);

SessionStateItemCollection sessionData = null;
if (lockScriptReturnValueArray.Length > 1 && lockScriptReturnValueArray[1] != null)
{
RedisResult data = lockScriptReturnValueArray[1];
var serializedSessionStateItemCollection = data;

if (serializedSessionStateItemCollection != null)
{
sessionData = DeserializeSessionStateItemCollection(serializedSessionStateItemCollection);
}
}
return sessionData;
}
}
}
Loading