Updates from BitShares FC #22
9 changed files with 289 additions and 257 deletions
|
|
@ -30,7 +30,6 @@ script:
|
||||||
- 'which build-wrapper-linux-x86-64 && build-wrapper-linux-x86-64 --out-dir bw-output make -j 2 || make -j 2'
|
- 'which build-wrapper-linux-x86-64 && build-wrapper-linux-x86-64 --out-dir bw-output make -j 2 || make -j 2'
|
||||||
- set -o pipefail
|
- set -o pipefail
|
||||||
- tests/run-parallel-tests.sh tests/all_tests
|
- tests/run-parallel-tests.sh tests/all_tests
|
||||||
- "tests/api 2>&1 | grep -vE 'callback result 9|remote_calc->add. 4, 5 .: 9|set callback|] \\.$'"
|
|
||||||
- tests/hmac_test 2>&1 | cat
|
- tests/hmac_test 2>&1 | cat
|
||||||
- tests/ecc_test README.md 2>&1 | cat
|
- tests/ecc_test README.md 2>&1 | cat
|
||||||
- 'find CMakeFiles/fc.dir -type d | while read d; do gcov -o "$d" "${d/CMakeFiles*.dir/./}"/*.cpp; done >/dev/null'
|
- 'find CMakeFiles/fc.dir -type d | while read d; do gcov -o "$d" "${d/CMakeFiles*.dir/./}"/*.cpp; done >/dev/null'
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,91 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace fc {
|
namespace fc {
|
||||||
struct identity_member {
|
namespace detail {
|
||||||
|
/// This metafunction determines whether its template argument is an instantiation of fc::optional
|
||||||
|
template<typename T> struct is_optional : public std::false_type {};
|
||||||
|
template<typename T> struct is_optional<fc::optional<T>> : public std::true_type {};
|
||||||
|
/// This metafunction determines whether all of its template arguments are instantiations of fc::optional
|
||||||
|
template<typename... Ts> struct all_optionals;
|
||||||
|
template<> struct all_optionals<> : public std::true_type {};
|
||||||
|
template<typename T, typename... Ts> struct all_optionals<T, Ts...> : public std::false_type {};
|
||||||
|
template<typename T, typename... Ts> struct all_optionals<fc::optional<T>, Ts...> : public all_optionals<Ts...> {};
|
||||||
|
|
||||||
|
/// A wrapper of std::function allowing callers to omit the last several arguments if those arguments are
|
||||||
|
/// fc::optional types. i.e. given a function taking (int, double, bool, fc::optional<string>, fc::optional<char>),
|
||||||
|
/// whereas normally the last two arguments must be provided, this template allows them to be omitted.
|
||||||
|
/// Note that this only applies to trailing optional arguments, i.e. given a callable taking
|
||||||
|
/// (fc::optional<int>, int, fc::optional<int>), only the last argument can be omitted.
|
||||||
|
///
|
||||||
|
/// A discussion of how exactly this works is available here:
|
||||||
|
/// https://github.com/bitshares/bitshares-fc/pull/126#issuecomment-490566060
|
||||||
|
template<typename R, typename... Parameters>
|
||||||
|
struct optionals_callable : public std::function<R(Parameters...)> {
|
||||||
|
using std::function<R(Parameters...)>::operator();
|
||||||
|
|
||||||
|
template<typename... CutList>
|
||||||
|
struct short_pack {};
|
||||||
|
/// This metafunction removes the first several types from a variadic parameter pack of types.
|
||||||
|
/// The first parameter is the number of arguments to remove from the beginning of the pack.
|
||||||
|
/// All subsequent parameters are types in the list to be cut
|
||||||
|
/// The result pack_cutter<...>::type is a short_pack<RemainingTypes>
|
||||||
|
template<unsigned RemoveCount, typename... Types>
|
||||||
|
struct pack_cutter;
|
||||||
|
template<unsigned RemoveCount, typename, typename... Types>
|
||||||
|
struct pack_cutter_impl;
|
||||||
|
template<typename... Types>
|
||||||
|
struct pack_cutter_impl<0, void, Types...> {
|
||||||
|
static_assert(all_optionals<Types...>::value, "All omitted arguments must correspond to optional parameters.");
|
||||||
|
using type = short_pack<Types...>;
|
||||||
|
};
|
||||||
|
template<unsigned RemoveCount, typename T, typename... Types>
|
||||||
|
struct pack_cutter_impl<RemoveCount, std::enable_if_t<RemoveCount != 0>, T, Types...>
|
||||||
|
: public pack_cutter_impl<RemoveCount - 1, void, Types...> {};
|
||||||
|
template<unsigned RemoveCount, typename... Types>
|
||||||
|
struct pack_cutter : public pack_cutter_impl<RemoveCount, void, Types...> {};
|
||||||
|
template<unsigned RemoveCount, typename... Types>
|
||||||
|
using pack_cutter_t = typename pack_cutter<RemoveCount, Types...>::type;
|
||||||
|
|
||||||
|
template<typename F, typename... OptionalTypes>
|
||||||
|
R call_function(F&& f, short_pack<OptionalTypes...>) {
|
||||||
|
return f(OptionalTypes()...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Overload the function call operator, enabled if the caller provides fewer arguments than there are parameters.
|
||||||
|
/// Pads out the provided arguments with default-constructed optionals, checking that they are indeed optional types
|
||||||
|
template<class... Args>
|
||||||
|
std::enable_if_t<sizeof...(Args) < sizeof...(Parameters), R> operator()(Args... args) {
|
||||||
|
// Partially apply with the arguments provided
|
||||||
|
auto partial_function = [this, &args...](auto&&... rest) {
|
||||||
|
return (*this)(std::forward<decltype(args)>(args)..., std::move(rest)...);
|
||||||
|
};
|
||||||
|
// Cut the provided arguments' types out of the Parameters list, and store the rest in a dummy type
|
||||||
|
pack_cutter_t<sizeof...(Args), std::decay_t<Parameters>...> dummy;
|
||||||
|
// Pass the partially applied function and the dummy type to another function which can deduce the optional
|
||||||
|
// types and call the function with default instantiations of those types
|
||||||
|
return call_function(std::move(partial_function), dummy);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is no longer used and probably no longer can be used without generalizing the infrastructure around it, but I
|
||||||
|
// kept it because it is informative.
|
||||||
|
// struct identity_member {
|
||||||
|
// template<typename R, typename C, typename P, typename... Args>
|
||||||
|
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
|
||||||
|
// template<typename R, typename C, typename P, typename... Args>
|
||||||
|
// static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
|
||||||
|
// };
|
||||||
|
/// Used as the Transform template parameter for APIs, this type has two main purposes: first, it reads the argument
|
||||||
|
/// list and return type of a method into template parameters; and second, it uses those types in conjunction with the
|
||||||
|
/// optionals_callable template above to create a function pointer which supports optional arguments.
|
||||||
|
struct identity_member_with_optionals {
|
||||||
template<typename R, typename C, typename P, typename... Args>
|
template<typename R, typename C, typename P, typename... Args>
|
||||||
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...) );
|
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...) );
|
||||||
template<typename R, typename C, typename P, typename... Args>
|
template<typename R, typename C, typename P, typename... Args>
|
||||||
static std::function<R(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const );
|
static detail::optionals_callable<R, Args...> functor( P&& p, R (C::*mem_func)(Args...)const );
|
||||||
};
|
};
|
||||||
|
|
||||||
template< typename Interface, typename Transform >
|
template< typename Interface, typename Transform >
|
||||||
struct vtable : public std::enable_shared_from_this<vtable<Interface,Transform>>
|
struct vtable : public std::enable_shared_from_this<vtable<Interface,Transform>>
|
||||||
{ private: vtable(); };
|
{ private: vtable(); };
|
||||||
|
|
@ -57,13 +135,13 @@ namespace fc {
|
||||||
|
|
||||||
// defined in api_connection.hpp
|
// defined in api_connection.hpp
|
||||||
template< typename T >
|
template< typename T >
|
||||||
api<T, identity_member> as();
|
api<T, identity_member_with_optionals> as();
|
||||||
};
|
};
|
||||||
typedef std::shared_ptr< api_base > api_ptr;
|
typedef std::shared_ptr< api_base > api_ptr;
|
||||||
|
|
||||||
class api_connection;
|
class api_connection;
|
||||||
|
|
||||||
template<typename Interface, typename Transform = identity_member >
|
template<typename Interface, typename Transform = identity_member_with_optionals >
|
||||||
class api : public api_base {
|
class api : public api_base {
|
||||||
public:
|
public:
|
||||||
typedef vtable<Interface,Transform> vtable_type;
|
typedef vtable<Interface,Transform> vtable_type;
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,12 @@ namespace fc { namespace http {
|
||||||
void on_connection( const on_connection_handler& handler);
|
void on_connection( const on_connection_handler& handler);
|
||||||
void listen( uint16_t port );
|
void listen( uint16_t port );
|
||||||
void listen( const fc::ip::endpoint& ep );
|
void listen( const fc::ip::endpoint& ep );
|
||||||
|
uint16_t get_listening_port();
|
||||||
void start_accept();
|
void start_accept();
|
||||||
|
|
||||||
|
void stop_listening();
|
||||||
|
void close();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class detail::websocket_server_impl;
|
friend class detail::websocket_server_impl;
|
||||||
std::unique_ptr<detail::websocket_server_impl> my;
|
std::unique_ptr<detail::websocket_server_impl> my;
|
||||||
|
|
@ -83,6 +87,9 @@ namespace fc { namespace http {
|
||||||
|
|
||||||
websocket_connection_ptr connect( const std::string& uri );
|
websocket_connection_ptr connect( const std::string& uri );
|
||||||
websocket_connection_ptr secure_connect( const std::string& uri );
|
websocket_connection_ptr secure_connect( const std::string& uri );
|
||||||
|
|
||||||
|
void close();
|
||||||
|
void synchronous_close();
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<detail::websocket_client_impl> my;
|
std::unique_ptr<detail::websocket_client_impl> my;
|
||||||
std::unique_ptr<detail::websocket_tls_client_impl> smy;
|
std::unique_ptr<detail::websocket_tls_client_impl> smy;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ namespace fc {
|
||||||
template<typename R, typename Arg0, typename ... Args>
|
template<typename R, typename Arg0, typename ... Args>
|
||||||
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )
|
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )
|
||||||
{
|
{
|
||||||
return [=]( Args... args ) { return f( a0, args... ); };
|
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
|
||||||
|
// captured as optional<bool>(false).
|
||||||
|
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
|
||||||
}
|
}
|
||||||
template<typename R>
|
template<typename R>
|
||||||
R call_generic( const std::function<R()>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth = 1 )
|
R call_generic( const std::function<R()>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth = 1 )
|
||||||
|
|
@ -44,9 +46,11 @@ namespace fc {
|
||||||
template<typename R, typename Arg0, typename ... Args>
|
template<typename R, typename Arg0, typename ... Args>
|
||||||
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
|
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
|
||||||
{
|
{
|
||||||
FC_ASSERT( a0 != e );
|
bool optional_args = all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
|
||||||
|
FC_ASSERT( a0 != e || optional_args );
|
||||||
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
|
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
|
||||||
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
|
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
|
||||||
|
return call_generic<R,Args...>( bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename R, typename ... Args>
|
template<typename R, typename ... Args>
|
||||||
|
|
@ -137,7 +141,9 @@ namespace fc {
|
||||||
template<typename R, typename Arg0, typename ... Args>
|
template<typename R, typename Arg0, typename ... Args>
|
||||||
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )const
|
std::function<R(Args...)> bind_first_arg( const std::function<R(Arg0,Args...)>& f, Arg0 a0 )const
|
||||||
{
|
{
|
||||||
return [=]( Args... args ) { return f( a0, args... ); };
|
// Capture a0 this way because of a {compiler,fc,???} bug that causes optional<bool>() to be incorrectly
|
||||||
|
// captured as optional<bool>(false).
|
||||||
|
return [f, a0 = std::decay_t<Arg0>(a0)]( Args... args ) { return f( a0, args... ); };
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename R>
|
template<typename R>
|
||||||
|
|
@ -172,9 +178,11 @@ namespace fc {
|
||||||
template<typename R, typename Arg0, typename ... Args>
|
template<typename R, typename Arg0, typename ... Args>
|
||||||
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
|
R call_generic( const std::function<R(Arg0,Args...)>& f, variants::const_iterator a0, variants::const_iterator e, uint32_t max_depth )
|
||||||
{
|
{
|
||||||
FC_ASSERT( a0 != e, "too few arguments passed to method" );
|
bool optional_args = detail::all_optionals<std::decay_t<Arg0>, std::decay_t<Args>...>::value;
|
||||||
|
FC_ASSERT( a0 != e || optional_args, "too few arguments passed to method" );
|
||||||
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
|
FC_ASSERT( max_depth > 0, "Recursion depth exceeded!" );
|
||||||
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, a0->as< typename std::decay<Arg0>::type >( max_depth - 1 ) ), a0+1, e, max_depth - 1 );
|
auto arg = (a0 == e)? std::decay_t<Arg0>() : a0->as<std::decay_t<Arg0>>(max_depth - 1);
|
||||||
|
return call_generic<R,Args...>( this->bind_first_arg<R,Arg0,Args...>( f, arg ), a0+1, e, max_depth - 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
struct api_visitor
|
struct api_visitor
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,6 @@ namespace fc { namespace http {
|
||||||
auto current_con = _connections.find(hdl);
|
auto current_con = _connections.find(hdl);
|
||||||
assert( current_con != _connections.end() );
|
assert( current_con != _connections.end() );
|
||||||
wdump(("server")(msg->get_payload()));
|
wdump(("server")(msg->get_payload()));
|
||||||
//std::cerr<<"recv: "<<msg->get_payload()<<"\n";
|
|
||||||
auto payload = msg->get_payload();
|
auto payload = msg->get_payload();
|
||||||
std::shared_ptr<websocket_connection> con = current_con->second;
|
std::shared_ptr<websocket_connection> con = current_con->second;
|
||||||
++_pending_messages;
|
++_pending_messages;
|
||||||
|
|
@ -290,7 +289,7 @@ namespace fc { namespace http {
|
||||||
if( _server.is_listening() )
|
if( _server.is_listening() )
|
||||||
_server.stop_listening();
|
_server.stop_listening();
|
||||||
|
|
||||||
if( _connections.size() )
|
if ( _connections.size() )
|
||||||
_closed = new fc::promise<void>();
|
_closed = new fc::promise<void>();
|
||||||
|
|
||||||
auto cpy_con = _connections;
|
auto cpy_con = _connections;
|
||||||
|
|
@ -431,6 +430,7 @@ namespace fc { namespace http {
|
||||||
|
|
||||||
typedef websocket_client_type::connection_ptr websocket_client_connection_type;
|
typedef websocket_client_type::connection_ptr websocket_client_connection_type;
|
||||||
typedef websocket_tls_client_type::connection_ptr websocket_tls_client_connection_type;
|
typedef websocket_tls_client_type::connection_ptr websocket_tls_client_connection_type;
|
||||||
|
using websocketpp::connection_hdl;
|
||||||
|
|
||||||
class websocket_client_impl
|
class websocket_client_impl
|
||||||
{
|
{
|
||||||
|
|
@ -484,6 +484,7 @@ namespace fc { namespace http {
|
||||||
websocket_client_type _client;
|
websocket_client_type _client;
|
||||||
websocket_connection_ptr _connection;
|
websocket_connection_ptr _connection;
|
||||||
std::string _uri;
|
std::string _uri;
|
||||||
|
fc::optional<connection_hdl> _hdl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -625,13 +626,29 @@ namespace fc { namespace http {
|
||||||
}
|
}
|
||||||
void websocket_server::listen( const fc::ip::endpoint& ep )
|
void websocket_server::listen( const fc::ip::endpoint& ep )
|
||||||
{
|
{
|
||||||
my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) );
|
my->_server.listen( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4(uint32_t(ep.get_address())),ep.port()) );
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t websocket_server::get_listening_port()
|
||||||
|
{
|
||||||
|
websocketpp::lib::asio::error_code ec;
|
||||||
|
return my->_server.get_local_endpoint(ec).port();
|
||||||
}
|
}
|
||||||
|
|
||||||
void websocket_server::start_accept() {
|
void websocket_server::start_accept() {
|
||||||
my->_server.start_accept();
|
my->_server.start_accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void websocket_server::stop_listening()
|
||||||
|
{
|
||||||
|
my->_server.stop_listening();
|
||||||
|
}
|
||||||
|
|
||||||
|
void websocket_server::close()
|
||||||
|
{
|
||||||
|
for (auto& connection : my->_connections)
|
||||||
|
my->_server.close(connection.first, websocketpp::close::status::normal, "Goodbye");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -678,6 +695,7 @@ namespace fc { namespace http {
|
||||||
my->_connected = fc::promise<void>::ptr( new fc::promise<void>("websocket::connect") );
|
my->_connected = fc::promise<void>::ptr( new fc::promise<void>("websocket::connect") );
|
||||||
|
|
||||||
my->_client.set_open_handler( [=]( websocketpp::connection_hdl hdl ){
|
my->_client.set_open_handler( [=]( websocketpp::connection_hdl hdl ){
|
||||||
|
my->_hdl = hdl;
|
||||||
auto con = my->_client.get_con_from_hdl(hdl);
|
auto con = my->_client.get_con_from_hdl(hdl);
|
||||||
my->_connection = std::make_shared<detail::websocket_connection_impl<detail::websocket_client_connection_type>>( con );
|
my->_connection = std::make_shared<detail::websocket_connection_impl<detail::websocket_client_connection_type>>( con );
|
||||||
my->_closed = fc::promise<void>::ptr( new fc::promise<void>("websocket::closed") );
|
my->_closed = fc::promise<void>::ptr( new fc::promise<void>("websocket::closed") );
|
||||||
|
|
@ -717,7 +735,20 @@ namespace fc { namespace http {
|
||||||
smy->_client.connect(con);
|
smy->_client.connect(con);
|
||||||
smy->_connected->wait();
|
smy->_connected->wait();
|
||||||
return smy->_connection;
|
return smy->_connection;
|
||||||
} FC_CAPTURE_AND_RETHROW( (uri) ) }
|
} FC_CAPTURE_AND_RETHROW( (uri) ) }
|
||||||
|
|
||||||
|
void websocket_client::close()
|
||||||
|
{
|
||||||
|
if (my->_hdl)
|
||||||
|
my->_client.close(*my->_hdl, websocketpp::close::status::normal, "Goodbye");
|
||||||
|
}
|
||||||
|
|
||||||
|
void websocket_client::synchronous_close()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
if (my->_closed)
|
||||||
|
my->_closed->wait();
|
||||||
|
}
|
||||||
|
|
||||||
websocket_connection_ptr websocket_tls_client::connect( const std::string& uri )
|
websocket_connection_ptr websocket_tls_client::connect( const std::string& uri )
|
||||||
{ try {
|
{ try {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
|
|
||||||
add_executable( api api.cpp )
|
|
||||||
target_link_libraries( api fc )
|
|
||||||
|
|
||||||
if( ECC_IMPL STREQUAL secp256k1 )
|
if( ECC_IMPL STREQUAL secp256k1 )
|
||||||
add_executable( blind all_tests.cpp crypto/blind.cpp )
|
add_executable( blind all_tests.cpp crypto/blind.cpp )
|
||||||
target_link_libraries( blind fc )
|
target_link_libraries( blind fc )
|
||||||
|
|
@ -55,5 +51,6 @@ add_executable( all_tests all_tests.cpp
|
||||||
utf8_test.cpp
|
utf8_test.cpp
|
||||||
variant_test.cpp
|
variant_test.cpp
|
||||||
logging_tests.cpp
|
logging_tests.cpp
|
||||||
|
api_tests.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries( all_tests fc )
|
target_link_libraries( all_tests fc )
|
||||||
|
|
|
||||||
191
tests/api.cpp
191
tests/api.cpp
|
|
@ -1,191 +0,0 @@
|
||||||
#include <fc/api.hpp>
|
|
||||||
#include <fc/log/logger.hpp>
|
|
||||||
#include <fc/rpc/api_connection.hpp>
|
|
||||||
#include <fc/rpc/websocket_api.hpp>
|
|
||||||
|
|
||||||
class calculator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
int32_t add( int32_t a, int32_t b ); // not implemented
|
|
||||||
int32_t sub( int32_t a, int32_t b ); // not implemented
|
|
||||||
void on_result( const std::function<void(int32_t)>& cb );
|
|
||||||
void on_result2( const std::function<void(int32_t)>& cb, int test );
|
|
||||||
};
|
|
||||||
|
|
||||||
FC_API( calculator, (add)(sub)(on_result)(on_result2) )
|
|
||||||
|
|
||||||
|
|
||||||
class login_api
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
fc::api<calculator> get_calc()const
|
|
||||||
{
|
|
||||||
FC_ASSERT( calc );
|
|
||||||
return *calc;
|
|
||||||
}
|
|
||||||
fc::optional<fc::api<calculator>> calc;
|
|
||||||
std::set<std::string> test( const std::string&, const std::string& ) { return std::set<std::string>(); }
|
|
||||||
};
|
|
||||||
FC_API( login_api, (get_calc)(test) );
|
|
||||||
|
|
||||||
using namespace fc;
|
|
||||||
|
|
||||||
class some_calculator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
int32_t add( int32_t a, int32_t b ) { wlog("."); if( _cb ) _cb(a+b); return a+b; }
|
|
||||||
int32_t sub( int32_t a, int32_t b ) { wlog(".");if( _cb ) _cb(a-b); return a-b; }
|
|
||||||
void on_result( const std::function<void(int32_t)>& cb ) { wlog( "set callback" ); _cb = cb; return ; }
|
|
||||||
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
|
|
||||||
std::function<void(int32_t)> _cb;
|
|
||||||
};
|
|
||||||
class variant_calculator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
double add( fc::variant a, fc::variant b ) { return a.as_double()+b.as_double(); }
|
|
||||||
double sub( fc::variant a, fc::variant b ) { return a.as_double()-b.as_double(); }
|
|
||||||
void on_result( const std::function<void(int32_t)>& cb ) { wlog("set callback"); _cb = cb; return ; }
|
|
||||||
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
|
|
||||||
std::function<void(int32_t)> _cb;
|
|
||||||
};
|
|
||||||
|
|
||||||
using namespace fc::http;
|
|
||||||
using namespace fc::rpc;
|
|
||||||
|
|
||||||
#define MAX_DEPTH 10
|
|
||||||
|
|
||||||
int main( int argc, char** argv )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
fc::api<calculator> calc_api( std::make_shared<some_calculator>() );
|
|
||||||
|
|
||||||
fc::http::websocket_server server;
|
|
||||||
server.on_connection([&]( const websocket_connection_ptr& c ){
|
|
||||||
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
|
|
||||||
auto login = std::make_shared<login_api>();
|
|
||||||
login->calc = calc_api;
|
|
||||||
wsc->register_api(fc::api<login_api>(login));
|
|
||||||
c->set_session_data( wsc );
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen( 8090 );
|
|
||||||
server.start_accept();
|
|
||||||
|
|
||||||
for( uint32_t i = 0; i < 5000; ++i )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
fc::http::websocket_client client;
|
|
||||||
auto con = client.connect( "ws://localhost:8090" );
|
|
||||||
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
|
|
||||||
auto remote_login_api = apic->get_remote_api<login_api>();
|
|
||||||
auto remote_calc = remote_login_api->get_calc();
|
|
||||||
remote_calc->on_result( []( uint32_t r ) { elog( "callback result ${r}", ("r",r) ); } );
|
|
||||||
wdump((remote_calc->add( 4, 5 )));
|
|
||||||
} catch ( const fc::exception& e )
|
|
||||||
{
|
|
||||||
edump((e.to_detail_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wlog( "exit scope" );
|
|
||||||
}
|
|
||||||
catch( const fc::exception& e )
|
|
||||||
{
|
|
||||||
edump((e.to_detail_string()));
|
|
||||||
}
|
|
||||||
wlog( "returning now..." );
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
some_calculator calc;
|
|
||||||
variant_calculator vcalc;
|
|
||||||
|
|
||||||
fc::api<calculator> api_calc( &calc );
|
|
||||||
fc::api<calculator> api_vcalc( &vcalc );
|
|
||||||
fc::api<calculator> api_nested_calc( api_calc );
|
|
||||||
|
|
||||||
wdump( (api_calc->add(5,4)) );
|
|
||||||
wdump( (api_calc->sub(5,4)) );
|
|
||||||
wdump( (api_vcalc->add(5,4)) );
|
|
||||||
wdump( (api_vcalc->sub(5,4)) );
|
|
||||||
wdump( (api_nested_calc->sub(5,4)) );
|
|
||||||
wdump( (api_nested_calc->sub(5,4)) );
|
|
||||||
|
|
||||||
/*
|
|
||||||
variants v = { 4, 5 };
|
|
||||||
auto g = to_generic( api_calc->add );
|
|
||||||
auto r = call_generic( api_calc->add, v.begin(), v.end() );
|
|
||||||
wdump((r));
|
|
||||||
wdump( (g(v)) );
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
try {
|
|
||||||
fc::api_server server;
|
|
||||||
auto api_id = server.register_api( api_calc );
|
|
||||||
wdump( (api_id) );
|
|
||||||
auto result = server.call( api_id, "add", {4, 5} );
|
|
||||||
wdump( (result) );
|
|
||||||
} catch ( const fc::exception& e )
|
|
||||||
{
|
|
||||||
elog( "${e}", ("e",e.to_detail_string() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
ilog( "------------------ NESTED TEST --------------" );
|
|
||||||
try {
|
|
||||||
login_api napi_impl;
|
|
||||||
napi_impl.calc = api_calc;
|
|
||||||
fc::api<login_api> napi(&napi_impl);
|
|
||||||
|
|
||||||
fc::api_server server;
|
|
||||||
auto api_id = server.register_api( napi );
|
|
||||||
wdump( (api_id) );
|
|
||||||
auto result = server.call( api_id, "get_calc" );
|
|
||||||
wdump( (result) );
|
|
||||||
result = server.call( result.as_uint64(), "add", {4,5} );
|
|
||||||
wdump( (result) );
|
|
||||||
|
|
||||||
|
|
||||||
fc::api<api_server> serv( &server );
|
|
||||||
|
|
||||||
fc::api_client<login_api> apic( serv );
|
|
||||||
|
|
||||||
fc::api<login_api> remote_api = apic;
|
|
||||||
|
|
||||||
|
|
||||||
auto remote_calc = remote_api->get_calc();
|
|
||||||
int r = remote_calc->add( 4, 5 );
|
|
||||||
idump( (r) );
|
|
||||||
|
|
||||||
} catch ( const fc::exception& e )
|
|
||||||
{
|
|
||||||
elog( "${e}", ("e",e.to_detail_string() ) );
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
ilog( "------------------ NESTED TEST --------------" );
|
|
||||||
try {
|
|
||||||
login_api napi_impl;
|
|
||||||
napi_impl.calc = api_calc;
|
|
||||||
fc::api<login_api> napi(&napi_impl);
|
|
||||||
|
|
||||||
|
|
||||||
auto client_side = std::make_shared<local_api_connection>(MAX_DEPTH);
|
|
||||||
auto server_side = std::make_shared<local_api_connection>(MAX_DEPTH);
|
|
||||||
server_side->set_remote_connection( client_side );
|
|
||||||
client_side->set_remote_connection( server_side );
|
|
||||||
|
|
||||||
server_side->register_api( napi );
|
|
||||||
|
|
||||||
fc::api<login_api> remote_api = client_side->get_remote_api<login_api>();
|
|
||||||
|
|
||||||
auto remote_calc = remote_api->get_calc();
|
|
||||||
int r = remote_calc->add( 4, 5 );
|
|
||||||
idump( (r) );
|
|
||||||
|
|
||||||
} catch ( const fc::exception& e )
|
|
||||||
{
|
|
||||||
elog( "${e}", ("e",e.to_detail_string() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
138
tests/api_tests.cpp
Normal file
138
tests/api_tests.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
#include <fc/api.hpp>
|
||||||
|
#include <fc/log/logger.hpp>
|
||||||
|
#include <fc/rpc/api_connection.hpp>
|
||||||
|
#include <fc/rpc/websocket_api.hpp>
|
||||||
|
|
||||||
|
class calculator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int32_t add( int32_t a, int32_t b ); // not implemented
|
||||||
|
int32_t sub( int32_t a, int32_t b ); // not implemented
|
||||||
|
void on_result( const std::function<void(int32_t)>& cb );
|
||||||
|
void on_result2( const std::function<void(int32_t)>& cb, int test );
|
||||||
|
};
|
||||||
|
|
||||||
|
FC_API( calculator, (add)(sub)(on_result)(on_result2) )
|
||||||
|
|
||||||
|
|
||||||
|
class login_api
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
fc::api<calculator> get_calc()const
|
||||||
|
{
|
||||||
|
FC_ASSERT( calc );
|
||||||
|
return *calc;
|
||||||
|
}
|
||||||
|
fc::optional<fc::api<calculator>> calc;
|
||||||
|
std::set<std::string> test( const std::string&, const std::string& ) { return std::set<std::string>(); }
|
||||||
|
};
|
||||||
|
FC_API( login_api, (get_calc)(test) );
|
||||||
|
|
||||||
|
|
||||||
|
class optionals_api
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string foo( const std::string& first, const fc::optional<std::string>& second,
|
||||||
|
const fc::optional<std::string>& third ) {
|
||||||
|
return fc::json::to_string(fc::variants{first, {second, 2}, {third, 2}});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FC_API( optionals_api, (foo) );
|
||||||
|
|
||||||
|
using namespace fc;
|
||||||
|
|
||||||
|
class some_calculator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int32_t add( int32_t a, int32_t b ) { wlog("."); if( _cb ) _cb(a+b); return a+b; }
|
||||||
|
int32_t sub( int32_t a, int32_t b ) { wlog(".");if( _cb ) _cb(a-b); return a-b; }
|
||||||
|
void on_result( const std::function<void(int32_t)>& cb ) { wlog( "set callback" ); _cb = cb; return ; }
|
||||||
|
void on_result2( const std::function<void(int32_t)>& cb, int test ){}
|
||||||
|
std::function<void(int32_t)> _cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
using namespace fc::http;
|
||||||
|
using namespace fc::rpc;
|
||||||
|
|
||||||
|
#define MAX_DEPTH 10
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(api_tests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(login_test) {
|
||||||
|
try {
|
||||||
|
fc::api<calculator> calc_api( std::make_shared<some_calculator>() );
|
||||||
|
|
||||||
|
auto server = std::make_shared<fc::http::websocket_server>();
|
||||||
|
server->on_connection([&]( const websocket_connection_ptr& c ){
|
||||||
|
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
|
||||||
|
auto login = std::make_shared<login_api>();
|
||||||
|
login->calc = calc_api;
|
||||||
|
wsc->register_api(fc::api<login_api>(login));
|
||||||
|
c->set_session_data( wsc );
|
||||||
|
});
|
||||||
|
|
||||||
|
server->listen( 0 );
|
||||||
|
auto listen_port = server->get_listening_port();
|
||||||
|
server->start_accept();
|
||||||
|
|
||||||
|
auto client = std::make_shared<fc::http::websocket_client>();
|
||||||
|
auto con = client->connect( "ws://localhost:" + std::to_string(listen_port) );
|
||||||
|
server->stop_listening();
|
||||||
|
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
|
||||||
|
auto remote_login_api = apic->get_remote_api<login_api>();
|
||||||
|
auto remote_calc = remote_login_api->get_calc();
|
||||||
|
bool remote_triggered = false;
|
||||||
|
remote_calc->on_result( [&remote_triggered]( uint32_t r ) { remote_triggered = true; } );
|
||||||
|
BOOST_CHECK_EQUAL(remote_calc->add( 4, 5 ), 9);
|
||||||
|
BOOST_CHECK(remote_triggered);
|
||||||
|
|
||||||
|
client->synchronous_close();
|
||||||
|
server->close();
|
||||||
|
fc::usleep(fc::milliseconds(50));
|
||||||
|
client.reset();
|
||||||
|
server.reset();
|
||||||
|
} FC_LOG_AND_RETHROW()
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(optionals_test) {
|
||||||
|
try {
|
||||||
|
auto optionals = std::make_shared<optionals_api>();
|
||||||
|
fc::api<optionals_api> oapi(optionals);
|
||||||
|
BOOST_CHECK_EQUAL(oapi->foo("a"), "[\"a\",null,null]");
|
||||||
|
BOOST_CHECK_EQUAL(oapi->foo("a", "b"), "[\"a\",\"b\",null]");
|
||||||
|
BOOST_CHECK_EQUAL(oapi->foo("a", "b", "c"), "[\"a\",\"b\",\"c\"]");
|
||||||
|
BOOST_CHECK_EQUAL(oapi->foo("a", {}, "c"), "[\"a\",null,\"c\"]");
|
||||||
|
|
||||||
|
auto server = std::make_shared<fc::http::websocket_server>();
|
||||||
|
server->on_connection([&]( const websocket_connection_ptr& c ){
|
||||||
|
auto wsc = std::make_shared<websocket_api_connection>(c, MAX_DEPTH);
|
||||||
|
wsc->register_api(fc::api<optionals_api>(optionals));
|
||||||
|
c->set_session_data( wsc );
|
||||||
|
});
|
||||||
|
|
||||||
|
server->listen( 0 );
|
||||||
|
auto listen_port = server->get_listening_port();
|
||||||
|
server->start_accept();
|
||||||
|
|
||||||
|
auto client = std::make_shared<fc::http::websocket_client>();
|
||||||
|
auto con = client->connect( "ws://localhost:" + std::to_string(listen_port) );
|
||||||
|
server->stop_listening();
|
||||||
|
auto apic = std::make_shared<websocket_api_connection>(con, MAX_DEPTH);
|
||||||
|
auto remote_optionals = apic->get_remote_api<optionals_api>();
|
||||||
|
|
||||||
|
BOOST_CHECK_EQUAL(remote_optionals->foo("a"), "[\"a\",null,null]");
|
||||||
|
BOOST_CHECK_EQUAL(remote_optionals->foo("a", "b"), "[\"a\",\"b\",null]");
|
||||||
|
BOOST_CHECK_EQUAL(remote_optionals->foo("a", "b", "c"), "[\"a\",\"b\",\"c\"]");
|
||||||
|
BOOST_CHECK_EQUAL(remote_optionals->foo("a", {}, "c"), "[\"a\",null,\"c\"]");
|
||||||
|
|
||||||
|
client->synchronous_close();
|
||||||
|
server->close();
|
||||||
|
fc::usleep(fc::milliseconds(50));
|
||||||
|
client.reset();
|
||||||
|
server.reset();
|
||||||
|
} FC_LOG_AND_RETHROW()
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
@ -20,21 +20,8 @@ BOOST_AUTO_TEST_CASE(websocket_test)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
bool listen_ok = false;
|
server.listen( 0 );
|
||||||
for( int i = 0; !listen_ok && i < 5; ++i )
|
port = server.get_listening_port();
|
||||||
{
|
|
||||||
port = std::rand() % 50000 + 10000;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
server.listen( port );
|
|
||||||
listen_ok = true;
|
|
||||||
}
|
|
||||||
catch( std::exception& ignore )
|
|
||||||
{
|
|
||||||
// if the port is busy, listen() will throw a std::exception, do nothing here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BOOST_REQUIRE( listen_ok );
|
|
||||||
|
|
||||||
server.start_accept();
|
server.start_accept();
|
||||||
|
|
||||||
|
|
@ -44,49 +31,27 @@ BOOST_AUTO_TEST_CASE(websocket_test)
|
||||||
echo = s;
|
echo = s;
|
||||||
});
|
});
|
||||||
c_conn->send_message( "hello world" );
|
c_conn->send_message( "hello world" );
|
||||||
fc::usleep( fc::seconds(1) );
|
fc::usleep( fc::milliseconds(100) );
|
||||||
BOOST_CHECK_EQUAL("echo: hello world", echo);
|
BOOST_CHECK_EQUAL("echo: hello world", echo);
|
||||||
c_conn->send_message( "again" );
|
c_conn->send_message( "again" );
|
||||||
fc::usleep( fc::seconds(1) );
|
fc::usleep( fc::milliseconds(100) );
|
||||||
BOOST_CHECK_EQUAL("echo: again", echo);
|
BOOST_CHECK_EQUAL("echo: again", echo);
|
||||||
|
|
||||||
s_conn->close(0, "test");
|
s_conn->close(0, "test");
|
||||||
fc::usleep( fc::seconds(1) );
|
fc::usleep( fc::milliseconds(100) );
|
||||||
try {
|
BOOST_CHECK_THROW(c_conn->send_message( "again" ), fc::exception);
|
||||||
c_conn->send_message( "again" );
|
|
||||||
BOOST_FAIL("expected assertion failure");
|
|
||||||
} catch (const fc::exception& e) {
|
|
||||||
//std::cerr << e.to_string() << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
c_conn = client.connect( "ws://localhost:" + fc::to_string(port) );
|
c_conn = client.connect( "ws://localhost:" + fc::to_string(port) );
|
||||||
c_conn->on_message_handler([&](const std::string& s){
|
c_conn->on_message_handler([&](const std::string& s){
|
||||||
echo = s;
|
echo = s;
|
||||||
});
|
});
|
||||||
c_conn->send_message( "hello world" );
|
c_conn->send_message( "hello world" );
|
||||||
fc::usleep( fc::seconds(1) );
|
fc::usleep( fc::milliseconds(100) );
|
||||||
BOOST_CHECK_EQUAL("echo: hello world", echo);
|
BOOST_CHECK_EQUAL("echo: hello world", echo);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
BOOST_CHECK_THROW(c_conn->send_message( "again" ), fc::assert_exception);
|
||||||
c_conn->send_message( "again" );
|
BOOST_CHECK_THROW(client.connect( "ws://localhost:" + fc::to_string(port) ), fc::exception);
|
||||||
BOOST_FAIL("expected assertion failure");
|
|
||||||
} catch (const fc::assert_exception& e) {
|
|
||||||
std::cerr << "Expected assertion failure : " << e.to_string() << "\n";
|
|
||||||
} catch (const fc::exception& e) {
|
|
||||||
BOOST_FAIL("Unexpected exception : " + e.to_string());
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
BOOST_FAIL("Unexpected exception : " + std::string(e.what()));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
c_conn = client.connect( "ws://localhost:" + fc::to_string(port) );
|
|
||||||
BOOST_FAIL("expected fc::exception (fail to connect)");
|
|
||||||
} catch (const fc::exception& e) {
|
|
||||||
std::cerr << "Excepted fc::exception : " << e.to_string() << "\n";
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
BOOST_FAIL("Unexpected exception : " + std::string(e.what()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue