Skip to content

Commit

Permalink
✨ JsonRPC 服务器
Browse files Browse the repository at this point in the history
Signed-off-by: 舰队的偶像-岛风酱! <[email protected]>
  • Loading branch information
frg2089 committed May 6, 2023
1 parent 52a1dea commit 75cd2a2
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/Shimakaze.Sdk.JsonRPC.Server/HandlerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Shimakaze.Sdk.JsonRPC.Server;

/// <summary>
/// 表示这是一个JsonRpc Handler
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class HandlerAttribute : Attribute
{
/// <summary>
/// 这个Handler中的方法/事件的路径
/// </summary>
public string? Route { get; set; }

/// <summary>
/// 表示这是一个JsonRpc Handler
/// </summary>
/// <param name="route">这个Handler中的方法/事件的路径</param>
public HandlerAttribute(string? route = null)
{
Route = route;
}
}
66 changes: 66 additions & 0 deletions src/Shimakaze.Sdk.JsonRPC.Server/JsonRPCHostedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Reflection;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using StreamJsonRpc;

namespace Shimakaze.Sdk.JsonRPC.Server;

internal sealed class JsonRPCHostedService : IHostedService
{
private readonly IServiceCollection _services;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<JsonRPCHostedService> _logger;
private readonly JsonRPCHostedServiceOptions _options;

public JsonRPCHostedService(
IServiceCollection services,
IServiceProvider serviceProvider)
{
_services = services;
_serviceProvider = serviceProvider;
_logger = serviceProvider.GetRequiredService<ILogger<JsonRPCHostedService>>();
_options = serviceProvider.GetRequiredService<JsonRPCHostedServiceOptions>();
}

public Task StartAsync(CancellationToken cancellationToken)
{
JsonRpc jsonRpc = new(
new NewLineDelimitedMessageHandler(
_options.Output ?? throw new ArgumentNullException(nameof(_options.Output)),
_options.Input ?? throw new ArgumentNullException(nameof(_options.Input)),
_options.JsonRpcMessageTextFormatter ?? throw new ArgumentNullException(nameof(_options.JsonRpcMessageTextFormatter))
)
);
int count = 0;
foreach (var target in _services
.Select(i => (
Metadata: i.ImplementationType?.GetCustomAttribute<HandlerAttribute>(),
i.ImplementationType))
.Where(i => i is
{
Metadata: not null,
ImplementationType:
{
IsInterface: false,
IsAbstract: false
}
})
.SelectMany(i => i.ImplementationType!.GetTargets(i.Metadata?.Route, _serviceProvider.GetRequiredService(i.ImplementationType!))))
{
var isEvent = target.Method.ReturnType == typeof(void);
_logger.LogDebug("Find {type}: {path}", isEvent ? "Event" : "Method", target.Path);
jsonRpc.AddLocalRpcMethod(target.Path, target.Method, target.Object);
count++;
}
_logger.LogInformation("Loaded {count} JsonRPC Methods/Events.", count);
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
22 changes: 22 additions & 0 deletions src/Shimakaze.Sdk.JsonRPC.Server/JsonRPCHostedServiceOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using StreamJsonRpc;

namespace Shimakaze.Sdk.JsonRPC.Server;

/// <summary>
/// Options for JsonRPCHostedService
/// </summary>
public sealed class JsonRPCHostedServiceOptions
{
/// <summary>
/// Input Stream
/// </summary>
public Stream? Input { get; set; }
/// <summary>
/// Output Stream
/// </summary>
public Stream? Output { get; set; }
/// <summary>
/// JsonRpcMessageTextFormatter
/// </summary>
public IJsonRpcMessageTextFormatter? JsonRpcMessageTextFormatter { get; set; }
}
21 changes: 21 additions & 0 deletions src/Shimakaze.Sdk.JsonRPC.Server/MethodAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Shimakaze.Sdk.JsonRPC.Server;

/// <summary>
/// 表示这是一个JsonRpc 方法/事件
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class MethodAttribute : Attribute
{
/// <summary>
/// 这个方法/事件的路径
/// </summary>
public string? Route { get; set; }
/// <summary>
/// 表示这是一个JsonRpc 方法/事件
/// </summary>
/// <param name="route">这个方法/事件的路径</param>
public MethodAttribute(string? route = null)
{
Route = route;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageReference Include="StreamJsonRpc" Version="2.14.24" />
</ItemGroup>
</Project>
5 changes: 5 additions & 0 deletions src/Shimakaze.Sdk.JsonRPC.Server/Target.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Reflection;

namespace Shimakaze.Sdk.JsonRPC.Server;

internal sealed record Target(string Path, MethodInfo Method, object? Object);
65 changes: 65 additions & 0 deletions src/Shimakaze.Sdk.JsonRPC.Server/TargetExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

using Microsoft.Extensions.DependencyInjection;

namespace Shimakaze.Sdk.JsonRPC.Server;

/// <summary>
/// JsonRpc Server Helper
/// </summary>
public static class TargetExtensions
{
/// <summary>
/// 添加所有的Handler
/// </summary>
/// <param name="services"></param>
/// <param name="optionsBuilder"></param>
/// <returns></returns>
public static IServiceCollection AddJsonRpcHandlers(this IServiceCollection services, Action<JsonRPCHostedServiceOptions> optionsBuilder)
{
foreach (var t in AppDomain
.CurrentDomain
.GetAssemblies()
.SelectMany(asm => asm.GetTypes())
.Where(t => t.GetCustomAttribute<HandlerAttribute>() is not null))
services.AddTransient(t, t);

return services
.AddSingleton<JsonRPCHostedServiceOptions>(provider =>
{
JsonRPCHostedServiceOptions options;
optionsBuilder(options = new());
return options;
})
.AddHostedService<JsonRPCHostedService>(provider => new(services, provider));
}

internal static IEnumerable<Target> GetTargets(this Type type, string? route, object? o)
{
return type
.GetMethods()
.Select(m => (metadata: m.GetCustomAttribute<MethodAttribute>(), m))
.Where(i => i.metadata is not null)
.Select(i => new Target(type.GetFullPath(route, i.metadata?.Route, i.m), i.m, o));
}

private static string GetFullPath(this Type type, string? route, string? method, MethodInfo m)
{
route ??= Regex.Replace(type.Name, "Handlers?|Controllers?", string.Empty);
method ??= m.Name;
if (method.StartsWith('/'))
return method;

StringBuilder sb = new();
if (!route.StartsWith('/'))
sb.Append('/');
sb.Append(route);
if (!route.EndsWith('/'))
sb.Append('/');

sb.Append(method);
return sb.ToString();
}
}

0 comments on commit 75cd2a2

Please sign in to comment.