services

InterfaceRpc 2.0

This past week my Interface RPC library hit version 2.0 and I want to take a minute to go over how I got there.

I used the first version for a few services that my company depends on. It was running in production just fine, for a while. After a couple months I found an issue however. The issue was with how exceptions were handled by my code which in-turn caused an additional exception. The result of this caused threads to hang and eventually starve the system. It was a simple fix and services continued to run fine.

I ran into a second problem a few months later. This one, however, was much more serious and caused a service to hang until restarted. I knew what method was being called to cause it but that's about it. I didn't do much digging other than that because I didn't want to maintain the HttpListener implementation of this library any more. It was a stop-gap for how I really wanted it to work anyways...

Ideally I wanted to host the service on ASP.NET Core and plug into that ecosystem of things, like; depenency injection, configuration, and security.

So that's what I did. Instead of fixing a bug I chucked most of my code, refactored what was left (A LOT), and made a 2.0 I felt a whole lot better about.

I've had 2.0 running in production for months now with no issues. This past week a co-worker tidy'd up one last thing which pushed me over the edge in removing the pre-release moniker and published the official 2.0 nuget package! Let's take a look.

Service Setup

This is done during Startup within the Configure method. The RPC service is essentially now middleware in the ASP.NET pipeline.

public void Configure(IApplicationBuilder app)
{
    app.UseRpcService<IDemoService>(options => {...});
}

The options you can configure are

  • Prefix - hosts the service under a virtual subdirectory. This is useful for hosting multiple services in the same app.
  • AuthorizationScope - either None, Required, or AdHoc (default). If AdHoc then authorization will only be checked when your method is decorated with AuthorizeAttribute.
  • ServiceFactory - a function that returns the instance of the interface. You can grab this from ASP.NET's DI container by calling app.ApplicationServices.GetService<IDemoService>()
  • AuthorizationHandler - a function that returns true for authorized. See below for an example.

Here we are looking for a security token in the Authorization header to determine if a user is authorized to access the service.

options.AuthorizationHandler = (methodName, instance, context) =>
{
    if(!context.Request.Headers.ContainsKey("Authorization"))
    {
        return false;
    }
    if(!context.Request.Headers.TryGetValue("Authorization", out StringValues authHeader))
    {
        return false;
    }
    var bearerCredentials = authHeader.ToString().Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[1];
    var tokenHandler = new JwtSecurityTokenHandler();

    var validationParameters = new TokenValidationParameters
    {
        IssuerSigningKey = _signingCredentialStore.GetSigningCredentialsAsync().GetAwaiter().GetResult().Key,
        ValidAudience = "demo-service",
        ValidIssuer = "https://sso.domain.com"
    };

    var user = tokenHandler.ValidateToken(bearerCredentials, validationParameters, out SecurityToken securityToken);
    var token = securityToken as JwtSecurityToken;
    return token != null && token.ValidTo >= DateTime.Now;
};

_signingCredentialStore is defined in the Startup class as private static ISigningCredentialStore _signingCredentialStore; and is set in the ConfigureServices method by calling serviceProvider.GetService<ISigningCredentialStore>()

IDemoService is also configured in ConfigureServices just like any other implementation would.

services.AddTransient<IDemoService, DemoService>();

Creating a Client

The client code hasn't changed much.

var client = RpcClient<IDemoService>.Create(new RpcClientOptions
{
    BaseAddress = "https://demo.domain.com",
    SetAuthorizationHeaderAction = () =>
    {
        return new RpcClientAuthorizationHeader
        {
            Credentials = "access token",
            Type = "Bearer"
        };
    }
});

SetAuthorizationHeaderAction will be called before every request.

Wrapping Up

Everything else pretty much works the same as before. The service responds to POST requests where the method name in the URL translates to a method on the interface. Serialization can be any type defined in the SerializerDotNet library (JSON and Protobuf currently).

One thing that is not apparent is that the service and client both handle async methods.

I hope you find this useful and choose to use it for your next project! If you'd like to contribute you can find the source on GitHub.

Interface to RPC Service

I'm currently working on a project to quickly stand up an RPC service based on an interface. It's a .NET Standard library. Here's how it works.

Let's say you have an interface,

public interface IEchoService
{
    string Echo(string echo);
}

, and you have an implementation that needs to be exposed so that others can call it. I chose HTTP for this.

On the server I use HttpListener to create a web server and listen for requests. When a request comes in the path maps to a method name. For example, if my web server is listening on http://localhost:6000/ and a request for http://localhost:6000/Echo comes in - that maps to the Echo method on the interface... then I use reflection to call it on the implementation that was provided.

If Echo didn't map to a method on the interface then a 404 response is returned.

In my library all HTTP requests are POSTs. The content is deserialized based upon the Content-Type header value of the request. I am using another library I created to handle this called SerializerDotNet. At the time of writing it supports JSON and Protobuf. The same serializer used for deserialization is also used to return data in the response and the response's Content-Type is the same as the incoming request.

Remember when I said "quickly stand up an RPC service"? This is how quickly it's done...

var svc = new RpcService<IEchoService>(new EchoService());
svc.Start();

To use this in your own project add InterfaceRpc.Service to your project using NuGet.

There is a file called rpcsettings.json where you can specify additional settings. Currently only the web server prefixes and number of connections.

To create a client it's just as easy...

var client = new RpcClient<IEchoService>.Create("http://localhost:6000/");

To use this in your own project add InterfaceRpc.Client to your project using NuGet.

By default it uses JsonSerializer. There is an optional 2nd argument where you can specify the type of ISerializer (like ProtobufSerializer)

Then you can call any method defined in your interface:

var echoed = client.Echo("hello");

Limitations

(stuff I could use help on)

  • I punted on handling SSL certs so it only runs over plain HTTP right now. This seems reasonable given that it's common to setup a load balancer or proxy in front of web sites and services.

  • 8 parameters in method signatures - this can expand to more in the future, if needed, but I think once you get this many it's better to create a class with properties for each and use 1 parameter in the signature.

  • Because of limitations with ValueTuple, text based serialization reads a little funky, but I'm sure Microsoft will fix this in future versions of .NET Standard. In the meantime I'm curious to see what people think about having "Item1", "Item2", etc... map to method parameters. There could be some work done to replace these with parameter names but it would add (unnecessary?) overhead.

  • A good name. Any suggestions?

  • See the issues list on github for more

Why?

This will help tremendously in moving off of WCF / .NET Framework and onto .NET Core. WCF clients aren't supported in .NET Core. The good thing about WCF is that it is interface-based, so swapping out a server implementation is relatively easy. Assuming a ChannelFactory was used for the client, it can be replaced even easier.

If you'd like to help in fleshing this idea out more please contribute through the github repo!

InterfaceRpc.Service NuGet Status
InterfaceRpc.Client NuGet Status