Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added RemotingServer events to allow method interception #97

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading