C ++のJSON-RPC

宣言型JSONシリアライザーに関する以前の記事で、C ++テンプレートを使用してデータ構造を記述し、それらをシリアル化する方法について説明しました。 これは非常に便利です コードのサイズを小さくするだけでなく、発生する可能性のあるエラーの数を最小限に抑えます。 ほぼ同じアプローチがwjrpcに適用されました。これについては、この記事で説明します。 しかし、wjrpcはインターフェイス用に開発されたフレームワークから「引き裂かれた」ため、アーキテクチャと非同期インターフェイスの問題にも触れます。









wjrpcが実行され、メッセージデータ構造のJSON記述が実装されるJSONシリアライザーについては説明しません。 前の記事でwjsonについて話しました。 JSON-RPCのAPIサービスの説明の宣言バージョンを検討する前に、「手動」で解析を実装する方法を検討します。 これには、データを取得して検証するためにより多くのランタイムコードを記述する必要がありますが、理解する方が簡単です。 すべてのサンプルは、プロジェクトのサンプルセクションにあります。







例として、加算、減算、乗算、および除算演算を実行できる単純な計算機のAPIを検討してください。 クエリ自体は簡単なので、追加操作のコードのみを提供します。







calc / api / plus.hpp
#pragma once #include <memory> #include <functional> namespace request { struct plus { int first=0; int second=0; typedef std::unique_ptr<plus> ptr; }; } // request namespace response { struct plus { int value=0; typedef std::unique_ptr<plus> ptr; typedef std::function<void(ptr)> callback; }; } // response
      
      





この目的のために指定されたフォルダー内の個別のファイルに、各要求/応答ペアの構造を配置することを好みます。 これにより、プロジェクト内を簡単に移動できます。 上記の例に示すように、ネームスペースを使用してヘルパータイプを定義する必要はありませんが、コードの可読性は向上します。 これらのtypedefの意味については、後で説明します。







要求ごとに、JSON記述を作成します。







calc / api / plus_json.hpp
 #pragma once #include "calc/api/plus.hpp" #include <wjson/json.hpp> #include <wjson/name.hpp> namespace request { struct plus_json { JSON_NAME(first) JSON_NAME(second) typedef wjson::object< plus, wjson::member_list< wjson::member<n_first, plus, int, &plus::first>, wjson::member<n_second, plus, int, &plus::second> > > type; typedef typename type::serializer serializer; typedef typename type::target target; }; } namespace response { struct plus_json { JSON_NAME(value) typedef wjson::object< plus, wjson::member_list< wjson::member<n_value, plus, int, &plus::value> > > type; typedef typename type::serializer serializer; typedef typename type::target target; }; }
      
      





それを構造に入れる理由は、wjsonの記事で説明しました。 ここでは、これらの構造がJSON記述として認識されるためにtypedefの定義が必要であることに注意してください。







JSON-RPCメッセージの処理は、2つの段階に分けることができます。 最初の段階で、リクエストのタイプとメソッドの名前を決定し、2番目に、このメソッドのパラメーターをデシリアライズする必要があります。







