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

Support Redis URI #208

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
41 changes: 0 additions & 41 deletions src/NRedisStack/Auxiliary.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
using NRedisStack.Core;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

namespace NRedisStack
{
public static class Auxiliary
{
private static string? _libraryName = $"NRedisStack;.NET-{Environment.Version}";
private static bool _setInfo = true;
public static void ResetInfoDefaults()
{
_setInfo = true;
_libraryName = $"NRedisStack;.NET-{Environment.Version}";
}
public static List<object> MergeArgs(RedisKey key, params RedisValue[] items)
{
var args = new List<object>(items.Length + 1) { key };
Expand All @@ -36,48 +28,15 @@ public static object[] AssembleNonNullArguments(params object?[] arguments)

// public static IDatabase GetDatabase(this ConnectionMultiplexer redis) => redis.GetDatabase("", "");

// TODO: add all the signatures of GetDatabase
public static IDatabase GetDatabase(this ConnectionMultiplexer redis,
string? LibraryName)
{
var _db = redis.GetDatabase();
if (LibraryName == null) // the user wants to disable the library name and version sending
_setInfo = false;

else // the user set his own the library name
_libraryName = $"NRedisStack({LibraryName});.NET-{Environment.Version})";

return _db;
}

private static void SetInfoInPipeline(this IDatabase db)
{
if (_libraryName == null) return;
Pipeline pipeline = new Pipeline(db);
_ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryName, _libraryName!);
_ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion());
pipeline.Execute();
}

public static RedisResult Execute(this IDatabase db, SerializedCommand command)
{
var compareVersions = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints()[0]).Version.CompareTo(new Version(7, 1, 242));
if (_setInfo && compareVersions >= 0)
{
_setInfo = false;
db.SetInfoInPipeline();
}
return db.Execute(command.Command, command.Args);
}

public async static Task<RedisResult> ExecuteAsync(this IDatabaseAsync db, SerializedCommand command)
{
var compareVersions = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints()[0]).Version.CompareTo(new Version(7, 1, 242));
if (_setInfo && compareVersions >= 0)
{
_setInfo = false;
((IDatabase)db).SetInfoInPipeline();
}
return await db.ExecuteAsync(command.Command, command.Args);
}

Expand Down
47 changes: 47 additions & 0 deletions src/NRedisStack/Configuration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

namespace NRedisStack
{
public class Configuration
{
private ConfigurationOptions _options = new ConfigurationOptions();

public static Configuration Parse(string redisConnectionString) =>
new Configuration().DoParse(redisConnectionString);

public Configuration DoParse(string redisConnectionString)
{
try // Redis URI parsing
{
_options = RedisUriParser.FromUri(redisConnectionString);
}
catch (UriFormatException) // StackExchange.Redis connection string parsing
{
_options = ConfigurationOptions.Parse(redisConnectionString);
}
SetLibName(_options);
return this;
}


Configuration()
{
SetLibName(_options);
}

public ConfigurationOptions GetOptions()
{
return _options;
}

private static void SetLibName(ConfigurationOptions options)
{
if (options.LibraryName != null) // the user set his own the library name
options.LibraryName = $"NRedisStack({options.LibraryName});.NET-{Environment.Version})";
else // the default library name and version sending
options.LibraryName = $"NRedisStack;.NET-{Environment.Version}";
}

}
}
37 changes: 37 additions & 0 deletions src/NRedisStack/ConnectionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using StackExchange.Redis;

namespace NRedisStack
{
public static class ConnectionManager
{
public static IConnectionMultiplexer Connect(string redisConnectionString)
{
return Connect(Configuration.Parse(redisConnectionString));
}

public static async Task<IConnectionMultiplexer> ConnectAsync(string redisConnectionString)
{
return await ConnectAsync(Configuration.Parse(redisConnectionString));
}

public static IConnectionMultiplexer Connect(Configuration options)
{
return ConnectionMultiplexer.Connect(options.GetOptions());
}

public static async Task<IConnectionMultiplexer> ConnectAsync(Configuration options)
{
return await ConnectionMultiplexer.ConnectAsync(options.GetOptions());
}

public static IConnectionMultiplexer Connect(ConfigurationOptions options)
{
return ConnectionMultiplexer.Connect(options);
}

public static async Task<IConnectionMultiplexer> ConnectAsync(ConfigurationOptions options)
{
return await ConnectionMultiplexer.ConnectAsync(options);
}
}
}
2 changes: 1 addition & 1 deletion src/NRedisStack/NRedisStack.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<ItemGroup>
<PackageReference Include="NetTopologySuite" Version="2.5.0" />
<PackageReference Include="System.Text.Json" Version="7.0.2" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

Expand Down
134 changes: 134 additions & 0 deletions src/NRedisStack/RedisUriParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using StackExchange.Redis;

[assembly: InternalsVisibleTo("NRedisStack.Tests")]

namespace NRedisStack
{
/// <summary>
/// URI parsing utility.
/// </summary>
internal static class RedisUriParser
{
/// <summary>
/// Parses a Config options for StackExchange Redis from the URI.
/// </summary>
/// <param name="redisUri">Redis Uri string</param>
/// <returns>A configuration options result for SE.Redis.</returns>
internal static ConfigurationOptions FromUri(string redisUri)
{
var options = new ConfigurationOptions();

if (string.IsNullOrEmpty(redisUri))
{
options.EndPoints.Add("localhost:6379");
return options;
}

var uri = new Uri(redisUri);
ParseHost(options, uri);
ParseUserInfo(options, uri);
ParseQueryArguments(options, uri);
ParseDefaultDatabase(options, uri);
options.Ssl = uri.Scheme == "rediss";
options.AbortOnConnectFail = false;
return options;
}

private static void ParseDefaultDatabase(ConfigurationOptions options, Uri uri)
{
if (string.IsNullOrEmpty(uri.AbsolutePath))
{
return;
}

var dbNumStr = Regex.Match(uri.AbsolutePath, "[0-9]+").Value;
int dbNum;
if (int.TryParse(dbNumStr, out dbNum))
{
options.DefaultDatabase = dbNum;
}
}

private static IList<KeyValuePair<string, string>> ParseQuery(string query) =>
query.Split('&').Select(x =>
new KeyValuePair<string, string>(x.Split('=').First(), x.Split('=').Last())).ToList();

private static void ParseUserInfo(ConfigurationOptions options, Uri uri)
{
if (!string.IsNullOrEmpty(uri.UserInfo))
{
var userInfo = uri.UserInfo.Split(':');
if (userInfo.Length > 1)
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
{
options.User = Uri.UnescapeDataString(userInfo[0]);
options.Password = Uri.UnescapeDataString(userInfo[1]);
}
else
{
throw new FormatException(
"Username and password must be in the form username:password - if there is no username use the format :password");
}
}
}

private static void ParseHost(ConfigurationOptions options, Uri uri)
{
var port = uri.Port >= 0 ? uri.Port : 6379;
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
var host = !string.IsNullOrEmpty(uri.Host) ? uri.Host : "localhost";
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
options.EndPoints.Add($"{host}:{port}");
}

private static void ParseQueryArguments(ConfigurationOptions options, Uri uri)
{
if (string.IsNullOrEmpty(uri.Query))
{
return;
}

var queryArgs = ParseQuery(uri.Query.Substring(1));

var actions = new Dictionary<string, Action<string>>(StringComparer.OrdinalIgnoreCase)
{
{ "timeout", value => SetTimeoutOptions(options, value) },
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
{ "clientname", value => options.ClientName = value },
{ "sentinel_primary_name", value => options.ServiceName = value },
{ "endpoint", value => options.EndPoints.Add(value) },
{ "allowadmin", value => options.AllowAdmin = bool.Parse(value) },
{ "abortConnect", value => options.AbortOnConnectFail = bool.Parse(value) },
{ "asynctimeout", value => options.AsyncTimeout = int.Parse(value) },
{ "retry", value => options.ConnectRetry = int.Parse(value) },
{ "protocol", value => ParseRedisProtocol(options, value) }
// TODO: add more options
};

foreach (var arg in queryArgs.Where(arg => actions.ContainsKey(arg.Key)))
{
actions[arg.Key.ToLower()](arg.Value);
}
}

private static RedisProtocol ParseRedisProtocol(ConfigurationOptions options, string value)
{
switch (value)
{
case "2":
return RedisProtocol.Resp2;
case "3":
return RedisProtocol.Resp3;
default:
throw new FormatException("Invalid protocol specified");
}
}

private static void SetTimeoutOptions(ConfigurationOptions options, string value)
{
var timeout = int.Parse(value);
options.AsyncTimeout = timeout;
options.SyncTimeout = timeout;
options.ConnectTimeout = timeout;
}

}
}
2 changes: 1 addition & 1 deletion tests/Doc/Doc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\NRedisStack\NRedisStack.csproj" />
Expand Down
Loading
Loading