Coder Social home page Coder Social logo

expecho / servicefabric-remoting-customheaders Goto Github PK

View Code? Open in Web Editor NEW
12.0 5.0 9.0 95 KB

This package allows injecting custom message headers into remoting messages (Actors and Reliable Services, V2 remoting only) at runtime. The headers are available client side to read. It also provides message interception using BeforeHandleRequestResponseAsync and AfterHandleRequestResponseAsync to act on remoting events.

License: MIT License

C# 87.20% PowerShell 12.80%
service-fabric azure azure-service-fabric reliable-actors reliable-stateful-service remoting

servicefabric-remoting-customheaders's Introduction

ServiceFabric.Remoting.CustomHeaders

This package allows injecting custom headers into remoting messages (Actors and Reliable Services, V2 remoting only) at runtime. The headers are available client side to read. It also provides message interception using BeforeHandleRequestResponseAsync and AfterHandleRequestResponseAsync to act on remoting events.

Common used classes:

NuGet

Download the NuGet package NuGet Status

Examples

This repository includes a Service Fabric application for demonstration purposes. A Console Application is used to access the application and shows the usage of the package.

Usage scenarios

Custom headers can be used to pass data between the sender and the receiver like tracing information or security context data. Using the BeforeHandleRequestResponseAsync and AfterHandleRequestResponseAsync actions additional logging can be applied monitor the flow between remoting calls.

How to use

Prepare Reliable Services

Modify the service and create a listener that can handle the requests

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
    yield return new ServiceInstanceListener(context =>
        new FabricTransportServiceRemotingListener(context,
            new ExtendedServiceRemotingMessageDispatcher(context, this)));
}

Prepare Actors

Register the actor using the ExtendedActorService service (usually done in the program.cs file):

ActorRuntime.RegisterActorAsync<DemoActor> (
(context, actorType) =>
   {
	   return new ExtendedActorService(context, actorType);
   }).GetAwaiter().GetResult();

Sender

On the sender side, create a proxy to the actor or service. The Create method accepts an instance of the CustomHeaders class:

Calling Reliable Services

var customHeaders = new CustomHeaders
{
	{"Header1", DateTime.Now},
	{"Header2", Guid.NewGuid()}
};

var serviceUri = new Uri("fabric:/ServiceFabric.Remoting.CustomHeaders.DemoApplication/DemoService");
var proxyFactory = new ServiceProxyFactory(handler => 
                    new ExtendedServiceRemotingClientFactory(
                        new FabricTransportServiceRemotingClientFactory(remotingCallbackMessageHandler: handler), customHeaders));
var proxy = proxyFactory.CreateServiceProxy<IDemoService>(serviceUri); // or  in case of actors
var actorResponse = proxy.SayHelloToActor().GetAwaiter().GetResult();

Sending Message to Actors

var customHeaders = new CustomHeaders
{
	{"Header1", DateTime.Now},
	{"Header2", Guid.NewGuid()}
};

var serviceUri = new Uri("fabric:/ServiceFabric.Remoting.CustomHeaders.DemoApplication/DemoService");
var proxyFactory = new ActorProxyFactory(handler => 
                    new ExtendedServiceRemotingClientFactory(
		        new FabricTransportActorRemotingClientFactory(handler), customHeaders));
var proxy = proxyFactory.CreateActorProxy<IDemoService>(serviceUri); 
var response = proxy.SayHello().GetAwaiter().GetResult();

There is an overload of the Create method that accepts a Func<CustomHeaders>. This is useful in scenarios where the created proxy factory or proxy is reused. Since creating a proxy factory is expensive this is the preferred way if you need dynamic header values. The func is invoked on every request made using the proxy:

