using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Fleet.Core;
namespace Fleet.Rpc;
/// 对端违反了协议:方法没有声明、方向不对、请求和通知用反了、或者角色不允许。
public readonly record struct RpcViolation(string Method, string Reason);
/// 一次分发的结果:要回的响应(通知为空),以及对端有没有违规。
public readonly record struct RpcDispatch(RpcMessage? Response, RpcViolation? Violation);
///
/// 方法路由,与传输方式无关,也是协议方向的唯一卡口:节点和服务端、Probe 和 Agent 的入站分发都经过这里。
/// 由传输层提供(例如调用方节点)。路由器知道自己在连接的哪一侧()和声明表,
/// 注册处理函数时核对方向、类型和角色,分发时拒绝没有声明或不该由对方发起的方法,发送时核对自己有没有资格发起。
///
public sealed class RpcRouter
{
private delegate ValueTask> Route(TContext context, JsonElement? parameters, CancellationToken ct);
private static readonly JsonElement s_emptyObject = JsonDocument.Parse("{}").RootElement.Clone();
private readonly Dictionary _routes = new(StringComparer.Ordinal);
private readonly Action? _onUnhandled;
private readonly string? _localRole;
private readonly Func? _callerRole;
/// 本路由器在连接的哪一侧。
/// 方法声明表。
/// 本节点的角色(节点侧和对等面用):声明限定了角色的方法,角色不符就不能注册。
/// 从调用上下文取出对端的角色(服务端用):声明限定了角色的方法,角色不符就拒绝。
public RpcRouter(RpcSide side, MethodTable table, string? localRole = null, Func? callerRole = null, Action? onUnhandled = null)
{
Side = side;
Table = table;
_localRole = localRole;
_callerRole = callerRole;
_onUnhandled = onUnhandled;
}
public RpcSide Side { get; }
public MethodTable Table { get; }
private RpcPlane Plane => RpcSides.PlaneOf(Side);
public bool Has(string method) => _routes.ContainsKey(method);
public IReadOnlyCollection Methods => _routes.Keys;
public RpcRouter On(
string method,
JsonTypeInfo paramsInfo,
JsonTypeInfo resultInfo,
Func>> handler)
{
Declare(method, MethodKind.Request);
_routes[method] = async (context, raw, ct) =>
{
if (!TryDeserialize(raw, paramsInfo, out TParams? p))
{
return Result.Fail(FleetErrors.InvalidParams, "params could not be parsed");
}
Result r = await handler(context, p, ct);
return r.TryGet(out TResult? value, out FleetError? error)
? Result.Ok(JsonSerializer.SerializeToElement(value, resultInfo))
: Result.Fail(error);
};
return this;
}
public RpcRouter OnNotify(
string method,
JsonTypeInfo paramsInfo,
Func handler)
{
Declare(method, MethodKind.Notification);
_routes[method] = async (context, raw, ct) =>
{
if (!TryDeserialize(raw, paramsInfo, out TParams? p))
{
return Result.Fail(FleetErrors.InvalidParams, "params could not be parsed");
}
await handler(context, p, ct);
return Result.Ok(null);
};
return this;
}
///
/// 处理一条请求或通知。请求返回响应;通知返回 null(失败时只丢弃)。
/// 处理函数里的异常属于程序缺陷:在这个边界捕获,回复 internal 错误,并交给 onUnhandled 记录。
///
public async ValueTask DispatchAsync(TContext context, RpcMessage message, CancellationToken ct) =>
(await DispatchCheckedAsync(context, message, ct)).Response;
///
/// 先核对声明(方向、类型、角色),再分发。违规的消息不会到达处理函数,返回的结果里带 ,
/// 由会话决定怎么办(计数、超过上限就断开连接)。声明过、方向对、但这一侧没有处理函数的方法不算违规,只是不支持。
///
public async ValueTask DispatchCheckedAsync(TContext context, RpcMessage message, CancellationToken ct)
{
string method = message.Method ?? "";
if (Check(context, message) is { } violation)
{
return new RpcDispatch(
message.Id is null ? null : RpcMessage.Failure(message.Id, new FleetError(FleetErrors.ProtocolViolation, violation.Reason)),
violation);
}
if (!_routes.TryGetValue(method, out Route? route))
{
return new RpcDispatch(message.Id is null ? null : RpcMessage.Failure(message.Id, new FleetError(FleetErrors.Unsupported, $"unknown method {method}")), null);
}
Result result;
try
{
result = await route(context, message.Params, ct);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
_onUnhandled?.Invoke(method, ex);
result = Result.Fail(FleetErrors.Internal, "internal error");
}
if (message.Id is null)
{
return new RpcDispatch(null, null);
}
return new RpcDispatch(
result.TryGet(out JsonElement? value, out FleetError? error)
? new RpcMessage { Id = message.Id, Result = value ?? s_emptyObject }
: RpcMessage.Failure(message.Id, error),
null);
}
/// 入站检查:这条消息是不是对方有资格发起的、类型对不对、对方的角色允不允许。
public RpcViolation? Check(TContext context, RpcMessage message)
{
string method = message.Method ?? "";
MethodSpec? spec = Table.Find(Plane, method);
if (spec is null)
{
return new RpcViolation(method, $"method {method} is not declared");
}
if (spec.To != Side)
{
return new RpcViolation(method, $"{method} cannot be initiated by the {RpcSides.Opposite(Side)} side");
}
MethodKind kind = message.Id is null ? MethodKind.Notification : MethodKind.Request;
if (spec.Kind != kind)
{
return new RpcViolation(method, $"{method} is a {spec.Kind.ToString().ToLowerInvariant()}");
}
if (_callerRole is not null && !spec.AllowsRole(_callerRole(context)))
{
return new RpcViolation(method, $"{method} is not allowed for this node role");
}
return null;
}
///
/// 发送前检查:本侧有没有资格发起这个方法、类型对不对、对端的角色允不允许(服务端发给节点时传入节点的角色)。
/// 违反是程序缺陷,抛 。
///
public void CheckSend(string method, MethodKind kind, string? peerRole = null)
{
MethodSpec? spec = Table.Find(Plane, method);
if (spec is null)
{
throw new InvalidOperationException($"method {method} is not declared");
}
if (spec.From != Side)
{
throw new InvalidOperationException($"{method} cannot be initiated by the {Side} side");
}
if (spec.Kind != kind)
{
throw new InvalidOperationException($"{method} is a {spec.Kind.ToString().ToLowerInvariant()}");
}
if (peerRole is not null && !spec.AllowsRole(peerRole))
{
throw new InvalidOperationException($"{method} cannot be sent to a {peerRole}");
}
}
private void Declare(string method, MethodKind kind)
{
MethodSpec spec = Table.Find(Plane, method) ?? throw new InvalidOperationException($"method {method} is not declared in the method table");
if (spec.To != Side)
{
throw new InvalidOperationException($"{method} is initiated by the {spec.From} side and cannot be handled on the {Side} side");
}
if (spec.Kind != kind)
{
throw new InvalidOperationException($"{method} is declared as a {spec.Kind.ToString().ToLowerInvariant()}");
}
if (_localRole is not null && !spec.AllowsRole(_localRole))
{
throw new InvalidOperationException($"{method} is not for the {_localRole} role");
}
}
private static bool TryDeserialize(JsonElement? raw, JsonTypeInfo info, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out T? value)
{
value = default;
try
{
value = raw is null ? JsonSerializer.Deserialize("{}", info) : raw.Value.Deserialize(info);
return value is not null;
}
catch (JsonException)
{
return false;
}
}
}