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
36 changes: 36 additions & 0 deletions src/NRedisStack/NRedisStackConfigurationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

namespace NRedisStack
{
public class NRedisStackConfigurationOptions
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly ConfigurationOptions _configurationOptions = new ConfigurationOptions();

public NRedisStackConfigurationOptions(string redisConnectionString)
{
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
_configurationOptions = RedisUriParser.FromUri(redisConnectionString);
SetLibName(_configurationOptions);
}


NRedisStackConfigurationOptions()
{
SetLibName(_configurationOptions);
}

public ConfigurationOptions GetConfigurationOptions()
{
return _configurationOptions;
}

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}";
}

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

namespace NRedisStack
{
public static class NRedisStackConnectionMultiplexer // check if I can use this name to ConnectionMultiplexer
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
{
public static ConnectionMultiplexer Connect(string redisConnectionString)
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
{
var options = new NRedisStackConfigurationOptions(redisConnectionString);
return Connect(options);
}

public static Task<ConnectionMultiplexer> ConnectAsync(string redisConnectionString)
{
var options = new NRedisStackConfigurationOptions(redisConnectionString);
return ConnectAsync(options);
}
public static ConnectionMultiplexer Connect(NRedisStackConfigurationOptions options)
{
return ConnectionMultiplexer.Connect(options.GetConfigurationOptions());
}
public static Task<ConnectionMultiplexer> ConnectAsync(NRedisStackConfigurationOptions options)
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
{
return ConnectionMultiplexer.ConnectAsync(options.GetConfigurationOptions());
}
}
}
120 changes: 120 additions & 0 deletions src/NRedisStack/RedisUriParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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)}
// TODO: add more options
};

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

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

}
}