var customHeadersProvider = new Func<CustomHeaders>(() => new CustomHeaders
{
	{"Header1", DateTime.Now},
	{"Header2", Guid.NewGuid()}
});
var serviceUri = new Uri("fabric:/ServiceFabric.Remoting.CustomHeaders.DemoApplication/DemoService");
var proxyFactory = new ServiceProxyFactory(handler =>
                    new ExtendedServiceRemotingClientFactory(
                        new FabricTransportServiceRemotingClientFactory(remotingCallbackMessageHandler: handler), customHeadersProvider));
var proxy = proxyFactory.CreateServiceProxy<IDemoService>(serviceUri);

Receiver

The receiving service or actor can extract the values in the custom headers using the RemotingContext class:

public async Task<string> SayHello()
{
	var remotingContext =
		string.Join(", ", RemotingContext.Keys.Select(k => $"{k}: {RemotingContext.GetData(k)}"));

	ServiceEventSource.Current.ServiceMessage(Context, $"SayHelloToActor got context: {remotingContext}");
	return Task.FromResult($"Got the following message headers: {remotingContext}")
}

Sample content of remotingContext:

Header1: 06/24/2018 08:30:18, Header2: 2c95548a-6efd-4855-82eb-29ea827be87b

Headers passthrough

In case the headers need to flow from one call to the other CustomHeaders.FromRemotingContext can be used as demonstrated:

public async Task<string> SayHelloToActor()
{
	var remotingContext =
		string.Join(", ", RemotingContext.Keys.Select(k => $"{k}: {RemotingContext.GetData(k)}"));

	ServiceEventSource.Current.ServiceMessage(Context, $"SayHelloToActor got context: {remotingContext}");
	var proxyFactory = new ActorProxyFactory(handler =>
                new ExtendedServiceRemotingClientFactory(
                    new FabricTransportActorRemotingClientFactory(handler), CustomHeaders.FromRemotingContext));
	var proxy = proxyFactory.CreateActorProxy<IDemoActor>(new ActorId(1));
	var response = await proxy.GetGreetingResponseAsync(CancellationToken.None);

	return $"DemoService passed context '{remotingContext}' to actor and got as response: {response}";
}

This removes the need to create a new CustomHeaders instance based on the current values in the RemotingContext.

Message interception

Messages can be intercepted on both the sending side and the receiving side. This can be used fo example to log method calls or performance.

Client-side message interception

On the receiving side messages can be intercepted using the BeforeHandleRequestResponseAsync and AfterHandleRequestResponseAsync extension points when creating a service listener:

For services

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
	yield return new ServiceInstanceListener(context =>
		new FabricTransportServiceRemotingListener(context,
			new ExtendedServiceRemotingMessageDispatcher(context, this)
			{
				// Optional, log the call before being handled
				BeforeHandleRequestResponseAsync = requestInfo =>
				{
					var sw = new Stopwatch();
					sw.Start();
					ServiceEventSource.Current.ServiceMessage(Context, $"BeforeHandleRequestResponseAsync {requestInfo.Service} {requestInfo.Method}");
					return Task.FromResult<object>(sw);
				},
				// Optional, log the call after being handled
				AfterHandleRequestResponseAsync = responseInfo =>
				{
					var sw = (Stopwatch) responseInfo.State;
					ServiceEventSource.Current.ServiceMessage(Context, $"AfterHandleRequestResponseAsync {responseInfo.Service} {responseInfo.Method} took {sw.ElapsedMilliseconds}ms");
					return Task.CompletedTask;
				}
			}));
}

For actors

ActorRuntime.RegisterActorAsync<DemoActor> (
(context, actorType) =>
   {
	   var service = new ExtendedActorService(context, actorType)
	   {
		   // Optional, allows call interception. Executed before the response is handled
		   BeforeHandleRequestResponseAsync = requestInfo =>
		   {
			   ActorEventSource.Current.Message($"BeforeHandleRequestResponseAsync {requestInfo.ActorService} {requestInfo.Method} for actor {requestInfo.ActorId.ToString()}");
			   return Task.CompletedTask;
		   },
		   // Optional, allows call interception. Executed after the response is handled
		   AfterHandleRequestResponseAsync = responseInfo =>
		   {
			   ActorEventSource.Current.Message($"AfterHandleRequestResponseAsync {responseInfo.ActorService} {responseInfo.Method} for actor {responseInfo.ActorId.ToString()}");
			   return Task.CompletedTask;
		   }
	   };
	   return service;
   }).GetAwaiter().GetResult();