plus用のシリアル化されたJSON-RPC
 <p>{ "jsonrpc":"2.0", "method":"plus", "params":{ "first":2, "second":3 }, "id":1 }</p> <source><!--</spoiler>--> ,         : <!--<spoiler title="  ">--> ```cpp struct request { std::string version, std::string method, std::string params, std::string id }
      
      





リクエストのJSONの説明
 JSON_NAME(jsonrpc) JSON_NAME(method) JSON_NAME(params) JSON_NAME(id) typedef wjson::object< request, wjson::member_list< wjson::member<n_jsonrpc, request, std::string, &request::version>, wjson::member<n_method, request, std::string, &request::method>, wjson::member<n_params, request, std::string, &request::params, json::raw_value<> >, wjson::member<n_id, request, std::string, &request::id, json::raw_value<> > > > request_json;
      
      





この説明では、 request::params



およびrequest::id



フィールドにjsonが変換されずにそのままコピーされ、 request::method



フィールドに実際のメソッド名があります。 メソッドの名前を定義したら、上記の構造を使用してパラメーターを逆シリアル化できます。







メソッド名を決定するために、クエリ全体を中間データ構造に逆シリアル化する必要はありません。 それを解析し、paramsフィールドに関連するリクエストの一部のみをデシリアライズすれば十分です。 これはwjson ::パーサーを直接使用して行うことができますが、wjsonはraw_pairコンストラクトも提供します(以前の記事では考慮しませんでした)。 これがwjrpcでどのように実装されているかを見てみましょう。







まず、wjrpcはstd :: string文字列では機能しませんが、次のタイプを定義します。







 namespace wjrpc { typedef std::vector<char> data_type; typedef std::unique_ptr<data_type> data_ptr; }
      
      





data_ptrを移動可能なバッファーと見なすことができます。これを使用することで、データが誤って再度コピーされないことを保証できます。







着信するwjrpcメッセージは、次の構造にデシリアライズしようとします。







wjrpc ::着信
 namespace wjrpc { struct incoming { typedef data_type::iterator iterator; typedef std::pair<iterator, iterator> pair_type; pair_type method; pair_type params; pair_type result; pair_type error; pair_type id; }; }
      
      





すべてのwjrpc ::着信要素は、入力バッファ内のイテレータのペアです。 たとえば、逆シリアル化中に、method.firstは入力要求のコロンの後にメソッド名を開く引用符を指し、method.secondは閉じ引用符の次の位置を指します。 この構造は、要求だけでなく、要求への応答、およびエラーメッセージも記述します。 フィールドに入力することにより、メッセージのタイプを判別するのは非常に簡単です。 そのような構造のJSON記述:







wjrpc :: incoming_json
 namespace wjrpc { struct incoming_json { typedef incoming::pair_type pair_type; typedef wjson::iterator_pair<pair_type> pair_json; JSON_NAME(id) JSON_NAME(method) JSON_NAME(params) JSON_NAME(result) JSON_NAME(error) typedef wjson::object< incoming, wjson::member_list< wjson::member<n_method, incoming, pair_type, &incoming::method, pair_json>, wjson::member<n_params, incoming, pair_type, &incoming::params, pair_json>, wjson::member<n_result, incoming, pair_type, &incoming::result, pair_json>, wjson::member<n_error, incoming, pair_type, &incoming::error, pair_json>, wjson::member<n_id, incoming, pair_type, &incoming::id, pair_json> > > type; typedef type::target target; typedef type::member_list member_list; typedef type::serializer serializer; }; }
      
      





本格的なデシリアライゼーションはありません-これは、入力バッファに位置を保存することで本質的に解析されます。 明らかに、そのような逆シリアル化の後、データは入力バッファが存在する間のみ有効になります。







wjrpc :: incoming_holderクラスは、リクエストバッファをキャプチャし、上記の構造に解析します。 インターフェースの詳細は説明しませんが、JSON-RPCの実装に使用する方法を示します。







最初に単純化された1つのメソッドの例
 #include "calc/api/plus.hpp" #include "calc/api/plus_json.hpp" #include <wjrpc/errors/error_json.hpp> #include <wjrpc/incoming/incoming_holder.hpp> #include <wjrpc/outgoing/outgoing_holder.hpp> #include <wjrpc/outgoing/outgoing_result.hpp> #include <wjrpc/outgoing/outgoing_result_json.hpp> #include <wjrpc/outgoing/outgoing_error.hpp> #include <wjrpc/outgoing/outgoing_error_json.hpp> #include <iostream> int main() { std::vector<std::string> req_list = { "{\"method\":\"plus\", \"params\":{ \"first\":2, \"second\":3 }, \"id\" :1 }", "{\"method\":\"minus\", \"params\":{ \"first\":5, \"second\":10 }, \"id\" :1 }", "{\"method\":\"multiplies\", \"params\":{ \"first\":2, \"second\":2 }, \"id\" :1 }", "{\"method\":\"divides\", \"params\":{ \"first\":9, \"second\":3 }, \"id\" :1 }" }; std::vector<std::string> res_list; for ( auto& sreq : req_list ) { wjrpc::incoming_holder inholder( sreq ); //      inholder.parse(nullptr); //       if ( inholder.method() == "plus" ) { //     auto params = inholder.get_params<request::plus_json>(nullptr); //    wjrpc::outgoing_result<response::plus> res; res.result = std::make_unique<response::plus>(); //   res.result->value = params->first + params->second; //  id      auto raw_id = inholder.raw_id(); res.id = std::make_unique<wjrpc::data_type>( raw_id.first, raw_id.second ); //   typedef wjrpc::outgoing_result_json<response::plus_json> result_json; res_list.push_back(std::string()); result_json::serializer()( res, std::back_inserter(res_list.back()) ); } /* else if ( inholder.method() == "minus" ) { ... } */ /* else if ( inholder.method() == "multiplies" ) { ... } */ /* else if ( inholder.method() == "divides" ) { ... } */ } for ( size_t i =0; i != res_list.size(); ++i) { std::cout << req_list[i] << std::endl; std::cout << res_list[i] << std::endl; std::cout << std::endl; } }
      
      





ここでは、エラーのチェックを行っていないため、ほとんどのコードが回答の形成に占有されています。 まず、incoming_holderを文字列で初期化して解析します。 この時点で、入力文字列は上記の着信構造に逆シリアル化されます。 文字列に有効なjsonオブジェクトが含まれている場合、このステップはエラーなしで合格します。







次に、リクエストのタイプを決定する必要があります。 これは、「method」、「result」、「error」、および「id」フィールドの有無によって簡単に実行できます。







組み合わせ メッセージの種類 確認する 取得する
メソッドとID リクエスト is_request get_params <>
IDのないメソッド 気づく is_notify get_params <>
結果とID リクエストへの応答 is_response get_result <>
エラーとID リクエストへの応答エラー is_request_error get_error <>
IDなしのエラー その他のエラー is_other_error get_error <>


いずれの条件も満たされない場合、リクエストが正しくないことは明らかです。







チェックと1つのメソッドの例
 #include "calc/api/plus.hpp" #include "calc/api/plus_json.hpp" #include <wjrpc/errors/error_json.hpp> #include <wjrpc/incoming/incoming_holder.hpp> #include <wjrpc/outgoing/outgoing_holder.hpp> #include <wjrpc/outgoing/outgoing_result.hpp> #include <wjrpc/outgoing/outgoing_result_json.hpp> #include <wjrpc/outgoing/outgoing_error.hpp> #include <wjrpc/outgoing/outgoing_error_json.hpp> #include <iostream> int main() { std::vector<std::string> req_list = { "{\"method\":\"plus\", \"params\":{ \"first\":2, \"second\":3 }, \"id\" :1 }", "{\"method\":\"minus\", \"params\":{ \"first\":5, \"second\":10 }, \"id\" :1 }", "{\"method\":\"multiplies\", \"params\":{ \"first\":2, \"second\":2 }, \"id\" :1 }", "{\"method\":\"divides\", \"params\":{ \"first\":9, \"second\":3 }, \"id\" :1 }" }; std::vector<std::string> res_list; for ( auto& sreq : req_list ) { wjrpc::incoming_holder inholder( sreq ); wjson::json_error e; inholder.parse(&e); if ( e ) { typedef wjrpc::outgoing_error<wjrpc::error> error_type; error_type err; err.error = std::make_unique<wjrpc::parse_error>(); typedef wjrpc::outgoing_error_json<wjrpc::error_json> error_json; std::string str; error_json::serializer()(err, std::back_inserter(str)); res_list.push_back(str); } else if ( inholder.is_request() ) { auto raw_id = inholder.raw_id(); auto call_id = std::make_unique<wjrpc::data_type>( raw_id.first, raw_id.second ); //       if ( inholder.method() == "plus" ) { auto params = inholder.get_params<request::plus_json>(&e); if ( !e ) { wjrpc::outgoing_result<response::plus> res; res.result = std::make_unique<response::plus>(); res.result->value = params->first + params->second; res.id = std::move(call_id); typedef wjrpc::outgoing_result_json<response::plus_json> result_json; std::string str; result_json::serializer()( res, std::back_inserter(str) ); res_list.push_back(str); } else { typedef wjrpc::outgoing_error<wjrpc::error> error_type; error_type err; err.error = std::make_unique<wjrpc::invalid_params>(); err.id = std::move(call_id); typedef wjrpc::outgoing_error_json<wjrpc::error_json> error_json; std::string str; error_json::serializer()(err, std::back_inserter(str)); res_list.push_back(str); } } /* else if ( inholder.method() == "minus" ) { ... } */ /* else if ( inholder.method() == "multiplies" ) { ... } */ /* else if ( inholder.method() == "divides" ) { ... } */ else { typedef wjrpc::outgoing_error<wjrpc::error> error_type; error_type err; err.error = std::make_unique<wjrpc::procedure_not_found>(); err.id = std::move(call_id); typedef wjrpc::outgoing_error_json<wjrpc::error_json> error_json; std::string str; error_json::serializer()(err, std::back_inserter(str)); res_list.push_back(str); } } else { typedef wjrpc::outgoing_error<wjrpc::error> error_type; error_type err; err.error = std::make_unique<wjrpc::invalid_request>(); typedef wjrpc::outgoing_error_json<wjrpc::error_json> error_json; std::string str; error_json::serializer()(err, std::back_inserter(str)); res_list.push_back(str); } } for ( size_t i =0; i != res_list.size(); ++i) { std::cout << req_list[i] << std::endl; std::cout << res_list[i] << std::endl; std::cout << std::endl; } }
      
      





ここで、ほとんどのコードはエラー処理、または対応するメッセージの形成です。 しかし、すべてのタイプのエラーについてはコードは似ており、違いはエラーのタイプのみです。 1つの定型関数を作成して、すべてのタイプのエラーをシリアル化できます。







エラー報告
 template<typename E> void make_error(wjrpc::incoming_holder inholder, std::string& out) { typedef wjrpc::outgoing_error<wjrpc::error> common_error; common_error err; err.error = std::make_unique<E>(); if ( inholder.has_id() ) { auto id = inholder.raw_id(); err.id = std::make_unique<wjrpc::data_type>(id.first, id.second); } typedef wjrpc::outgoing_error_json<wjrpc::error_json> error_json; error_json::serializer()(err, std::back_inserter(out)); }
      
      





エラーメッセージを生成するには、エラーの種類と呼び出し識別子(存在する場合)を知るだけです。 インホルダーオブジェクトは再配置可能であり、メッセージの形成後は不要になります。 この例では、呼び出し識別子を取得するためにのみ使用されますが、そこから入力バッファを「ピックアップ」して、そこにメッセージをシリアル化して、新しいバッファを作成しないようにすることもできます。







同様に、結果のシリアル化を実装できます。 しかし、同じタイプのコードを取り除き続ける前に、アプリケーション部分を整理します。アプリケーション部分はここでは多少失われ、各メソッドに対して1行だけで表されます。







インターフェース



前述したように、wjrpcは、コンポーネントのインターフェイスを明示的に定義する必要があるフレームワークから引き裂かれています。 さらに、これは純粋な仮想メソッドだけの構造ではなく、メソッドのパラメーターに特定の制限が課せられます。







フィールドが1つしかない場合でも、すべての入力および出力パラメーターを結合して構造体にする必要があります。 これは、リクエストのJSON記述を生成するのに便利です。事前に変換せずに、デシリアライズされた構造をメソッドに直接渡すことができる場合だけでなく、拡張性の点からも便利です。







たとえば、すべてのメソッドで、数値-操作の結果を返します。 可能であれば数字で1つのフィールドを持つ構造体で結果を説明する意味はありますか? また、一般的に入力パラメーターは配列(位置パラメーター)で渡すことができます。







ただし、要件が少し変更され、付随する情報をパラメーターのリストに追加する必要があるか、または除算操作の場合はゼロによる除算が発生したことを意味するフラグを追加する必要があるとします。 この場合、修正するには、インターフェイスとそのすべての実装だけでなく、使用されているすべての場所も「シェイクアップ」する必要があります。







JSON-RPCサービスがある場合、変更はサーバーだけでなくクライアントにも影響します。クライアントのインターフェースは何らかの形で調整する必要があります。 また、構造の場合は、構造にフィールドを追加するだけです。 さらに、クライアント部分とサーバー部分は個別に更新できます。







フレームワークのもう1つの特徴は、すべてのメソッドに非同期インターフェイスがあることです。 結果は直接ではなく、コールバック関数を通じて返されます。 また、不注意によるコピーエラーを避けるため、入力および出力オブジェクトはstd :: unique_ptr <>として記述されます。







計算機では、説明されている制限を考慮して、次のインターフェイスを取得します。







 struct icalc { virtual ~icalc() {} virtual void plus( request::plus::ptr req, response::plus::callback cb) = 0; virtual void minus( request::minus::ptr req, response::minus::callback cb) = 0; virtual void multiplies( request::multiplies::ptr req, response::multiplies::callback cb) = 0; virtual void divides( request::divides::ptr req, response::divides::callback cb) = 0; };
      
      





入力構造で定義した補助typedefを考えると、かなり見栄えがします。 しかし、そのようなインターフェースの実装は、比較的単純な例ではかなり膨大になる可能性があります。 入力要求がnullptrでないことを確認し、コールバック関数も確認する必要があります。 定義されていない場合、これは通知であることが明らかであり、この場合、呼び出しは無視する必要があります。 計算機の実装例に示すように、この機能はテンプレート化が簡単です。







calc1.hpp
 #pragma once #include "icalc.hpp" class calc1 : public icalc { public: virtual void plus( request::plus::ptr req, response::plus::callback cb) override; virtual void minus( request::minus::ptr req, response::minus::callback cb) override; virtual void multiplies( request::multiplies::ptr req, response::multiplies::callback cb) override; virtual void divides( request::divides::ptr req, response::divides::callback cb) override; private: template<typename Res, typename ReqPtr, typename Callback, typename F> void impl_( ReqPtr req, Callback cb, F f); };
      
      





calc1.cpp
 #include "calc1.hpp" #include <wjrpc/memory.hpp> template<typename Res, typename ReqPtr, typename Callback, typename F> void calc1::impl_( ReqPtr req, Callback cb, F f) { //   if ( cb == nullptr ) return; //   if ( req == nullptr ) return cb(nullptr); auto res = std::make_unique<Res>(); res->value = f(req->first,req->second); cb( std::move(res) ); } void calc1::plus( request::plus::ptr req, response::plus::callback cb) { this->impl_<response::plus>( std::move(req), cb, [](int f, int s) { return f+s; } ); } void calc1::minus( request::minus::ptr req, response::minus::callback cb) { this->impl_<response::minus>( std::move(req), cb, [](int f, int s) { return fs; }); } void calc1::multiplies( request::multiplies::ptr req, response::multiplies::callback cb) { this->impl_<response::multiplies>( std::move(req), cb, [](int f, int s) { return f*s; }); } void calc1::divides( request::divides::ptr req, response::divides::callback cb) { this->impl_<response::divides>( std::move(req), cb, [](int f, int s) { return s!=0 ? f/s : 0; }); }
      
      





この記事はまだ電卓の実装に関するものではないため、上記のコードをベストプラクティスと見なすべきではありません。 メソッド呼び出しの例:







 calc->plus( std::move(params), [](response::plus::ptr result) { … });
      
      





インターフェイスのトピックに戻りますが、サービスのコードを引き続き「削減」します。 結果を非同期にシリアル化するには、着信リクエストを「キャプチャ」する必要があります







例えば
 std::shared_ptr<wjrpc::incoming_holder> ph = std::make_shared<wjrpc::incoming_holder>( std::move(inholder) ); calc->plus( std::move(params), [ph, &res_list](response::plus::ptr result) { //    wjrpc::outgoing_result<response::plus> resp; resp.result = std::move(result); //    auto raw_id = ph->raw_id(); auto call_id = std::make_unique<wjrpc::data_type>( raw_id.first, raw_id.second ); resp.id = std::move(call_id); //   typedef wjrpc::outgoing_result_json<response::plus_json> result_json; typedef wjrpc::outgoing_result_json<response::plus_json> result_json; //   auto d = ph->detach(); d->clear(); result_json::serializer()( resp, std::back_inserter(d) ); res_list.push_back( std::string(d->begin(), d->end()) ); });
      
      





なぜなら incoming_holderは移動可能であるため、「キャプチャ」するには、std :: shared_ptrに移動します。 彼からバッファを取得する方法を示していますが、この場合はあまり意味がありません-とにかく、結果を行のリストに入れます。 参照によるres_listのキャプチャは、単なる例です。 要求は同期的に実行されることがわかっています。







エラーをシリアル化するためのテンプレート関数をすでに作成しました。回答についても同じことを行います。 ただし、このためには、結果の型に加えて、その値とJSONの説明を渡す必要があります。







ユニバーサルクエリレスポンスシリアライザー
 template<typename ResJ> void send_response(std::shared_ptr<wjrpc::incoming_holder> ph, typename ResJ::target::ptr result, std::string& out) { typedef ResJ result_json; typedef typename result_json::target result_type; wjrpc::outgoing_result<result_type> resp; resp.result = std::move(result); auto raw_id = ph->raw_id(); resp.id = std::make_unique<wjrpc::data_type>( raw_id.first, raw_id.second ); typedef wjrpc::outgoing_result_json<result_json> response_json; typename response_json::serializer()( resp, std::back_inserter( out ) ); }
      
      





ここでは、テンプレートパラメータを明示的に指定する必要があります。これは、応答構造のJSON記述であり、記述された構造のタイプを取得できます。 この関数を使用すると、各JSON-RPCメソッドのコードが大幅に簡素化されます。







plusメソッドの新しいバージョン
 if ( inholder.method() == "plus" ) { //   auto params = inholder.get_params<request::plus_json>(&e); if ( !e ) { std::shared_ptr<wjrpc::incoming_holder> ph = std::make_shared<wjrpc::incoming_holder>( std::move(inholder) ); calc->plus( std::move(params), std::bind( send_response<response::plus_json>, ph, std::placeholders::_1, std::ref(out)) ); } else { make_error<wjrpc::invalid_params>(std::move(inholder), out ); } } // else if ( inholder.method() == "minus" ) { ... } // else if ( inholder.method() == "multiplies" ) { .... } // else if ( inholder.method() == "divides" ) { .... } else { make_error<wjrpc::procedure_not_found>(std::move(inholder), out ); }
      
      





各メソッドについて、コードは最小限に削減されましたが、これでは十分ではありません。 パラメータの取得とエラーのチェックも同じタイプのコードです。







シリアル化とメソッド呼び出し
 template< typename JParams, typename JResult, void (icalc::*mem_ptr)( std::unique_ptr<typename JParams::target>, std::function< void(std::unique_ptr<typename JResult::target>) > ) > void invoke(wjrpc::incoming_holder inholder, std::shared_ptr<icalc> calc, std::string& out) { typedef JParams params_json; typedef JResult result_json; wjson::json_error e; auto params = inholder.get_params<params_json>(&e); if ( !e ) { std::shared_ptr<wjrpc::incoming_holder> ph = std::make_shared<wjrpc::incoming_holder>( std::move(inholder) ); (calc.get()->*mem_ptr)( std::move(params), std::bind( send_response<result_json>, ph, std::placeholders::_1, std::ref(out) ) ); } else { out = make_error<wjrpc::invalid_params>(); } }
      
      





これはwjrpcで実装されているものとほぼ同じです。 その結果、デモ例のコードは最小限に削減されます(ここで、すべてのメソッドの実装を既に提供できます)。







「手動処理」を使用した例の最終バージョン
 int main() { std::vector<std::string> req_list = { "{\"method\":\"plus\", \"params\":{ \"first\":2, \"second\":3 }, \"id\" :1 }", "{\"method\":\"minus\", \"params\":{ \"first\":5, \"second\":10 }, \"id\" :1 }", "{\"method\":\"multiplies\", \"params\":{ \"first\":2, \"second\":2 }, \"id\" :1 }", "{\"method\":\"divides\", \"params\":{ \"first\":9, \"second\":3 }, \"id\" :1 }" }; std::vector<std::string> res_list; auto calc = std::make_shared<calc1>(); for ( auto& sreq : req_list ) { res_list.push_back( std::string() ); std::string& out = res_list.back(); wjrpc::incoming_holder inholder( sreq ); wjson::json_error e; inholder.parse(&e); if ( e ) { out = make_error<wjrpc::parse_error>(); } else if ( inholder.is_request() ) { //       if ( inholder.method() == "plus" ) { invoke<request::plus_json, response::plus_json, &icalc::plus>( std::move(inholder), calc, out ); } else if ( inholder.method() == "minus" ) { invoke<request::minus_json, response::minus_json, &icalc::minus>( std::move(inholder), calc, out ); } else if ( inholder.method() == "multiplies" ) { invoke<request::multiplies_json, response::multiplies_json, &icalc::multiplies>( std::move(inholder), calc, out ); } else if ( inholder.method() == "divides" ) { invoke<request::divides_json, response::divides_json, &icalc::divides>( std::move(inholder), calc, out ); } else { out = make_error<wjrpc::procedure_not_found>(); } } else { out = make_error<wjrpc::invalid_request>(); } } for ( size_t i =0; i != res_list.size(); ++i) { std::cout << req_list[i] << std::endl; std::cout << res_list[i] << std::endl; std::cout << std::endl; } }
      
      





実行時のコード量を減らしたいというSuchな欲求は、いくつかの理由によるものです。 かなり複雑なデザインを使用して不要なif



を削除することで、コードの量を減らすだけでなく、プログラマーがnagovoditに満足する可能性のある場所も削除します。 また、コピーペーストなどのプログラマー、特にシリアル化に関連する面白くないコード、プロジェクト全体への拡散、または他のプロジェクトへの持ち込みさえも可能です。 怠azineは進歩のエンジンであり、人々が仕事を減らすことを可能にする何かを発明させるときです。 しかし、物事が後まで延期される場合はそうではありません。 些細なチェックのように思えますが、言葉で言えば、これはまだプロトタイプであり、数行のコードを書くのは延期され、忘れられて同時にプロジェクト全体にコピーされ、他のプログラマーによって取り上げられます。







実際、wjrpcの検討はまだ始まっていません。wjsonを使用してリクエストがどのように記述されるかを示し、テストケースのインターフェイスとアプリケーションロジックを記述し、JSON-RPCを手動で実装する方法を検討しました。完全なwjrpcの例を次に示します。







 #include "calc/calc1.hpp" #include "calc/api/plus_json.hpp" #include "calc/api/minus_json.hpp" #include "calc/api/multiplies_json.hpp" #include "calc/api/divides_json.hpp" #include <wjrpc/handler.hpp> #include <wjrpc/method.hpp> #include <iostream> #include <functional> JSONRPC_TAG(plus) JSONRPC_TAG(minus) JSONRPC_TAG(multiplies) JSONRPC_TAG(divides) struct method_list: wjrpc::method_list < wjrpc::target<icalc>, wjrpc::invoke_method<_plus_, request::plus_json, response::plus_json, icalc, &icalc::plus>, wjrpc::invoke_method<_minus_, request::minus_json, response::minus_json, icalc, &icalc::minus>, wjrpc::invoke_method<_multiplies_, request::multiplies_json, response::multiplies_json, icalc, &icalc::multiplies>, wjrpc::invoke_method<_divides_, request::divides_json, response::divides_json, icalc, &icalc::divides> >{}; class handler: public wjrpc::handler<method_list> {}; int main() { std::vector<std::string> req_list = { "{\"method\":\"plus\", \"params\":{ \"first\":2, \"second\":3 }, \"id\" :1 }", "{\"method\":\"minus\", \"params\":{ \"first\":5, \"second\":10 }, \"id\" :1 }", "{\"method\":\"multiplies\", \"params\":{ \"first\":2, \"second\":2 }, \"id\" :1 }", "{\"method\":\"divides\", \"params\":{ \"first\":9, \"second\":3 }, \"id\" :1 }" }; std::vector<std::string> res_list; auto calc = std::make_shared<calc1>(); handler h; handler::options_type opt; opt.target = calc; h.start(opt, 1); for ( auto& sreq : req_list ) { h.perform( sreq, [&res_list](std::string out) { res_list.push_back(out);} ); } for ( size_t i =0; i != res_list.size(); ++i) { std::cout << req_list[i] << std::endl; std::cout << res_list[i] << std::endl; std::cout << std::endl; } }
      
      





JSONRPC_TAGを使用して、wjsonのJSON NAMEと同様に、テンプレートパラメーターとして渡すメソッド名を指定します。唯一の違いは、プレフィックスnの代わりにアンダースコアで囲まれたエンティティの名前です







次に、wjrpc :: method_listおよびwjrpc :: invoke_methodを使用して、使用可能なすべてのメソッドについて説明します。メソッドのリストはwjrpc ::ハンドラーhandlerに渡されます。リストは、メソッドとともに、ハンドラーが動作するオブジェクトのインターフェイスタイプを記述しました。







perform_io, wjrpc::data_ptr.







 typedef std::vector<char> data_type; typedef std::unique_ptr<data_type> data_ptr; typedef std::function< void(data_ptr) > output_handler_t; void perform_io(data_ptr d, output_handler_t handler) { … } void perform(std::string str, std::function<void(std::string)> handler) { auto d = std::make_unique<data_type>( str.begin(), str.end() ); this->perform_io( std::move(d), [handler](data_ptr d) { handler( std::string(d->begin(), d->end()) ); }); }
      
      





, . , , ,







,
 struct plus_handler { template<typename T> void operator()(T& t, request::plus::ptr req) { //   t.target()->plus( std::move(req), nullptr ); } template<typename T, typename Handler> void operator()(T& t, request::plus::ptr req, Handler handler) { //   t.target()->plus( std::move(req), [handler](response::plus::ptr res) { if ( res != nullptr ) handler( std::move(res), nullptr ); else handler( nullptr, std::make_unique<wjrpc::service_unavailable>() ); }); } };
      
      





 struct plus_handler { template<typename T> void operator()(T&, request::plus::ptr req) { } template<typename T, typename Handler> void operator()(T&, request::plus::ptr req, Handler handler) { if (req==nullptr) { handler( nullptr, std::make_unique<wjrpc::invalid_params>() ); return; } auto res = std::make_unique<response::plus>(); res->value = req->first + req->second; handler( std::move(res), nullptr ); } };
      
      





handler , , . t — JSON-RPC- ( self python). :







 struct method_list: wjrpc::method_list < wjrpc::target<icalc>, wjrpc::method< wjrpc::name<_plus_>, wjrpc::invoke<request::plus_json, response::plus_json, plus_handler> >, wjrpc::invoke_method<_minus_, request::minus_json, response::minus_json, icalc, &icalc::minus>, wjrpc::invoke_method<_multiplies_, request::multiplies_json, response::multiplies_json, icalc, &icalc::multiplies>, wjrpc::invoke_method<_divides_, request::divides_json, response::divides_json, icalc, &icalc::divides> >{};
      
      





, invoke_method<> wjrpc::method<>, :







mem_fun_handler
 template< typename Params, typename Result, typename I, void (I::*mem_ptr)( std::unique_ptr<Params>, std::function< void(std::unique_ptr<Result>) > ) > struct mem_fun_handler { typedef std::unique_ptr<Params> request_ptr; typedef std::unique_ptr<Result> responce_ptr; typedef std::unique_ptr< error> json_error_ptr; typedef std::function< void(responce_ptr, json_error_ptr) > jsonrpc_callback; template<typename T> void operator()(T& t, request_ptr req) const; template<typename T> void operator()(T& t, request_ptr req, jsonrpc_callback cb) const; };
      
      





, JSON-RPC , , .







, ( ), , ( ) . . , - , . . , ? - . , , . , , . , , , .







- “” . wjrpc::invoke_method. , JSON-RPC. “” , .







, wjrpc::invoke_method.







 struct icalc { virtual ~icalc() {} virtual request::plus::ptr plus( request::plus::ptr req) = 0; virtual request::minus::ptr minus( request::minus::ptr req) = 0; virtual request::multiplies::ptr multiplies( request::multiplies::ptr req) = 0; virtual request::divides::ptr divides( request::divides::ptr req) = 0; };
      
      





wjrpc::handler<> . callback, , , , , .







 calc->plus( std::move(req), [this](response::plus::ptr) {} );
      
      





 std::shared_ptr<calc> pthis = this->shared_from_this(); calc->plus( std::move(req), [pthis](response::plus::ptr) {} );
      
      





, .. .







 std::weak_ptr<calc> wthis = this->shared_from_this(); calc->plus( std::move(req), [wthis](response::plus::ptr) { if ( auto pthis = wthis.lock() ) { /* … */ } } );
      
      





, (, ). , , callback- .







 std::weak_ptr<int> w = this->_p; /* _p = std::shared_ptr<int>(1);*/ std::weak_ptr<calc> wthis = this->shared_from_this(); calc->plus( std::move(req), [wthis, w](response::plus::ptr) { if ( auto pthis = wthis.lock() ) { if ( nullptr == w.lock() ) return; /* … */ } } );
      
      





( wjrpc)







callback-
 template<typename H> class owner_handler { public: typedef std::weak_ptr<int> weak_type; owner_handler() = default; owner_handler(H&& h, weak_type alive) : _handler( std::forward<H>(h) ) , _alive(alive) { } template <class... Args> auto operator()(Args&&... args) -> typename std::result_of< H(Args&&...) >::type { if ( auto p = _alive.lock() ) { return _handler( std::forward<Args>(args)... ); } return typename std::result_of< H(Args&&...) >::type(); } private: H _handler; weak_type _alive; };
      
      





( )
 class owner { public: typedef std::shared_ptr<int> alive_type; typedef std::weak_ptr<int> weak_type; owner() : _alive( std::make_shared<int>(1) ) { } owner(const owner& ) = delete; owner& operator = (const owner& ) = delete; owner(owner&& ) = default; owner& operator = (owner&& ) = default; alive_type& alive() { return _alive; } const alive_type& alive() const { return _alive; } void reset() { _alive = std::make_shared<int>(*_alive + 1); } template<typename Handler> owner_handler<typename std::remove_reference<Handler>::type> wrap(Handler&& h) const { return owner_handler< typename std::remove_reference<Handler>::type >( std::forward<Handler>(h), std::weak_ptr<int>(_alive) ); } private: mutable alive_type _alive; };
      
      





使用例
 // owner -   std::weak_ptr<calc> wthis = this->shared_from_this(); calc->plus( std::move(req), this->wrap([wthis](response::plus::ptr) { if ( auto pthis = wthis.lock() ) { /* … */ } })); // … //    owner::reset(); //  callback-   
      
      





. . , . , , . , .







JSON-RPC Engine



wjrpc::engine – , , jsonrpc-, . , , , wjrpc::engine. . callback-, , . wjrpc::engine, , , , .







, icalc. , plus, .







calc/calc_p.hpp
 #pragma once #include "icalc.hpp" class calc_p : public icalc { public: void initialize(std::shared_ptr<icalc>); virtual void plus( request::plus::ptr req, response::plus::callback cb) override; virtual void minus( request::minus::ptr req, response::minus::callback cb) override; virtual void multiplies( request::multiplies::ptr req, response::multiplies::callback cb) override; virtual void divides( request::divides::ptr req, response::divides::callback cb) override; private: template<typename ReqPtr, typename Callback> bool check_( ReqPtr& req, Callback& cb); std::shared_ptr<icalc> _next; };
      
      





calc/calc_p.cpp
 #include "calc_p.hpp" #include <memory> void calc_p::initialize(std::shared_ptr<icalc> next) { _next = next; } void calc_p::plus( request::plus::ptr req, response::plus::callback cb) { if ( !this->check_(req, cb)) return; req->first++; req->second++; _next->plus(std::move(req), [cb](response::plus::ptr res) { res->value++; cb(std::move(res) ); }); } void calc_p::minus( request::minus::ptr req, response::minus::callback cb) { if ( this->check_(req, cb)) _next->minus(std::move(req), std::move(cb) ); } void calc_p::multiplies( request::multiplies::ptr req, response::multiplies::callback cb) { if ( this->check_(req, cb)) _next->multiplies(std::move(req), std::move(cb) ); } void calc_p::divides( request::divides::ptr req, response::divides::callback cb) { if ( this->check_(req, cb)) _next->divides(std::move(req), std::move(cb) ); } template<typename ReqPtr, typename Callback> bool calc_p::check_( ReqPtr& req, Callback& cb) { if ( cb==nullptr ) return false; if ( req != nullptr ) return true; cb(nullptr); return false; }
      
      





, icalc, , plus, “”. .







, : , , . , , .







, , , JSON-RPC .







 JSONRPC_TAG(plus) JSONRPC_TAG(minus) JSONRPC_TAG(multiplies) JSONRPC_TAG(divides) struct method_list: wjrpc::method_list < wjrpc::call_method<_plus_, request::plus_json, response::plus_json>, wjrpc::call_method<_minus_, request::minus_json, response::minus_json>, wjrpc::call_method<_multiplies_, request::multiplies_json, response::multiplies_json>, wjrpc::call_method<_divides_, request::divides_json, response::divides_json, icalc> > {};
      
      





, , , JSON- . — icalc:







 class handler : public ::wjrpc::handler<method_list> , public icalc { public: virtual void plus( request::plus::ptr req, response::plus::callback cb) override { this->template call<_plus_>( std::move(req), cb, nullptr ); } virtual void minus( request::minus::ptr req, response::minus::callback cb) override { this->template call<_minus_>( std::move(req), cb, nullptr ); } virtual void multiplies( request::multiplies::ptr req, response::multiplies::callback cb) override { this->template call<_multiplies_>( std::move(req), cb, nullptr ); } virtual void divides( request::divides::ptr req, response::divides::callback cb) override { this->template call<_divides_>( std::move(req), cb, nullptr ); } };
      
      





— call<> , , , . , . , callback nullptr.







JSON-RPC , callback nullptr, , , . JSON-RPC . , .







, , :











, , , , . , , , . , . — , . , , , .







. callback, callback, , . JSON-RPC , id JSON-RPC . callback , callback, .







calc/calc_p.cpp
 #include "calc/calc1.hpp" #include "calc/calc_p.hpp" #include "calc/api/plus_json.hpp" #include "calc/api/minus_json.hpp" #include "calc/api/multiplies_json.hpp" #include "calc/api/divides_json.hpp" #include <wjrpc/engine.hpp> #include <wjrpc/handler.hpp> #include <wjrpc/method.hpp> #include <iostream> #include <functional> namespace service { JSONRPC_TAG(plus) JSONRPC_TAG(minus) JSONRPC_TAG(multiplies) JSONRPC_TAG(divides) struct method_list: wjrpc::method_list < wjrpc::target<icalc>, wjrpc::invoke_method<_plus_, request::plus_json, response::plus_json, icalc, &icalc::plus>, wjrpc::invoke_method<_minus_, request::minus_json, response::minus_json, icalc, &icalc::minus>, wjrpc::invoke_method<_multiplies_, request::multiplies_json, response::multiplies_json, icalc, &icalc::multiplies>, wjrpc::invoke_method<_divides_, request::divides_json, response::divides_json, icalc, &icalc::divides> >{}; class handler: public ::wjrpc::handler<method_list> {}; typedef wjrpc::engine<handler> engine_type; } namespace gateway { JSONRPC_TAG(plus) JSONRPC_TAG(minus) JSONRPC_TAG(multiplies) JSONRPC_TAG(divides) struct method_list: wjrpc::method_list < wjrpc::call_method<_plus_, request::plus_json, response::plus_json>, wjrpc::call_method<_minus_, request::minus_json, response::minus_json>, wjrpc::call_method<_multiplies_, request::multiplies_json, response::multiplies_json>, wjrpc::call_method<_divides_, request::divides_json, response::divides_json> > {}; class handler : public ::wjrpc::handler<method_list> , public icalc { public: virtual void plus( request::plus::ptr req, response::plus::callback cb) override { this->template call<_plus_>( std::move(req), cb, nullptr ); } virtual void minus( request::minus::ptr req, response::minus::callback cb) override { this->template call<_minus_>( std::move(req), cb, nullptr ); } virtual void multiplies( request::multiplies::ptr req, response::multiplies::callback cb) override { this->template call<_multiplies_>( std::move(req), cb, nullptr ); } virtual void divides( request::divides::ptr req, response::divides::callback cb) override { this->template call<_divides_>( std::move(req), cb, nullptr ); } }; typedef wjrpc::engine<handler> engine_type; } int main() { //  N1 auto prx1 = std::make_shared<calc_p>(); //  auto gtw = std::make_shared<gateway::engine_type>(); //  auto srv = std::make_shared<service::engine_type>(); //  N2 auto prx2 = std::make_shared<calc_p>(); //  auto clc = std::make_shared<calc1>(); //      prx2->initialize(clc); //         service::engine_type::options_type srv_opt; srv_opt.target = prx2; srv->start(srv_opt, 11); //   gateway::engine_type::options_type cli_opt; gtw->start(cli_opt, 22); //         gtw->reg_io(33, [srv]( wjrpc::data_ptr d, wjrpc::io_id_t /*io_id*/, wjrpc::output_handler_t handler) { std::cout << " REQUEST: " << std::string( d->begin(), d->end() ) << std::endl; srv->perform_io(std::move(d), 44, [handler](wjrpc::data_ptr d) { //     JSON-RPC  std::cout << " RESPONSE: " << std::string( d->begin(), d->end() ) << std::endl; handler(std::move(d) ); }); }); //       ID auto gtwh = gtw->find(33); //       prx1->initialize(gtwh); //  plus   (prx1->gtw->srv->prx2->clc) auto plus = std::make_unique<request::plus>(); plus->first = 1; plus->second = 2; prx1->plus( std::move(plus), [](response::plus::ptr res) { std::cout << "1+2=" << res->value << std::endl;; }); //  plus   (gtw->srv->prx2->clc) auto minus = std::make_unique<request::minus>(); minus->first = 4; minus->second = 3; gtwh->minus( std::move(minus), [](response::minus::ptr res) { std::cout << "4-3=" << res->value << std::endl;; }); }
      
      





結果:







 REQUEST: {"jsonrpc":"2.0","method":"plus","params":{"first":2,"second":3},"id":1} RESPONSE: {"jsonrpc":"2.0","result":{"value":8},"id":1} 1+2=9 REQUEST: {"jsonrpc":"2.0","method":"minus","params":{"first":4,"second":3},"id":2} RESPONSE: {"jsonrpc":"2.0","result":{"value":1},"id":2} 4-3=1
      
      





, JSON-RPC 2 3 1 2. , , 8. , 9. , , , munus , .







— , , , , , , :







create_id
 inline wjrpc::io_id_t create_id() { static std::atomic<wjrpc::io_id_t> counter( (wjrpc::io_id_t(1)) ); return counter.fetch_add(1); }
      
      





. , :







  gtw->reg_io(33, []( wjrpc::data_ptr, wjrpc::io_id_t, wjrpc::output_handler_t)
      
      





JSON-RPC , . - , , , . . . 44 — , . service::engine_type, ( , wjrpc ), .







(::pipe), , , , , - , , , . examples , , , .







wjson, , wjrpc::incoming_holder JSON-RPC . , wjrpc::handler , run-time .







wjrpc . faslib wjson . :







 git clone https://github.com/migashko/faslib.git git clone https://github.com/mambaru/wjson.git git clone https://github.com/mambaru/wjrpc.git #      wjrpc cd faslib mkdir build cd build cmake .. #     cd ../../wjrpc mkdir build cd build cmake -DWJRPC_BUILD_ALL=ON .. make
      
      






All Articles