diff --git a/src/CoreIpc.sln b/src/CoreIpc.sln
index 12d4a07d..74779ed7 100644
--- a/src/CoreIpc.sln
+++ b/src/CoreIpc.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# 17
+# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.CoreIpc", "UiPath.CoreIpc\UiPath.CoreIpc.csproj", "{58200319-1F71-4E22-894D-7E69E0CD0B57}"
@@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.CoreIpc.Http", "UiPa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.Ipc.Tests", "UiPath.Ipc.Tests\UiPath.Ipc.Tests.csproj", "{E238E183-92CF-48A6-890F-C422853D6656}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiPath.CoreIpc.Extensions.Abstractions", "UiPath.CoreIpc.Extensions.Abstractions\UiPath.CoreIpc.Extensions.Abstractions.csproj", "{F519AE2B-88A6-482E-A6E2-B525F71F566D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +41,10 @@ Global
{E238E183-92CF-48A6-890F-C422853D6656}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E238E183-92CF-48A6-890F-C422853D6656}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E238E183-92CF-48A6-890F-C422853D6656}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Playground/Program.cs b/src/Playground/Program.cs
index 613e8d5a..e373bd18 100644
--- a/src/Playground/Program.cs
+++ b/src/Playground/Program.cs
@@ -44,7 +44,7 @@ private static async Task Main(string[] args)
typeof(Contracts.IServerOperations), // DEVINE
new EndpointSettings(typeof(Contracts.IServerOperations)) // ASTALALT
{
- BeforeCall = async (callInfo, _) =>
+ BeforeIncommingCall = async (callInfo, _) =>
{
Console.WriteLine($"Server: {callInfo.Method.Name}");
}
@@ -76,36 +76,30 @@ private static async Task Main(string[] args)
var c1 = new IpcClient()
{
- Config = new()
+ Callbacks = new()
{
- Callbacks = new()
- {
- typeof(Contracts.IClientOperations),
- { typeof(Contracts.IClientOperations2), new Impl.Client2() },
- },
- ServiceProvider = clientSP,
- Scheduler = clientScheduler,
+ typeof(Contracts.IClientOperations),
+ { typeof(Contracts.IClientOperations2), new Impl.Client2() },
},
+ ServiceProvider = clientSP,
+ Scheduler = clientScheduler,
Transport = new NamedPipeClientTransport()
{
PipeName = Contracts.PipeName,
ServerName = ".",
AllowImpersonation = false,
- },
+ }
};
var c2 = new IpcClient()
{
- Config = new()
+ ServiceProvider = clientSP,
+ Callbacks = new()
{
- ServiceProvider = clientSP,
- Callbacks = new()
- {
- typeof(Contracts.IClientOperations),
- { typeof(Contracts.IClientOperations2), new Impl.Client2() },
- },
- Scheduler = clientScheduler,
+ typeof(Contracts.IClientOperations),
+ { typeof(Contracts.IClientOperations2), new Impl.Client2() },
},
+ Scheduler = clientScheduler,
Transport = new NamedPipeClientTransport()
{
PipeName = Contracts.PipeName,
@@ -116,16 +110,13 @@ private static async Task Main(string[] args)
var proxy1 = new IpcClient()
{
- Config = new()
+ ServiceProvider = clientSP,
+ Callbacks = new()
{
- ServiceProvider = clientSP,
- Callbacks = new()
- {
- typeof(Contracts.IClientOperations),
- { typeof(Contracts.IClientOperations2), new Impl.Client2() },
- },
- Scheduler = clientScheduler,
+ typeof(Contracts.IClientOperations),
+ { typeof(Contracts.IClientOperations2), new Impl.Client2() },
},
+ Scheduler = clientScheduler,
Transport = new NamedPipeClientTransport()
{
PipeName = Contracts.PipeName,
diff --git a/src/UiPath.CoreIpc.Extensions.Abstractions/UiPath.CoreIpc.Extensions.Abstractions.csproj b/src/UiPath.CoreIpc.Extensions.Abstractions/UiPath.CoreIpc.Extensions.Abstractions.csproj
new file mode 100644
index 00000000..2186c679
--- /dev/null
+++ b/src/UiPath.CoreIpc.Extensions.Abstractions/UiPath.CoreIpc.Extensions.Abstractions.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.0;net461;net6.0-windows
+ enable
+ enable
+ preview
+ true
+ enable
+ true
+
+
+
+
+
+
+
diff --git a/src/UiPath.CoreIpc.Http/BidiHttpListener.cs b/src/UiPath.CoreIpc.Http/BidiHttpListener.cs
deleted file mode 100644
index 06076a21..00000000
--- a/src/UiPath.CoreIpc.Http/BidiHttpListener.cs
+++ /dev/null
@@ -1,285 +0,0 @@
-using Nito.AsyncEx;
-using System.Buffers;
-using System.Collections.Concurrent;
-using System.Diagnostics.CodeAnalysis;
-using System.IO.Pipelines;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Channels;
-
-namespace UiPath.Ipc.Http;
-
-using static Constants;
-using IBidiHttpListenerConfig = IListenerConfig;
-
-public sealed partial record BidiHttpListener : ServerTransport, IBidiHttpListenerConfig
-{
- public required Uri Uri { get; init; }
-
- BidiHttpListenerState IBidiHttpListenerConfig.CreateListenerState(IpcServer server)
- => new(server, this);
-
- BidiHttpServerConnectionState IBidiHttpListenerConfig.CreateConnectionState(IpcServer server, BidiHttpListenerState listenerState)
- => new(server, listenerState);
-
- async ValueTask IBidiHttpListenerConfig.AwaitConnection(BidiHttpListenerState listenerState, BidiHttpServerConnectionState connectionState, CancellationToken ct)
- {
- await connectionState.WaitForConnection(ct);
- return connectionState;
- }
-
- public IEnumerable Validate()
- {
- throw new NotImplementedException();
- }
-}
-
-internal sealed class BidiHttpListenerState : IAsyncDisposable
-{
- private readonly IpcServer _ipcServer;
- private readonly CancellationTokenSource _cts = new();
- private readonly HttpListener _httpListener;
- private readonly Task _processing;
- private readonly Lazy _disposing;
-
- private readonly ConcurrentDictionary> _connections = new();
- private readonly Channel<(Guid connectionId, Uri reverseUri)> _newConnections = Channel.CreateUnbounded<(Guid connectionId, Uri reverseUri)>();
-
- public ChannelReader<(Guid connectionId, Uri reverseUri)> NewConnections => _newConnections.Reader;
- public ChannelReader GetConnectionChannel(Guid connectionId) => _connections[connectionId];
-
- public BidiHttpListenerState(IpcServer ipcServer, BidiHttpListener listener)
- {
- _ipcServer = ipcServer;
- _httpListener = new HttpListener()
- {
- Prefixes =
- {
- listener.Uri.ToString()
- }
- };
- _processing = ProcessContexts();
- _disposing = new(DisposeCore);
- }
-
- public ValueTask DisposeAsync() => new(_disposing.Value);
-
- private async Task DisposeCore()
- {
- _cts.Cancel();
- try
- {
- await _processing;
- }
- catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token)
- {
- }
-
- foreach (var pair in _connections)
- {
- pair.Value.Writer.Complete();
- }
- _cts.Dispose();
- }
-
- private async Task ProcessContexts()
- {
- await foreach (var (context, connectionId, reverseUri) in AwaitContexts())
- {
- var connectionChannel = _connections.GetOrAdd(connectionId, _ =>
- {
- _newConnections.Writer.TryWrite((connectionId, reverseUri));
- return Channel.CreateUnbounded();
- });
-
- await connectionChannel.Writer.WriteAsync(context, _cts.Token);
- }
-
- async IAsyncEnumerable<(HttpListenerContext context, Guid connectionId, Uri reverseUri)> AwaitContexts()
- {
- while (!_cts.Token.IsCancellationRequested)
- {
- var context = await _httpListener.GetContextAsync();
-
- if (!TryAcceptContext(context, out var connectionId, out var reverseUri))
- {
- context.Response.StatusCode = 400;
- context.Response.Close();
- continue;
- }
-
- yield return (context, connectionId, reverseUri);
- }
- }
-
- bool TryAcceptContext(HttpListenerContext context, out Guid connectionId, [NotNullWhen(returnValue: true)] out Uri? reverseUri)
- {
- if (!Guid.TryParse(context.Request.Headers[ConnectionIdHeader], out connectionId) ||
- !Uri.TryCreate(context.Request.Headers[ReverseUriHeader], UriKind.Absolute, out reverseUri))
- {
- connectionId = Guid.Empty;
- reverseUri = null;
- return false;
- }
-
- return true;
- }
- }
-}
-
-internal sealed class BidiHttpServerConnectionState : Stream, IAsyncDisposable
-{
- private readonly Pipe _pipe = new();
-
- private readonly IpcServer _server;
- private readonly BidiHttpListenerState _listenerState;
-
- private readonly CancellationTokenSource _cts = new();
- private readonly AsyncLock _lock = new();
- private (Guid connectionId, Uri reverseUri)? _connection = null;
- private HttpClient? _client;
- private Task? _processing = null;
- private readonly Lazy _disposing;
-
- public BidiHttpServerConnectionState(IpcServer server, BidiHttpListenerState listenerState)
- {
- _server = server;
- _listenerState = listenerState;
- _disposing = new(DisposeCore);
- }
-
- public
-#if !NET461
- override
-#endif
- ValueTask DisposeAsync() => new(_disposing.Value);
-
- private async Task DisposeCore()
- {
- _cts.Cancel();
-
- _client?.Dispose();
-
- try
- {
- await (_processing ?? Task.CompletedTask);
- }
- catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token)
- {
- // ignored
- }
-
- _cts.Dispose();
- }
-
- public async Task WaitForConnection(CancellationToken ct)
- {
- using (await _lock.LockAsync(ct))
- {
- if (_connection is not null)
- {
- throw new InvalidOperationException();
- }
-
- _connection = await _listenerState.NewConnections.ReadAsync(ct);
-
- _client = new()
- {
- BaseAddress = _connection.Value.reverseUri,
- DefaultRequestHeaders =
- {
- { ConnectionIdHeader, _connection.Value.connectionId.ToString() }
- }
- };
-
- _processing = ProcessContexts(_cts.Token);
- }
- }
-
- private async Task ProcessContexts(CancellationToken ct)
- {
- var reader = _listenerState.GetConnectionChannel(_connection!.Value.connectionId);
-
- while (await reader.WaitToReadAsync(ct))
- {
- if (!reader.TryRead(out var context))
- {
- continue;
- }
- await ProcessContext(context);
- }
-
- async Task ProcessContext(HttpListenerContext context)
- {
- try
- {
- while (true)
- {
- var memory = _pipe.Writer.GetMemory();
- var cbRead = await context.Request.InputStream.ReadAsync(memory, ct);
- if (cbRead is 0)
- {
- break;
- }
- _pipe.Writer.Advance(cbRead);
- var flushResult = await _pipe.Writer.FlushAsync(ct);
- if (flushResult.IsCompleted)
- {
- break;
- }
- }
- }
- finally
- {
- context.Response.StatusCode = 200;
- context.Response.Close();
- }
- }
- }
-
- public override bool CanRead => true;
- public override bool CanSeek => false;
- public override bool CanWrite => true;
-
- public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct)
- {
- var memory = new Memory(buffer, offset, count);
- var readResult = await _pipe.Reader.ReadAsync(ct);
-
- var take = (int)Math.Min(readResult.Buffer.Length, memory.Length);
-
- readResult.Buffer.Slice(start: 0, length: take).CopyTo(memory.Span);
- _pipe.Reader.AdvanceTo(readResult.Buffer.GetPosition(take));
-
- return take;
- }
-
- public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct)
- {
- var memory = new ReadOnlyMemory(buffer, offset, count);
- if (_client is null)
- {
- throw new InvalidOperationException();
- }
-
- HttpContent content =
-#if NET461
- new ByteArrayContent(memory.ToArray());
-#else
- new ReadOnlyMemoryContent(memory);
-#endif
-
- await _client.PostAsync(requestUri: "", content, ct);
- }
-
- public override Task FlushAsync(CancellationToken cancellationToken)
- => Task.CompletedTask;
-
- public override void Flush() => throw new NotImplementedException();
- public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
- public override void SetLength(long value) => throw new NotImplementedException();
- public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException();
- public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
- public override long Length => throw new NotImplementedException();
- public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-}
diff --git a/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs b/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs
new file mode 100644
index 00000000..4e43ce58
--- /dev/null
+++ b/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs
@@ -0,0 +1,278 @@
+using Nito.AsyncEx;
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+using System.IO.Pipelines;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Channels;
+
+namespace UiPath.Ipc.Http;
+
+using static Constants;
+
+public sealed partial class BidiHttpServerTransport : ServerTransport
+{
+ public required Uri Uri { get; init; }
+
+ protected override IServerState CreateServerState()
+ => new BidiHttpServerState(this);
+
+ protected override IEnumerable ValidateCore() => [];
+
+ private sealed class BidiHttpServerState : IServerState
+ {
+ private readonly CancellationTokenSource _cts = new();
+ private readonly HttpListener _httpListener;
+ private readonly Task _processing;
+ private readonly Lazy _disposing;
+
+ private readonly ConcurrentDictionary> _connections = new();
+ private readonly Channel<(Guid connectionId, Uri reverseUri)> _newConnections = Channel.CreateUnbounded<(Guid connectionId, Uri reverseUri)>();
+
+ public ChannelReader<(Guid connectionId, Uri reverseUri)> NewConnections => _newConnections.Reader;
+ public ChannelReader GetConnectionChannel(Guid connectionId) => _connections[connectionId];
+
+ public BidiHttpServerState(BidiHttpServerTransport transport)
+ {
+ _httpListener = new HttpListener()
+ {
+ Prefixes =
+ {
+ transport.Uri.ToString()
+ }
+ };
+ _processing = ProcessContexts();
+ _disposing = new(DisposeCore);
+ }
+
+ public ValueTask DisposeAsync() => new(_disposing.Value);
+
+ private async Task DisposeCore()
+ {
+ _cts.Cancel();
+ try
+ {
+ await _processing;
+ }
+ catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token)
+ {
+ }
+
+ foreach (var pair in _connections)
+ {
+ pair.Value.Writer.Complete();
+ }
+ _cts.Dispose();
+ }
+
+ private async Task ProcessContexts()
+ {
+ await foreach (var (context, connectionId, reverseUri) in AwaitContexts())
+ {
+ var connectionChannel = _connections.GetOrAdd(connectionId, _ =>
+ {
+ _newConnections.Writer.TryWrite((connectionId, reverseUri));
+ return Channel.CreateUnbounded();
+ });
+
+ await connectionChannel.Writer.WriteAsync(context, _cts.Token);
+ }
+
+ async IAsyncEnumerable<(HttpListenerContext context, Guid connectionId, Uri reverseUri)> AwaitContexts()
+ {
+ while (!_cts.Token.IsCancellationRequested)
+ {
+ var context = await _httpListener.GetContextAsync();
+
+ if (!TryAcceptContext(context, out var connectionId, out var reverseUri))
+ {
+ context.Response.StatusCode = 400;
+ context.Response.Close();
+ continue;
+ }
+
+ yield return (context, connectionId, reverseUri);
+ }
+ }
+
+ bool TryAcceptContext(HttpListenerContext context, out Guid connectionId, [NotNullWhen(returnValue: true)] out Uri? reverseUri)
+ {
+ if (!Guid.TryParse(context.Request.Headers[ConnectionIdHeader], out connectionId) ||
+ !Uri.TryCreate(context.Request.Headers[ReverseUriHeader], UriKind.Absolute, out reverseUri))
+ {
+ connectionId = Guid.Empty;
+ reverseUri = null;
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ IServerConnectionSlot IServerState.CreateConnectionSlot() => new BidiHttpServerConnectionSlot(this);
+ }
+
+ private sealed class BidiHttpServerConnectionSlot : Stream, IServerConnectionSlot, IAsyncDisposable
+ {
+ private readonly Pipe _pipe = new();
+
+ private readonly BidiHttpServerState _listenerState;
+
+ private readonly CancellationTokenSource _cts = new();
+ private readonly AsyncLock _lock = new();
+ private (Guid connectionId, Uri reverseUri)? _connection = null;
+ private HttpClient? _client;
+ private Task? _processing = null;
+ private readonly Lazy _disposing;
+
+ public BidiHttpServerConnectionSlot(BidiHttpServerState serverState)
+ {
+ _listenerState = serverState;
+ _disposing = new(DisposeCore);
+ }
+
+ public
+#if !NET461
+ override
+#endif
+ ValueTask DisposeAsync() => new(_disposing.Value);
+
+ private async Task DisposeCore()
+ {
+ _cts.Cancel();
+
+ _client?.Dispose();
+
+ try
+ {
+ await (_processing ?? Task.CompletedTask);
+ }
+ catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token)
+ {
+ // ignored
+ }
+
+ _cts.Dispose();
+ }
+
+ public async Task WaitForConnection(CancellationToken ct)
+ {
+ using (await _lock.LockAsync(ct))
+ {
+ if (_connection is not null)
+ {
+ throw new InvalidOperationException();
+ }
+
+ _connection = await _listenerState.NewConnections.ReadAsync(ct);
+
+ _client = new()
+ {
+ BaseAddress = _connection.Value.reverseUri,
+ DefaultRequestHeaders =
+ {
+ { ConnectionIdHeader, _connection.Value.connectionId.ToString() }
+ }
+ };
+
+ _processing = ProcessContexts(_cts.Token);
+ }
+ }
+
+ private async Task ProcessContexts(CancellationToken ct)
+ {
+ var reader = _listenerState.GetConnectionChannel(_connection!.Value.connectionId);
+
+ while (await reader.WaitToReadAsync(ct))
+ {
+ if (!reader.TryRead(out var context))
+ {
+ continue;
+ }
+ await ProcessContext(context);
+ }
+
+ async Task ProcessContext(HttpListenerContext context)
+ {
+ try
+ {
+ while (true)
+ {
+ var memory = _pipe.Writer.GetMemory();
+ var cbRead = await context.Request.InputStream.ReadAsync(memory, ct);
+ if (cbRead is 0)
+ {
+ break;
+ }
+ _pipe.Writer.Advance(cbRead);
+ var flushResult = await _pipe.Writer.FlushAsync(ct);
+ if (flushResult.IsCompleted)
+ {
+ break;
+ }
+ }
+ }
+ finally
+ {
+ context.Response.StatusCode = 200;
+ context.Response.Close();
+ }
+ }
+ }
+
+ ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct)
+ {
+ throw new NotImplementedException();
+ }
+
+
+
+ public override bool CanRead => true;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+
+ public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ var memory = new Memory(buffer, offset, count);
+ var readResult = await _pipe.Reader.ReadAsync(ct);
+
+ var take = (int)Math.Min(readResult.Buffer.Length, memory.Length);
+
+ readResult.Buffer.Slice(start: 0, length: take).CopyTo(memory.Span);
+ _pipe.Reader.AdvanceTo(readResult.Buffer.GetPosition(take));
+
+ return take;
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct)
+ {
+ var memory = new ReadOnlyMemory(buffer, offset, count);
+ if (_client is null)
+ {
+ throw new InvalidOperationException();
+ }
+
+ HttpContent content =
+#if NET461
+ new ByteArrayContent(memory.ToArray());
+#else
+ new ReadOnlyMemoryContent(memory);
+#endif
+
+ await _client.PostAsync(requestUri: "", content, ct);
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ => Task.CompletedTask;
+
+ public override void Flush() => throw new NotImplementedException();
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
+ public override void SetLength(long value) => throw new NotImplementedException();
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException();
+ public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
+
+ public override long Length => throw new NotImplementedException();
+ public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+ }
+}
diff --git a/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj b/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj
index 524ffbc1..83f0f21e 100644
--- a/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj
+++ b/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj
@@ -21,7 +21,7 @@
-
+
diff --git a/src/UiPath.CoreIpc/Client/ServiceClient.cs b/src/UiPath.CoreIpc/Client/ServiceClient.cs
index 4c0edd2f..95ef34c3 100644
--- a/src/UiPath.CoreIpc/Client/ServiceClient.cs
+++ b/src/UiPath.CoreIpc/Client/ServiceClient.cs
@@ -11,7 +11,7 @@ private static IpcProxy CreateProxy(ServiceClient serviceClient) where T : cl
return proxy;
}
- protected abstract IServiceClientConfig Config { get; }
+ protected abstract IClientConfig Config { get; }
public abstract Stream? Network { get; }
public event EventHandler? ConnectionClosed;
@@ -66,11 +66,7 @@ async Task Invoke()
var (connection, newConnection) = await EnsureConnection(ct);
- if (Config.BeforeCall is not null)
- {
- var callInfo = new CallInfo(newConnection, method, args);
- await Config.BeforeCall(callInfo, ct);
- }
+ await (Config.BeforeOutgoingCall?.Invoke(new CallInfo(newConnection, method, args), ct) ?? Task.CompletedTask);
var requestId = connection.NewRequestId();
var request = new Request(_interfaceType.Name, requestId, methodName, serializedArguments, messageTimeout.TotalSeconds)
@@ -78,18 +74,18 @@ async Task Invoke()
UploadStream = uploadStream
};
- Config.Logger?.ServiceClient_Calling(methodName, requestId, Config.DebugName);
+ Config.Logger?.ServiceClient_Calling(methodName, requestId, Config.GetComputedDebugName());
Response response;
try
{
response = await connection.RemoteCall(request, ct); // returns user errors instead of throwing them (could throw for system bugs)
- Config.Logger?.ServiceClient_CalledSuccessfully(request.MethodName, requestId, Config.DebugName);
+ Config.Logger?.ServiceClient_CalledSuccessfully(request.MethodName, requestId, Config.GetComputedDebugName());
}
catch (Exception ex)
{
- Config.Logger?.ServiceClient_FailedToCall(request.MethodName, requestId, Config.DebugName, ex);
+ Config.Logger?.ServiceClient_FailedToCall(request.MethodName, requestId, Config.GetComputedDebugName(), ex);
throw;
}
@@ -133,7 +129,7 @@ string[] SerializeArguments()
public abstract void Dispose();
- public override string ToString() => Config.DebugName;
+ public override string ToString() => Config.GetComputedDebugName();
#region Generic adapter cache
private static readonly MethodInfo GenericDefOf_Invoke = ((Func>)Invoke).Method.GetGenericMethodDefinition();
@@ -227,9 +223,9 @@ public override async ValueTask CloseConnection()
var network = await Connect(ct);
- LatestConnection = new Connection(network, Config.DebugName, Config.Logger);
- var router = new Router(_client.Config.CreateCallbackRouterConfig(), _client.Config.ServiceProvider);
- _latestServer = new Server(router, _client.Config.RequestTimeout, LatestConnection);
+ LatestConnection = new Connection(network, Config.GetComputedDebugName(), Config.Logger);
+ var router = new Router(_client.CreateCallbackRouterConfig(), _client.ServiceProvider);
+ _latestServer = new Server(router, _client.RequestTimeout, LatestConnection);
_ = Pal();
return (LatestConnection, newlyConnected: true);
@@ -242,7 +238,7 @@ async Task Pal()
}
catch (Exception ex)
{
- Config.Logger.LogException(ex, Config.DebugName);
+ Config.Logger.LogException(ex, Config.GetComputedDebugName());
}
}
}
@@ -260,17 +256,17 @@ private async Task Connect(CancellationToken ct)
return network;
}
- protected override IServiceClientConfig Config => _client.Config;
+ protected override IClientConfig Config => _client;
}
internal sealed class ServiceClientForCallback : ServiceClient where TInterface : class
{
private readonly Connection _connection;
- private readonly IServiceClientConfig _config;
+ private readonly IClientConfig _config;
public override Stream? Network => _connection.Network;
- public ServiceClientForCallback(Connection connection, IServiceClientConfig config) : base(typeof(TInterface))
+ public ServiceClientForCallback(Connection connection, IClientConfig config) : base(typeof(TInterface))
{
_connection = connection;
_config = config;
@@ -286,5 +282,5 @@ public override void Dispose()
protected override Task<(Connection connection, bool newlyConnected)> EnsureConnection(CancellationToken ct)
=> Task.FromResult((_connection, newlyConnected: false));
- protected override IServiceClientConfig Config => _config;
+ protected override IClientConfig Config => _config;
}
diff --git a/src/UiPath.CoreIpc/Config/ClientConfig.cs b/src/UiPath.CoreIpc/Config/ClientConfig.cs
index c6ac4bdb..6c05c192 100644
--- a/src/UiPath.CoreIpc/Config/ClientConfig.cs
+++ b/src/UiPath.CoreIpc/Config/ClientConfig.cs
@@ -1,18 +1,9 @@
-using System.ComponentModel;
-
-namespace UiPath.Ipc;
+namespace UiPath.Ipc;
public sealed class ClientConfig : Peer, IServiceClientConfig
{
- public EndpointCollection? Callbacks { get; init; }
-
- public ILogger? Logger { get; init; }
- public BeforeConnectHandler? BeforeConnect { get; init; }
public BeforeCallHandler? BeforeCall { get; init; }
- [EditorBrowsable(EditorBrowsableState.Never)]
- public string DebugName { get; set; } = null!;
-
internal void Validate()
{
var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false;
@@ -22,36 +13,4 @@ internal void Validate()
throw new InvalidOperationException("ServiceProvider is required when you register injectable callbacks. Consider registering a callback instance.");
}
}
-
- internal ILogger? GetLogger(string name)
- {
- if (Logger is not null)
- {
- return Logger;
- }
-
- if (ServiceProvider?.GetService() is not { } loggerFactory)
- {
- return null;
- }
-
- return loggerFactory.CreateLogger(name);
- }
-
- internal override RouterConfig CreateCallbackRouterConfig()
- => RouterConfig.From(
- Callbacks.OrDefault(),
- endpoint => endpoint with
- {
- BeforeCall = null, // callbacks don't support BeforeCall
- Scheduler = endpoint.Scheduler ?? Scheduler
- });
}
-
-public interface IClientState : IDisposable
-{
- Stream? Network { get; }
-
- bool IsConnected();
- ValueTask Connect(IpcClient client, CancellationToken ct);
-}
\ No newline at end of file
diff --git a/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs b/src/UiPath.CoreIpc/Config/IClientConfig.cs
similarity index 58%
rename from src/UiPath.CoreIpc/Config/IServiceClientConfig.cs
rename to src/UiPath.CoreIpc/Config/IClientConfig.cs
index 19ed49fd..9e4741ab 100644
--- a/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs
+++ b/src/UiPath.CoreIpc/Config/IClientConfig.cs
@@ -1,11 +1,11 @@
namespace UiPath.Ipc;
// Maybe decommission
-internal interface IServiceClientConfig
+internal interface IClientConfig
{
TimeSpan RequestTimeout { get; }
BeforeConnectHandler? BeforeConnect { get; }
- BeforeCallHandler? BeforeCall { get; }
+ BeforeCallHandler? BeforeOutgoingCall { get; }
ILogger? Logger { get; }
- string DebugName { get; }
+ string GetComputedDebugName();
}
diff --git a/src/UiPath.CoreIpc/Config/IClientState.cs b/src/UiPath.CoreIpc/Config/IClientState.cs
new file mode 100644
index 00000000..74ff640c
--- /dev/null
+++ b/src/UiPath.CoreIpc/Config/IClientState.cs
@@ -0,0 +1,9 @@
+namespace UiPath.Ipc;
+
+public interface IClientState : IDisposable
+{
+ Stream? Network { get; }
+
+ bool IsConnected();
+ ValueTask Connect(IpcClient client, CancellationToken ct);
+}
diff --git a/src/UiPath.CoreIpc/Config/IpcClient.cs b/src/UiPath.CoreIpc/Config/IpcClient.cs
index 24104b16..7bed1c05 100644
--- a/src/UiPath.CoreIpc/Config/IpcClient.cs
+++ b/src/UiPath.CoreIpc/Config/IpcClient.cs
@@ -1,10 +1,22 @@
-namespace UiPath.Ipc;
+using System.ComponentModel;
-public sealed class IpcClient : Peer
+namespace UiPath.Ipc;
+
+public sealed class IpcClient : Peer, IClientConfig
{
- public required ClientConfig Config { get; init; }
+ public EndpointCollection? Callbacks { get; set; }
+
+ public ILogger? Logger { get; init; }
+ public BeforeConnectHandler? BeforeConnect { get; set; }
+ public BeforeCallHandler? BeforeOutgoingCall { get; set; }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public string DebugName { get; set; } = null!;
+
public required ClientTransport Transport { get; init; }
+ string IClientConfig.GetComputedDebugName() => DebugName ?? Transport.ToString();
+
private readonly ConcurrentDictionary _clients = new();
private ServiceClient GetServiceClient(Type proxyType)
{
@@ -16,18 +28,42 @@ private ServiceClient GetServiceClient(Type proxyType)
internal void Validate()
{
- if (Config is null)
+ var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false;
+
+ if (haveDeferredInjectedCallbacks && ServiceProvider is null)
{
- throw new InvalidOperationException($"{Config} is required.");
+ throw new InvalidOperationException("ServiceProvider is required when you register injectable callbacks. Consider registering a callback instance.");
}
+
if (Transport is null)
{
throw new InvalidOperationException($"{Transport} is required.");
}
- Config.Validate();
Transport.Validate();
+ }
- Config.DebugName ??= Transport.ToString();
+ internal ILogger? GetLogger(string name)
+ {
+ if (Logger is not null)
+ {
+ return Logger;
+ }
+
+ if (ServiceProvider?.GetService() is not { } loggerFactory)
+ {
+ return null;
+ }
+
+ return loggerFactory.CreateLogger(name);
}
+
+ internal RouterConfig CreateCallbackRouterConfig()
+ => RouterConfig.From(
+ Callbacks.OrDefault(),
+ endpoint => endpoint with
+ {
+ BeforeIncommingCall = null, // callbacks don't support BeforeCall
+ Scheduler = endpoint.Scheduler ?? Scheduler
+ });
}
diff --git a/src/UiPath.CoreIpc/Config/IpcServer.cs b/src/UiPath.CoreIpc/Config/IpcServer.cs
index d0f58020..e9c3d14d 100644
--- a/src/UiPath.CoreIpc/Config/IpcServer.cs
+++ b/src/UiPath.CoreIpc/Config/IpcServer.cs
@@ -14,8 +14,16 @@ public sealed class IpcServer : Peer, IAsyncDisposable
private bool _disposeStarted;
private Accepter? _accepter;
+ private Lazy _dispose;
- public async ValueTask DisposeAsync()
+ public IpcServer()
+ {
+ _dispose = new(DisposeCore);
+ }
+
+ public ValueTask DisposeAsync() => new(_dispose.Value);
+
+ private async Task DisposeCore()
{
Accepter? accepter = null;
lock (_lock)
@@ -77,6 +85,13 @@ private void OnNewConnectionError(Exception ex)
_stopped.TrySetException(ex);
}
+ internal RouterConfig CreateRouterConfig(IpcServer server) => RouterConfig.From(
+ server.Endpoints,
+ endpoint => endpoint with
+ {
+ Scheduler = endpoint.Scheduler ?? server.Scheduler
+ });
+
private sealed class ObserverAdapter : IObserver
{
public required Action OnNext { get; init; }
@@ -95,6 +110,7 @@ private sealed class Accepter : IAsyncDisposable
private readonly Task _running;
private readonly IObserver _newConnection;
private readonly TaskCompletionSource