Server-side message interception

On the sending side messages can be intercepted using the BeforeSendRequestResponseAsync and AfterSendRequestResponseAsync extension points when creating the ExtendedServiceRemotingClientFactory on constructor of the ServiceProxyFactory:

var proxyFactory = new ServiceProxyFactory(handler => // or ActorProxyFactory in case of actors
        new ExtendedServiceRemotingClientFactory(
            new FabricTransportServiceRemotingClientFactory(remotingCallbackMessageHandler: handler), customHeadersProvider)
        {
            // Optional, log the call before being handled
            BeforeSendRequestResponseAsync = requestInfo =>
            {
                var sw = new Stopwatch();
                sw.Start();
                Console.WriteLine($"BeforeSendRequestResponseAsync {requestInfo.Method}");
                return Task.FromResult<object>(sw);
            },
            // Optional, log the call after being handled
            AfterSendRequestResponseAsync = responseInfo =>
            {
                var sw = (Stopwatch)responseInfo.State;
                Console.WriteLine($"AfterSendRequestResponseAsync {responseInfo.Method} took {sw.ElapsedMilliseconds}ms");
                return Task.CompletedTask;
            }
        });

servicefabric-remoting-customheaders's People

Contributors

expecho avatar ttichy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

servicefabric-remoting-customheaders's Issues

Need to upgrade ServiceFabric references to 3.2.176

V3.0.9 currently uses SF 3.2.162, which causes all sorts of glorious SerializationExceptions when making remoting calls.

When NOT using CustomHeaders, using either SF 3.2.162 or 3.2.176 (latest) these issues do not occur. So I'm only asuming that if CustomHeaders were to use 3.2.176 then perhaps these problems would also go away?

Returning interface from Service

Is there any way to return interfaces from services? After running the demo project, returning concrete classes seems to work but I get a serialization exception when using an interface.

image

Memory Leak

I think there is a memory leak with how this is setup.
If you run the demo application and just hold the enter key (force it to do a bunch of requests in the loop) you will see in Process Explorer (or whatever you choose to use), the memory on DemoActor and DemoService increase over time and doesn't ever seem to be released.

I suspect it has something to do with the RemotingContext being static and using those AsyncLocal in the StateDictionary.
Is there something that needs to be done at the end of processing to clear out this memory? Or maybe the leak is somewhere else?

Thanks!
Will

Cast exception when communicating with ActorService

I am trying to delete the actor that is using ExtendedActorServiceRemotingDispatcher.

               var serviceProxy =
                    ActorServiceProxy.Create(
                        new Uri($"{FabricRuntime.GetActivationContext().ApplicationName}/MyActorService"),
                        actorId
                    );
                await serviceProxy.DeleteActorAsync(actorId, CancellationToken.None);

However, this line 34 var header = (IActorRemotingMessageHeaders)requestMessage.GetHeader(); throws

InvalidCastException: Unable to cast object of type 'Microsoft.ServiceFabric.Services.Remoting.V2.ServiceRemotingRequestMessageHeader' to type 'Microsoft.ServiceFabric.Actors.Remoting.V2.IActorRemotingMessageHeaders'.

I believe it happens because this call is not targeting a specific actor, but the actor service and thus doesn't have ActorId

Remoting Context is not thread safe?

Logging as an issue but maybe just a question.
The RemotingContext seems to not be thread safe. If new headers are passed with each call through the proxy, how does service fabric guarantee the proper headers get to the corresponding service method?

Can someone identify if this is an issue, or explain how it is not?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.