Skip to content

Commit

Permalink
Added more remote call processing events to allow method interception,
Browse files Browse the repository at this point in the history
…close #92.
  • Loading branch information
yallie committed Dec 6, 2024
1 parent 1e72abb commit 95bedb5
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 62 deletions.
69 changes: 68 additions & 1 deletion CoreRemoting.Tests/RpcTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -768,9 +768,72 @@ void AfterCall(object sender, ServerRpcContext e) =>
}

[Fact]
public void Authentication_is_taken_into_account()
public void BeginCall_event_handler_can_intercept_and_cancel_method_calls()
{
var counter = 0;

void InterceptMethodCalls(object sender, ServerRpcContext e)
{
Interlocked.Increment(ref counter);

// swap Echo and Reverse methods
e.MethodCallMessage.MethodName = e.MethodCallMessage.MethodName switch
{
"Echo" => "Reverse",
"Reverse" => "Echo",
var others => others
};

// disable IHobbitService
if (e.MethodCallMessage.ServiceName.Contains("IHobbitService"))
{
e.Cancel = true;
}
}

_serverFixture.Server.BeginCall += InterceptMethodCalls;
try
{
using var client = new RemotingClient(new ClientConfig()
{
ConnectionTimeout = 0,
InvocationTimeout = 0,
SendTimeout = 0,
Channel = ClientChannel,
MessageEncryption = false,
ServerPort = _serverFixture.Server.Config.NetworkPort,
});

client.Connect();

// try swapped methods
var proxy = client.CreateProxy<ITestService>();
Assert.Equal("321", proxy.Echo("123"));
Assert.Equal("Hello", proxy.Reverse("Hello"));

// try disabled service
var hobbit = client.CreateProxy<IHobbitService>();
Assert.Throws<RemoteInvocationException>(() =>
hobbit.QueryHobbits(h => h.LastName != ""));

// check interception counter
Assert.Equal(3, counter);
}
finally
{
_serverFixture.Server.BeginCall -= InterceptMethodCalls;
}
}

