MACE  1.0.0
 All Classes Namespaces Files Functions Variables Enumerations Defines
JSON-RPC
Remote Procedure Call Library

The MACE.RPC library supports JSON-RPC 1.0 / 2.0 calls over TCP or HTTP. It also transparently supports passing callback methods over RPC assuming your client and server both understand the convention.

At the most basic level is the mace::rpc::json::connection which provides 'raw' access to the JSON-RPC protocol. Each connection allows you to register methods to be called and make calls to the remote connection. You should only use a 'raw' connection if API you are interfacing to is highly dynamic and cannot be represented as a C++ struct.

In the following sections examples will be provided on how to create both client and servers that handle JSON-RPC over either TCP or HTTP. Because MACE.RPC leverages the MACE Stub Library to provide a native calling syntax, it is recommended that you review: Stub Basic Example (Type Erasure)

JSON-RPC Client over TCP

Given a basic calculator api:

  struct calculator {
    double add( double, double );
    double sub( double, double );
    double mult( double, double );
    double div( double, double );
  }
  MACE_STUB( calculator, (add)(sub)(mult)(div) )

You would create a JSON-RPC TCP client as follows:

  mace::rpc::json::client<calculator>  calc( host, port );

Which can be used as follows:

  try {
    // synchronous
    double result = calc->add( 4.4, 5.5 );
    
    // asynchronous
    mace::cmt::future<double> sr = calc->sub( 3.3, 2.2 );
    mace::cmt::future<double> mr = calc->mult( 3.3, 2.2 );
    mace::cmt::future<double> dr = calc->div( 3.3, 2.2 );
    
    std::cout<<sr.wait()<<", "<<mr.wait()<<", "<<dr.wait()<<std::endl;
  } catch ( const mace::rpc::exception& e ) {
    std::cerr<<e.message()<<std::endl;
  }

In this example, add will occur first, then sub, mult, and div will occur asynchronously and will not block until you call mace::cmt::future<T>::wait(). If an error object is received then an exception will be thrown. Exceptions will also be thrown if the connection is closed while waiting on the result.

Sending Notices

If you would like to send a notice then you would use the following code:

  calc->add.notice(4.4,5.5); // return void.

Named Parameters

MACE.RPC's support for JSON-RPC 2.0 includes Named Parameter support. To enable named parameters, you must modify your interface. Consider the following interface:

  struct vec : public mace::rpc::json::named_parameters{
    boost::optional<double> x;
    boost::optional<double> y;
    boost::optional<double> z;
  };
  MACE_REFLECT( vec, (x)(y)(z) );

  struct vec_calc : calculator {
    double dot( const vec& v );
  };
  MACE_STUB_DERIVED( vec_calc, (calculator), (dot) )

Notice that the dot( const vec& v ) method takes exactly one parameter. Normally, this parameter would be passed as:

  { "jsonrpc":"2.0", "method":"dot", "params":[{"x":1.0,"y":2.0,"z":3.0}]}

But because vec inherits mace::rpc::json::named_parameters, this single parameter will be translated as:

  { "jsonrpc":"2.0", "method":"dot", "params":{"x":1.0,"y":2.0,"z":3.0}}

Because the fields in vec are of type boost::optional<T>, if no value is set then the field will be left out of the JSON string entirely.

RPC Callbacks

If your interface contains any parameters or return values that include a boost::function<Signature> type then that function will be captured and registered with the client. The server will receive a the callback name and pass a functor that knows how to make the callback over the JSON-RPC connection to the registered method. This process is entirely transparent to both sides of the connection.

Consider the following interface:

  struct stock_ticker {
    void set_callback( boost::function<void(std::string,double)>& quote );
  };
  MACE_STUB( stock_ticker, (set_callback) )

Your client could then be implemented as like so:

  void print_stock( const std::string& name, double price ) {
    std::cout<<name<<":"<<price<<std::endl;
  }
  int main( int argc, char** argv ) {
    mace::rpc::json::client<stock_ticker> c(argv[1],atoi(argv[2]));
    c->set_callback( print_stock );
    mace::cmt::exec();
  }

Type Erasure

Because MACE.RPC leverages the Stub Library we can completely hide the fact that our algorithms are using a remote class via type erasure.

  mace::rpc::json::client<stock_ticker> c(argv[1],atoi(argv[2]));
  mace::stub::ptr<stock_ticker> st(c);
  st->set_callback(print_stock);

In this case the caller only knows about the mace::stub::ptr and has no knowledge that in reality they are making RPC calls. When you use this abstraction all RPC calls are synchronous and you lose the ability to perform RPC-specific actions like specifying a call should be sent as a notice.

If you would like to preserve the asynchronous features but still abstract the RPC implementation then you can use:

  mace::rpc::json::client<stock_ticker> c(argv[1],atoi(argv[2]));
  mace::actor::ptr<stock_ticker> st(c);
  st->set_callback(print_stock);

JSON-RPC Server over TCP

JSON-RPC Client over HTTP (Bitcoin Client)

The default HTTP client will open a new HTTP connection for each call and uses cpp-netlib's network library to peform the HTTP request. You use it like the TCP client except that certain features, like callbacks, are not supported.

In this example the client should interface with your local bitcoin server using the Bitcoin API

JSON-RPC Server over HTTP