/* * Copyright (c) 2017-2023 zhllxt * * author : zhllxt * email : 37792738@qq.com * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef __ASIO2_RPC_PROTOCOL_HPP__ #define __ASIO2_RPC_PROTOCOL_HPP__ #if defined(_MSC_VER) && (_MSC_VER >= 1200) #pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include <string> #include <string_view> #include <asio2/base/detail/util.hpp> #include <asio2/rpc/detail/rpc_serialization.hpp> namespace asio2::detail { struct use_tcp {}; struct use_websocket {}; /* * request : message type + request id + function name + parameters value... * response : message type + request id + function name + error code + result value * * message type : q - request, p - response * * if result type is void, then result type will wrapped to std::int8_t */ static constexpr char rpc_type_req = 'q'; static constexpr char rpc_type_rep = 'p'; class rpc_header { public: using id_type = std::uint64_t; rpc_header() noexcept {} rpc_header(char type, id_type id, std::string_view name) : type_(type), id_(id), name_(name) {} ~rpc_header() = default; rpc_header(const rpc_header& r) : type_(r.type_), id_(r.id_), name_(r.name_) {} rpc_header(rpc_header&& r) noexcept : type_(r.type_), id_(r.id_), name_(std::move(r.name_)) {} inline rpc_header& operator=(const rpc_header& r) { type_ = r.type_; id_ = r.id_; name_ = r.name_; return (*this); } inline rpc_header& operator=(rpc_header&& r) noexcept { type_ = r.type_; id_ = r.id_; name_ = std::move(r.name_); return (*this); } //template <class Archive> //inline void serialize(Archive & ar) //{ // ar(type_, id_, name_); //} template <class Archive> void save(Archive & ar) const { ar(type_, id_, name_); } template <class Archive> void load(Archive & ar) { ar(type_, id_, name_); } inline char type() const noexcept { return this->type_; } inline id_type id () const noexcept { return this->id_; } inline const std::string& name() const noexcept { return this->name_; } inline char get_type() const noexcept { return this->type_; } inline id_type get_id () const noexcept { return this->id_; } inline const std::string& get_name() const noexcept { return this->name_; } inline bool is_request () const noexcept { return this->type_ == rpc_type_req; } inline bool is_response() const noexcept { return this->type_ == rpc_type_rep; } inline rpc_header& type(char type ) noexcept { this->type_ = type; return (*this); } inline rpc_header& id (id_type id ) noexcept { this->id_ = id ; return (*this); } inline rpc_header& name(std::string_view name) { this->name_ = name; return (*this); } inline rpc_header& set_type(char type ) noexcept { this->type_ = type; return (*this); } inline rpc_header& set_id (id_type id ) noexcept { this->id_ = id ; return (*this); } inline rpc_header& set_name(std::string_view name) { this->name_ = name; return (*this); } protected: char type_; id_type id_ = 0; std::string name_; }; template<class ...Args> class rpc_request : public rpc_header { protected: template<class T> struct value_t { using type = T; }; template<class... Ts> struct value_t<std::basic_string_view<Ts...>> { using type = typename std::basic_string_view<Ts...>::value_type; }; template<class T> struct result_t { // if the parameters of rpc calling is raw pointer like char* , must convert it to std::string // if the parameters of rpc calling is std::string_view , must convert it to std::string // if the parameters of rpc calling is reference like std::string& , must remove it's // reference to std::string using ncvr_type = std::remove_cv_t<std::remove_reference_t<T>>; using char_type = std::remove_cv_t<std::remove_reference_t< std::remove_all_extents_t<std::remove_pointer_t<ncvr_type>>>>; using type = std::conditional_t< (std::is_pointer_v<ncvr_type> || std::is_array_v<ncvr_type>) && ( std::is_same_v<char_type, std::string::value_type> || std::is_same_v<char_type, std::wstring::value_type> || std::is_same_v<char_type, std::u16string::value_type> || std::is_same_v<char_type, std::u32string::value_type>) , std::basic_string<char_type> , std::conditional_t<is_template_instance_of_v<std::basic_string_view, ncvr_type> , std::basic_string<typename value_t<ncvr_type>::type> , ncvr_type>>; }; public: rpc_request() noexcept : rpc_header() { this->type_ = rpc_type_req; } // can't use tp_(std::forward_as_tuple(std::forward<Args>(args)...)) // if use tp_(std::forward_as_tuple(std::forward<Args>(args)...)), // when the args is nlohmann::json and under gcc 9.4.0, the json::object // maybe changed to json::array, like this: // {"name":"hello","age":10} will changed to [{"name":"hello","age":10}] // i don't why? rpc_request(std::string_view name, Args&&... args) : rpc_header(rpc_type_req, 0, name), tp_(std::forward<Args>(args)...) {} rpc_request(id_type id, std::string_view name, Args&&... args) : rpc_header(rpc_type_req, id, name), tp_(std::forward<Args>(args)...) {} ~rpc_request() = default; rpc_request(const rpc_request& r) : rpc_header(r), tp_(r.tp_) {} rpc_request(rpc_request&& r) noexcept : rpc_header(std::move(r)), tp_(std::move(r.tp_)) {} inline rpc_request& operator=(const rpc_request& r) { static_cast<rpc_header&>(*this) = r; tp_ = r.tp_; return (*this); } inline rpc_request& operator=(rpc_request&& r) noexcept { static_cast<rpc_header&>(*this) = std::move(r); tp_ = std::move(r.tp_); return (*this); } //template <class Archive> //void serialize(Archive & ar) //{ // ar(cereal::base_class<rpc_header>(this)); // detail::for_each_tuple(tp_, [&ar](auto& elem) mutable // { // ar(elem); // }); //} template <class Archive> void save(Archive & ar) const { ar(cereal::base_class<rpc_header>(this)); detail::for_each_tuple(tp_, [&ar](const auto& elem) mutable { ar << elem; }); } template <class Archive> void load(Archive & ar) { ar(cereal::base_class<rpc_header>(this)); detail::for_each_tuple(tp_, [&ar](auto& elem) mutable { ar >> elem; }); } protected: std::tuple<typename result_t<Args>::type...> tp_; }; template<class T> class rpc_response : public rpc_header { public: rpc_response() noexcept : rpc_header() { this->type_ = rpc_type_rep; } rpc_response(id_type id, std::string_view name) : rpc_header(rpc_type_rep, id, name) {} rpc_response(id_type id, std::string_view name, const error_code& ec, T&& ret) : rpc_header(rpc_type_rep, id, name), ec_(ec), ret_(std::forward<T>(ret)) {} ~rpc_response() = default; rpc_response(const rpc_response& r) : rpc_header(r), ec_(r.ec_), ret_(r.ret_) {} rpc_response(rpc_response&& r) noexcept : rpc_header(std::move(r)), ec_(std::move(r.ec_)), ret_(std::move(r.ret_)) {} inline rpc_response& operator=(const rpc_response& r) { static_cast<rpc_header&>(*this) = r; ec_ = r.ec_; ret_ = r.ret_; return (*this); } inline rpc_response& operator=(rpc_response&& r) noexcept { static_cast<rpc_header&>(*this) = std::move(r); ec_ = std::move(r.ec_); ret_ = std::move(r.ret_); return (*this); } //template <class Archive> //void serialize(Archive & ar) //{ // ar(cereal::base_class<rpc_header>(this)); // ar(ec_.value()); // ar(ret_); //} template <class Archive> void save(Archive & ar) const { ar << cereal::base_class<rpc_header>(this); ar << ec_.value(); ar << ret_; } template <class Archive> void load(Archive & ar) { ar >> cereal::base_class<rpc_header>(this); ar >> ec_.value(); ar >> ret_; } protected: error_code ec_; T ret_; }; } #endif // !__ASIO2_RPC_PROTOCOL_HPP__