chore: initial project import
This commit is contained in:
commit
26620707a0
347 files changed
+56172
No files matched your search
@@ -0,0 +1,170 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Fleet.Core;
|
||||
using Fleet.Rpc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Fleet.Server;
|
||||
|
||||
/// <summary>
|
||||
/// 服务端连接宿主:在线表 + 服务端发起的调用 + 状态推送通知。每个 WebSocket 对应一个 <see cref="NodeConnection"/>。
|
||||
/// </summary>
|
||||
public sealed class FleetLinkHost : IFleetConnections, IStateNotifier
|
||||
{
|
||||
private readonly ConcurrentDictionary<NodeId, NodeConnection> _connections = new();
|
||||
|
||||
internal FleetLinkHost(
|
||||
IFleetStore store,
|
||||
FleetServerOptions options,
|
||||
TimeProvider time,
|
||||
ILogger<FleetLinkHost> logger,
|
||||
IEnumerable<IStateProvider> stateProviders,
|
||||
IEnumerable<IInbox> inboxes,
|
||||
IEnumerable<INodeEventSink> sinks,
|
||||
FleetRouter router)
|
||||
{
|
||||
Store = store;
|
||||
Options = options;
|
||||
Time = time;
|
||||
Logger = logger;
|
||||
StateProviders = [.. stateProviders];
|
||||
Inboxes = inboxes.ToDictionary(i => i.Channel);
|
||||
Sinks = [.. sinks];
|
||||
Router = router.Inner;
|
||||
}
|
||||
|
||||
internal IFleetStore Store { get; }
|
||||
|
||||
internal FleetServerOptions Options { get; }
|
||||
|
||||
internal TimeProvider Time { get; }
|
||||
|
||||
internal ILogger<FleetLinkHost> Logger { get; }
|
||||
|
||||
internal IReadOnlyList<IStateProvider> StateProviders { get; }
|
||||
|
||||
internal IReadOnlyDictionary<byte, IInbox> Inboxes { get; }
|
||||
|
||||
internal IReadOnlyList<INodeEventSink> Sinks { get; }
|
||||
|
||||
internal RpcRouter<NodeCallContext> Router { get; }
|
||||
|
||||
public IReadOnlyCollection<NodeId> OnlineNodes => [.. _connections.Keys];
|
||||
|
||||
public bool IsOnline(NodeId node) => _connections.ContainsKey(node);
|
||||
|
||||
public NodeSessionInfo? GetSession(NodeId node) =>
|
||||
_connections.TryGetValue(node, out NodeConnection? c) ? c.Session : null;
|
||||
|
||||
/// <summary>处理一条 WebSocket 连接,直到连接结束。</summary>
|
||||
public async Task RunConnectionAsync(WebSocket socket, CancellationToken ct)
|
||||
{
|
||||
using var connection = new NodeConnection(this, socket);
|
||||
await connection.RunAsync(ct);
|
||||
}
|
||||
|
||||
/// <summary>返回被替换掉的旧连接(如果有)。</summary>
|
||||
internal NodeConnection? Register(NodeConnection connection)
|
||||
{
|
||||
NodeConnection? replaced = null;
|
||||
_connections.AddOrUpdate(
|
||||
connection.NodeId,
|
||||
connection,
|
||||
(_, existing) =>
|
||||
{
|
||||
replaced = existing;
|
||||
return connection;
|
||||
});
|
||||
return replaced;
|
||||
}
|
||||
|
||||
internal void Unregister(NodeConnection connection) =>
|
||||
_connections.TryRemove(new KeyValuePair<NodeId, NodeConnection>(connection.NodeId, connection));
|
||||
|
||||
public async ValueTask<Result<TResult>> RequestAsync<TParams, TResult>(
|
||||
NodeId node,
|
||||
string method,
|
||||
TParams parameters,
|
||||
JsonTypeInfo<TParams> paramsInfo,
|
||||
JsonTypeInfo<TResult> resultInfo,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!_connections.TryGetValue(node, out NodeConnection? connection))
|
||||
{
|
||||
return Result<TResult>.Fail(FleetErrors.NodeOffline, "node is not connected");
|
||||
}
|
||||
|
||||
Result<JsonElement> raw = await connection.RequestAsync(method, JsonSerializer.SerializeToElement(parameters, paramsInfo), ct);
|
||||
if (!raw.TryGet(out JsonElement value, out FleetError? error))
|
||||
{
|
||||
return Result<TResult>.Fail(error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TResult? result = value.Deserialize(resultInfo);
|
||||
return result is null
|
||||
? Result<TResult>.Fail(FleetErrors.Internal, "empty result")
|
||||
: Result<TResult>.Ok(result);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return Result<TResult>.Fail(FleetErrors.Internal, "result could not be parsed");
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<bool> NotifyAsync<TParams>(NodeId node, string method, TParams parameters, JsonTypeInfo<TParams> paramsInfo, CancellationToken ct)
|
||||
{
|
||||
if (!_connections.TryGetValue(node, out NodeConnection? connection))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await connection.SendAsync(RpcMessage.Notification(method, parameters, paramsInfo), ct);
|
||||
}
|
||||
|
||||
public async ValueTask CloseAllAsync(string reason)
|
||||
{
|
||||
foreach (NodeConnection connection in _connections.Values.ToArray())
|
||||
{
|
||||
await connection.CloseAsync(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisconnectAsync(NodeId node, string reason, CancellationToken ct)
|
||||
{
|
||||
if (_connections.TryGetValue(node, out NodeConnection? connection))
|
||||
{
|
||||
await connection.RevokeAsync(reason, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyChanged(NodeId node)
|
||||
{
|
||||
if (_connections.TryGetValue(node, out NodeConnection? connection))
|
||||
{
|
||||
connection.SignalStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyAllChanged()
|
||||
{
|
||||
foreach (NodeConnection connection in _connections.Values)
|
||||
{
|
||||
connection.SignalStateChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>产品注册的 RPC 方法路由。</summary>
|
||||
public sealed class FleetRouter
|
||||
{
|
||||
public FleetRouter(RpcRouter<NodeCallContext> inner)
|
||||
{
|
||||
Inner = inner;
|
||||
}
|
||||
|
||||
internal RpcRouter<NodeCallContext> Inner { get; }
|
||||
}
|
||||
Reference in new issue
Block a user