diff --git a/include/fc/api.hpp b/include/fc/api.hpp index e84f50b..06414b1 100644 --- a/include/fc/api.hpp +++ b/include/fc/api.hpp @@ -38,8 +38,33 @@ namespace fc { OtherType& _source; }; + template + class api; + + class api_connection; + + typedef uint32_t api_id_type; + + class api_base + { + public: + api_base() {} + virtual ~api_base() {} + + virtual uint64_t get_handle()const = 0; + + virtual api_id_type register_api( api_connection& conn )const = 0; + + // defined in api_connection.hpp + template< typename T > + api as(); + }; + typedef std::shared_ptr< api_base > api_ptr; + + class api_connection; + template - class api { + class api : public api_base { public: typedef vtable vtable_type; @@ -58,10 +83,12 @@ namespace fc { } api( const api& cpy ):_vtable(cpy._vtable),_data(cpy._data) {} + virtual ~api() {} friend bool operator == ( const api& a, const api& b ) { return a._data == b._data && a._vtable == b._vtable; } friend bool operator != ( const api& a, const api& b ) { return !(a._data == b._data && a._vtable == b._vtable); } - uint64_t get_handle()const { return uint64_t(_data.get()); } + virtual uint64_t get_handle()const override { return uint64_t(_data.get()); } + virtual api_id_type register_api( api_connection& conn )const override; // defined in api_connection.hpp vtable_type& operator*()const { FC_ASSERT( _vtable ); return *_vtable; } vtable_type* operator->()const { FC_ASSERT( _vtable ); return _vtable.get(); } diff --git a/include/fc/rpc/api_connection.hpp b/include/fc/rpc/api_connection.hpp index c06d2d0..44fc7c3 100644 --- a/include/fc/rpc/api_connection.hpp +++ b/include/fc/rpc/api_connection.hpp @@ -12,8 +12,6 @@ namespace fc { class api_connection; - - typedef uint32_t api_id_type; namespace detail { template @@ -68,6 +66,33 @@ namespace fc { }; } + /** + * If api is returned from a remote method, the API is eagerly bound to api of + * the correct type in api_visitor::from_variant(). This binding [1] needs a reference + * to the api_connection, which is made available to from_variant() as a parameter. + * + * However, in the case of a remote method which returns api_base which can subsequently + * be cast by the caller with as, we need to keep track of the connection because + * the binding is done later (when the client code actually calls as). + * + * [1] The binding actually happens in get_remote_api(). + */ + class any_api : public api_base + { + public: + any_api( api_id_type api_id, const std::shared_ptr& con ) + : _api_id(api_id), _api_connection(con) {} + + virtual uint64_t get_handle()const override + { return _api_id; } + + virtual api_id_type register_api( api_connection& conn )const override + { FC_ASSERT( false ); return api_id_type(); } + + api_id_type _api_id; + std::weak_ptr _api_connection; + }; + } // namespace detail class generic_api @@ -151,6 +176,9 @@ namespace fc { template std::function to_generic( const std::function>(Args...)>& f )const; + template + std::function to_generic( const std::function& f )const; + template std::function to_generic( const std::function& f )const; @@ -267,6 +295,15 @@ namespace fc { return con->get_remote_api( v.as_uint64() ); } + static fc::api_ptr from_variant( + const variant& v, + fc::api_ptr* /* used for template deduction */, + const std::shared_ptr& con + ) + { + return fc::api_ptr( new detail::any_api( v.as_uint64(), con ) ); + } + template static fc::variant convert_callbacks( const std::shared_ptr&, const T& v ) { @@ -370,6 +407,24 @@ namespace fc { return variant(); }; } + + template + std::function generic_api::api_visitor::to_generic( + const std::function& f )const + { + auto api_con = _api_con; + auto gapi = &api; + return [=]( const variants& args ) -> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + auto api_result = gapi->call_generic( f, args.begin(), args.end() ); + if( !api_result ) + return variant(); + return api_result->register_api( *con ); + }; + } + template std::function generic_api::api_visitor::to_generic( const std::function& f )const { @@ -389,6 +444,40 @@ namespace fc { }; } + /** + * It is slightly unclean tight coupling to have this method in the api class. + * It breaks encapsulation by requiring an api class method to have a pointer + * to an api_connection. The reason this is necessary is we have a goal of being + * able to call register_api() on an api through its base class api_base. But + * register_api() must know the template parameters! + * + * The only reasonable way to achieve the goal is to implement register_api() + * as a method in api (which obviously knows the template parameter T), + * then make the implementation accessible through the base class (by making + * it a pure virtual method in the base class which is overridden by the subclass's + * implementation). + */ + template< typename Interface, typename Transform > + api_id_type api< Interface, Transform >::register_api( api_connection& conn )const + { + return conn.register_api( *this ); + } + + template< typename T > + api api_base::as() + { + // TODO: this method should probably be const (if it is not too hard) + api* maybe_requested_type = dynamic_cast< api* >(this); + if( maybe_requested_type != nullptr ) + return *maybe_requested_type; + + detail::any_api* maybe_any = dynamic_cast< detail::any_api* >(this); + FC_ASSERT( maybe_any != nullptr ); + std::shared_ptr< api_connection > api_conn = maybe_any->_api_connection.lock(); + FC_ASSERT( api_conn ); + return api_conn->get_remote_api( maybe_any->_api_id ); + } + namespace detail { template template diff --git a/src/rpc/http_api.cpp b/src/rpc/http_api.cpp index c842369..c9a7786 100644 --- a/src/rpc/http_api.cpp +++ b/src/rpc/http_api.cpp @@ -11,9 +11,22 @@ http_api_connection::http_api_connection() { _rpc_state.add_method( "call", [this]( const variants& args ) -> variant { + // TODO: This logic is duplicated between http_api_connection and websocket_api_connection + // it should be consolidated into one place instead of copy-pasted FC_ASSERT( args.size() == 3 && args[2].is_array() ); + api_id_type api_id; + if( args[0].is_string() ) + { + variants subargs; + subargs.push_back( args[0] ); + variant subresult = this->receive_call( 1, "get_api_by_name", subargs ); + api_id = subresult.as_uint64(); + } + else + api_id = args[0].as_uint64(); + return this->receive_call( - args[0].as_uint64(), + api_id, args[1].as_string(), args[2].get_array() ); } ); diff --git a/src/rpc/websocket_api.cpp b/src/rpc/websocket_api.cpp index df09a68..60ef706 100644 --- a/src/rpc/websocket_api.cpp +++ b/src/rpc/websocket_api.cpp @@ -13,8 +13,19 @@ websocket_api_connection::websocket_api_connection( fc::http::websocket_connecti _rpc_state.add_method( "call", [this]( const variants& args ) -> variant { FC_ASSERT( args.size() == 3 && args[2].is_array() ); + api_id_type api_id; + if( args[0].is_string() ) + { + variants subargs; + subargs.push_back( args[0] ); + variant subresult = this->receive_call( 1, "get_api_by_name", subargs ); + api_id = subresult.as_uint64(); + } + else + api_id = args[0].as_uint64(); + return this->receive_call( - args[0].as_uint64(), + api_id, args[1].as_string(), args[2].get_array() ); } );