[Fact]
public void Authentication_is_taken_into_account_and_RejectCall_event_is_fired()
{
var rejectedMethod = string.Empty;
void RejectCall(object sender, ServerRpcContext e) =>
rejectedMethod = e.MethodCallMessage.MethodName;

_serverFixture.Server.Config.AuthenticationRequired = true;
_serverFixture.Server.RejectCall += RejectCall;
try
{
using var client = new RemotingClient(new ClientConfig()
Expand All @@ -790,10 +853,14 @@ public void Authentication_is_taken_into_account()

// Session is not authenticated
Assert.Contains("authenticated", ex.Message);

// Method call was rejected
Assert.Equal("Hello", rejectedMethod);
}
finally
{
_serverFixture.Server.Config.AuthenticationRequired = false;
_serverFixture.Server.RejectCall -= RejectCall;
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions CoreRemoting.Tests/Tools/ITestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public interface ITestService : IBaseService

string Echo(string text);

string Reverse(string text);

void MethodWithOutParameter(out int counter);

void Error(string text);
Expand Down
6 changes: 6 additions & 0 deletions CoreRemoting.Tests/Tools/TestService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using CoreRemoting.Tests.ExternalTypes;

Expand Down Expand Up @@ -54,6 +55,11 @@ public string Echo(string text)
return text;
}

public string Reverse(string text)
{
return new string(text.Reverse().ToArray());
}

public void MethodWithOutParameter(out int counter)
{
_counter++;
Expand Down
34 changes: 22 additions & 12 deletions CoreRemoting/IRemotingServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,70 @@ namespace CoreRemoting
public interface IRemotingServer : IDisposable
{
/// <summary>
/// Event: Fires before an RPC call is invoked.
/// Event: Fires when an RPC call is prepared and can be canceled.
/// </summary>
event EventHandler<ServerRpcContext> BeginCall;

/// <summary>
/// Event: Fires just before an RPC call is invoked.
/// </summary>
event EventHandler<ServerRpcContext> BeforeCall;

/// <summary>
/// Event: Fires after an RPC call is invoked.
/// Event: Fires after an RPC call is invoked, both on success or failure.
/// </summary>
event EventHandler<ServerRpcContext> AfterCall;

/// <summary>
/// Event: Fires if an error occurs.
/// </summary>
event EventHandler<Exception> Error;


/// <summary>
/// Event: Fires when an RPC call is rejected before BeginCall event. .
/// </summary>
event EventHandler<ServerRpcContext> RejectCall;

/// <summary>
/// Gets the unique name of this server instance.
/// </summary>
string UniqueServerInstanceName { get; }

/// <summary>
/// Gets the dependency injection container that is used a service registry.
/// </summary>
IDependencyInjectionContainer ServiceRegistry { get; }

/// <summary>
/// Gets the configured serializer.
/// </summary>
ISerializerAdapter Serializer { get; }

/// <summary>
/// Gets the component for easy building of method call messages.
/// </summary>
MethodCallMessageBuilder MethodCallMessageBuilder { get; }

/// <summary>
/// Gets the component for encryption and decryption of messages.
/// </summary>
IMessageEncryptionManager MessageEncryptionManager { get; }

/// <summary>
/// Gets the session repository to perform session management tasks.
/// </summary>
ISessionRepository SessionRepository { get; }

/// <summary>
/// Gets the configuration settings.
/// </summary>
ServerConfig Config { get; }

/// <summary>
/// Starts listening for client requests.
/// </summary>
void Start();

/// <summary>
/// Stops listening for client requests and close all open client connections.
/// </summary>
Expand Down
50 changes: 45 additions & 5 deletions CoreRemoting/RemotingServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using CoreRemoting.Serialization;
using CoreRemoting.Serialization.Bson;
using ServiceLifetime = CoreRemoting.DependencyInjection.ServiceLifetime;
using System.Runtime.ExceptionServices;

namespace CoreRemoting
{
Expand Down Expand Up @@ -91,7 +92,17 @@ public RemotingServer(ServerConfig config = null)
/// Event: Fires if an error occurs.
/// </summary>
public event EventHandler<Exception> Error;


/// <summary>
/// Event: Fires when an RPC call is rejected before BeforeCall event.
/// </summary>
public event EventHandler<ServerRpcContext> RejectCall;

/// <summary>
/// Event: Fires when an RPC call is prepared and can be canceled.
/// </summary>
public event EventHandler<ServerRpcContext> BeginCall;

/// <summary>
/// Gets the dependency injection container that is used a service registry.
/// </summary>
Expand Down Expand Up @@ -134,7 +145,7 @@ public RemotingServer(ServerConfig config = null)
public IServerChannel Channel { get; private set; }

/// <summary>
/// Fires the OnBeforeCall event.
/// Fires the <see cref="BeforeCall"/> event.
/// </summary>
/// <param name="serverRpcContext">Server side RPC call context</param>
internal void OnBeforeCall(ServerRpcContext serverRpcContext)
Expand All @@ -143,7 +154,7 @@ internal void OnBeforeCall(ServerRpcContext serverRpcContext)
}

/// <summary>
/// Fires the OnAfterCall event.
/// Fires the <see cref="AfterCall"/> event.
/// </summary>
/// <param name="serverRpcContext">Server side RPC call context</param>
internal void OnAfterCall(ServerRpcContext serverRpcContext)
Expand All @@ -152,14 +163,43 @@ internal void OnAfterCall(ServerRpcContext serverRpcContext)
}

/// <summary>
/// Fires the OnError event.
/// Fires the <see cref="Error"/> event.
/// </summary>
/// <param name="ex">Exception that describes the occurred error</param>
internal void OnError(Exception ex)
{
Error?.Invoke(this, ex);
}


/// <summary>
/// Fires the <see cref="RejectCall"/> event.
/// </summary>
/// <param name="serverRpcContext">Server side RPC call context</param>
internal void OnRejectCall(ServerRpcContext serverRpcContext)
{
RejectCall?.Invoke(this, serverRpcContext);
}

/// <summary>
/// Fires the <see cref="BeginCall"/> event.
/// </summary>
/// <param name="serverRpcContext">Server side RPC call context</param>
internal void OnBeginCall(ServerRpcContext serverRpcContext)
{
BeginCall?.Invoke(this, serverRpcContext);

if (serverRpcContext.Cancel)
{
var cancelEx = serverRpcContext.Exception ??
new RemoteInvocationException($"Invocation canceled: {
serverRpcContext.MethodCallMessage.ServiceName}.{
serverRpcContext.MethodCallMessage.MethodName}");

// rethrow the exception keeping the original stack trace
ExceptionDispatchInfo.Capture(cancelEx).Throw();
}
}

/// <summary>
/// Starts listening for client requests.
/// </summary>
Expand Down
Loading

0 comments on commit 95bedb5

Please sign in to comment.