I’ve found myself in need of a JSON-RPC implementation for a project I’m currently working on in C++. Unfortunately I couldn’t find any existing solutions that met my needs. Most of them implement far too many features, don’t take advantage of modern C++ features, and pull in a ton of dependencies. So I’ve decided to make my own, and document the process.
In order to make this work, I set my end goal as being able to send and receive JSON-RPC 2.0 messages. Rather than use HTTP for this, I am opting to use plain TCP sockets. To make the library actually useful, I need an easy way to bind functions to the RPC system, and that’s where I am going to start. As a sidenote, I am far from an expert at template programming but I refined it as much as I could.
As far as what I am using to actually make the function wrapper, I will be using JSON for Modern C++ and
C++17 features such as
The function wrapper should produce an invokable object that will accept a JSON object containing the function paramaters, and return the normal return type of the function. It should have nothing to with routing, response generation, or message processing.
Let’s look at the skeleton of the class and break it down from there
We can see that our template class defines the function signature with
T as our return and
Args as our function parameters. Next if
we look at the typedefs, we have
ttype as the tuple type which is applied to the function. After that we have
is the same as
ttype except all the constituent types are
std::optional. The defaults type is how we define fallbacks if a parameter
is missing, that way we can greatly simplify our RPC and reduce API breakages. We want it to fall back on a default as long as it isn’t
If we look at the constructor, you will see where we pass in our function, an array of parameter names, and the default paramaters. Other than that, our constructor doesn’t do anything.
Here’s an example of how you’d use it:
So without further ado, let’s look at at the
As you can see, I’ve implemented a special case here. Sometimes we might want
a function to have more specialized handling of parameters, so if there is only one parameter, and that parameter
json object, the entire params object is passed to it.
After checking for that case, it creates a
ttype tuple and unpacks the
params json object into it, then calls
the function with those arguments using
You can see that it’s starting to take shape now. The last thing to go over is parameter unpacking functions.
In order to unpack the parameters, we basically need to take the json
params object and load it into a tuple, performing all the
necessary conversions so that the function can be called. Let’s look at the implementation:
As with much template programming trickery, we use a recursive function to load the tuple.
The base case can be seen in the
ttype unpack_params_ method. It calls the templated version with an empty tuple, which then
recurses until it hits the arg count. The most important thing to note here is the
constexpr if which allows us to avoid having
to do any sort of specializations for the final case.
You can also see that each step calls a separate
param_handler_ function. Let’s move on to that.
The param handler looks through our
params json for a parameter with
param_name, then converts it to
U and returns it.
You can see we check if the value exists in
params and if not, we check the fallback. If there is no fallback, then
we throw an exception so that the message handler knows how the call failed. We also need to implement special logic for
json arguments and other types, as the
get function in the JSON library can’t convert to itself.
The most complex part of this is the error handling, as in order to implement the JSON-RPC protocol, we need to be able to get
information back on how exactly a call failed. I created
RPCInvalidParamsException for this, derived from
that way we can catch it specifically for the purposes of correctly generating error messages for the RPC protocol.
And with that, we have a functioning wrapper. At the end of this series I will put a link to the final code, so stay tuned.