BitShares-Core  7.0.2
BitShares blockchain node software and command-line wallet software
websocket_api.cpp
Go to the documentation of this file.
1 #include <fc/reflect/variant.hpp>
3 #include <fc/io/json.hpp>
4 
5 namespace fc { namespace rpc {
6 
8 {
9 }
10 
11 websocket_api_connection::websocket_api_connection( const std::shared_ptr<fc::http::websocket_connection>& c,
12  uint32_t max_depth )
13  : api_connection(max_depth),_connection(c)
14 {
15  FC_ASSERT( _connection, "A valid websocket connection is required" );
16  _rpc_state.add_method( "call", [this]( const variants& args ) -> variant
17  {
18  FC_ASSERT( args.size() == 3 && args[2].is_array() );
19  api_id_type api_id;
20  if( args[0].is_string() )
21  {
22  variant subresult = this->receive_call( 1, args[0].as_string() );
23  api_id = subresult.as_uint64();
24  }
25  else
26  api_id = args[0].as_uint64();
27 
28  return this->receive_call(
29  api_id,
30  args[1].as_string(),
31  args[2].get_array() );
32  } );
33 
34  _rpc_state.add_method( "notice", [this]( const variants& args ) -> variant
35  {
36  FC_ASSERT( args.size() == 2 && args[1].is_array() );
37  this->receive_notice( args[0].as_uint64(), args[1].get_array() );
38  return variant();
39  } );
40 
41  _rpc_state.add_method( "callback", [this]( const variants& args ) -> variant
42  {
43  FC_ASSERT( args.size() == 2 && args[1].is_array() );
44  this->receive_callback( args[0].as_uint64(), args[1].get_array() );
45  return variant();
46  } );
47 
48  _rpc_state.on_unhandled( [&]( const std::string& method_name, const variants& args )
49  {
50  return this->receive_call( 0, method_name, args );
51  } );
52 
53  _connection->on_message_handler( [this]( const std::string& msg ){
54  response reply = on_message(msg);
55  if( _connection && ( reply.id || reply.result || reply.error || reply.jsonrpc ) )
58  } );
59  _connection->on_http_handler( [this]( const std::string& msg ){
60  response reply = on_message(msg);
61  fc::http::reply result;
62  if( reply.error )
63  {
64  if( reply.error->code == -32603 )
66  else if( reply.error->code <= -32600 )
68  }
69  if( reply.id || reply.result || reply.error || reply.jsonrpc )
72  else
74 
75  return result;
76  } );
77  _connection->closed.connect( [this](){
78  closed();
79  _connection = nullptr;
80  } );
81 }
82 
84  api_id_type api_id,
85  string method_name,
86  variants args /* = variants() */ )
87 {
88  if( !_connection ) // defensive check
89  return variant(); // TODO return an error?
90 
91  auto request = _rpc_state.start_remote_call( "call", { api_id, std::move(method_name), std::move(args) } );
96 }
97 
99  uint64_t callback_id,
100  variants args /* = variants() */ )
101 {
102  if( !_connection ) // defensive check
103  return variant(); // TODO return an error?
104 
105  auto request = _rpc_state.start_remote_call( "callback", { callback_id, std::move(args) } );
110 }
111 
113  uint64_t callback_id,
114  variants args /* = variants() */ )
115 {
116  if( !_connection ) // defensive check
117  return;
118 
119  fc::rpc::request req{ optional<uint64_t>(), "notice", { callback_id, std::move(args) } };
123 }
124 
125 response websocket_api_connection::on_message( const std::string& message )
126 {
127  variant var;
128  try
129  {
131  }
132  catch( const fc::exception& e )
133  {
134  return response( variant(), { -32700, "Invalid JSON message", variant( e, _max_conversion_depth ) }, "2.0" );
135  }
136 
137  if( var.is_array() )
138  return response( variant(), { -32600, "Batch requests not supported" }, "2.0" );
139 
140  if( !var.is_object() )
141  return response( variant(), { -32600, "Invalid JSON request" }, "2.0" );
142 
143  variant_object var_obj = var.get_object();
144 
145  if( var_obj.contains( "id" )
146  && !var_obj["id"].is_string() && !var_obj["id"].is_numeric() && !var_obj["id"].is_null() )
147  return response( variant(), { -32600, "Invalid id" }, "2.0" );
148 
149  if( var_obj.contains( "method" ) && ( !var_obj["method"].is_string() || var_obj["method"].get_string() == "" ) )
150  return response( variant(), { -32600, "Missing or invalid method" }, "2.0" );
151 
152  if( var_obj.contains( "jsonrpc" ) && ( !var_obj["jsonrpc"].is_string() || var_obj["jsonrpc"] != "2.0" ) )
153  return response( variant(), { -32600, "Unsupported JSON-RPC version" }, "2.0" );
154 
155  if( var_obj.contains( "method" ) )
156  {
157  if( var_obj.contains( "params" ) && var_obj["params"].is_object() )
158  return response( variant(), { -32602, "Named parameters not supported" }, "2.0" );
159 
160  if( var_obj.contains( "params" ) && !var_obj["params"].is_array() )
161  return response( variant(), { -32600, "Invalid parameters" }, "2.0" );
162 
163  return on_request( std::move( var ) );
164  }
165 
166  if( var_obj.contains( "result" ) || var_obj.contains("error") )
167  {
168  if( !var_obj.contains( "id" ) || ( var_obj["id"].is_null() && !var_obj.contains( "jsonrpc" ) ) )
169  return response( variant(), { -32600, "Missing or invalid id" }, "2.0" );
170 
171  on_response( std::move( var ) );
172 
173  return response();
174  }
175 
176  return response( variant(), { -32600, "Missing method or result or error" }, "2.0" );
177 }
178 
180 {
182 }
183 
185 {
187  if( var.get_object().contains( "id" ) )
188  call.id = var.get_object()["id"]; // special handling for null id
189 
190  // null ID is valid in JSONRPC-2.0 but signals "no id" in JSONRPC-1.0
191  bool has_id = call.id.valid() && ( call.jsonrpc.valid() || !call.id->is_null() );
192 
193  try
194  {
195 #ifdef LOG_LONG_API
196  auto start = time_point::now();
197 #endif
198 
199  auto result = _rpc_state.local_call( call.method, call.params );
200 
201 #ifdef LOG_LONG_API
202  auto end = time_point::now();
203 
204  if( end - start > fc::milliseconds( LOG_LONG_API_MAX_MS ) )
205  elog( "API call execution time limit exceeded. method: ${m} params: ${p} time: ${t}",
206  ("m",call.method)("p",call.params)("t", end - start) );
207  else if( end - start > fc::milliseconds( LOG_LONG_API_WARN_MS ) )
208  wlog( "API call execution time nearing limit. method: ${m} params: ${p} time: ${t}",
209  ("m",call.method)("p",call.params)("t", end - start) );
210 #endif
211 
212  if( has_id )
213  return response( call.id, result, call.jsonrpc );
214  }
215  catch ( const fc::method_not_found_exception& e )
216  {
217  if( has_id )
218  return response( call.id, error_object{ -32601, "Method not found",
219  variant( (fc::exception) e, _max_conversion_depth ) }, call.jsonrpc );
220  }
221  catch ( const fc::exception& e )
222  {
223  if( has_id )
224  return response( call.id, error_object{ e.code(), "Execution error: " + e.to_string(),
225  variant( e, _max_conversion_depth ) },
226  call.jsonrpc );
227  }
228  catch ( const std::exception& e )
229  {
230  elog( "Internal error - ${e}", ("e",e.what()) );
231  return response( call.id, error_object{ -32603, "Internal error", variant( e.what(), _max_conversion_depth ) },
232  call.jsonrpc );
233  }
234  catch ( ... )
235  {
236  elog( "Internal error while processing RPC request" );
237  throw;
238  }
239  return response();
240 }
241 
242 } } // namespace fc::rpc
fc::rpc::request::params
variants params
Definition: state.hpp:11
fc::json::legacy_parser
@ legacy_parser
Definition: json.hpp:22
fc::rpc::state::handle_reply
void handle_reply(const response &response)
Definition: state.cpp:30
fc::milliseconds
microseconds milliseconds(int64_t s)
Definition: time.hpp:35
fc::variant_object
An order-perserving dictionary of variant's.
Definition: variant_object.hpp:20
fc::http::reply::InternalServerError
@ InternalServerError
Definition: connection.hpp:33
fc::rpc::state::add_method
void add_method(const std::string &name, method m)
Definition: state.cpp:11
fc::rpc::response::id
optional< variant > id
Definition: state.hpp:31
fc::rpc::websocket_api_connection::send_callback
virtual variant send_callback(uint64_t callback_id, variants args=variants()) override
Definition: websocket_api.cpp:98
fc::rpc::websocket_api_connection::_rpc_state
fc::rpc::state _rpc_state
Definition: websocket_api.hpp:32
wlog
#define wlog(FORMAT,...)
Definition: logger.hpp:123
fc::rpc::request::id
optional< variant > id
Definition: state.hpp:9
fc::http::reply::body_as_string
std::string body_as_string
Definition: connection.hpp:39
fc::exception
Used to generate a useful error report when an exception is thrown.
Definition: exception.hpp:56
variant.hpp
fc::rpc::websocket_api_connection::~websocket_api_connection
~websocket_api_connection()
Definition: websocket_api.cpp:7
fc::variant_object::contains
bool contains(const char *key) const
Definition: variant_object.hpp:61
fc::rpc::state::wait_for_response
variant wait_for_response(const variant &request_id)
Definition: state.cpp:52
websocket_api.hpp
fc::rpc::request
Definition: state.hpp:7
fc::variant::as_uint64
uint64_t as_uint64() const
Definition: variant.cpp:398
fc
Definition: api.hpp:15
fc::http::reply::BadRequest
@ BadRequest
Definition: connection.hpp:29
fc::api_id_type
uint32_t api_id_type
Definition: api.hpp:122
fc::rpc::request::jsonrpc
optional< std::string > jsonrpc
Definition: state.hpp:12
fc::rpc::state::on_unhandled
void on_unhandled(const std::function< variant(const string &, const variants &)> &unhandled)
Definition: state.cpp:64
fc::json::from_string
static variant from_string(const string &utf8_str, parse_type ptype=legacy_parser, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:458
fc::variant::is_array
bool is_array() const
Definition: variant.cpp:368
fc::rpc::request::method
std::string method
Definition: state.hpp:10
fc::json::to_string
static string to_string(const variant &v, output_formatting format=stringify_large_ints_and_doubles, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:650
fc::rpc::response
Definition: state.hpp:22
fc::json::stringify_large_ints_and_doubles
@ stringify_large_ints_and_doubles
Definition: json.hpp:32
fc::api_connection::closed
fc::signal< void()> closed
Definition: api_connection.hpp:297
fc::optional::valid
bool valid() const
Definition: optional.hpp:186
fc::variants
std::vector< variant > variants
Definition: variant.hpp:170
fc::rpc::websocket_api_connection::on_message
response on_message(const std::string &message)
Definition: websocket_api.cpp:125
fc::rpc::state::start_remote_call
request start_remote_call(const string &method_name, variants args)
Definition: state.cpp:46
fc::variant::as
T as(uint32_t max_depth) const
Definition: variant.hpp:337
fc::rpc::response::jsonrpc
optional< std::string > jsonrpc
Definition: state.hpp:32
fc::rpc::websocket_api_connection::send_call
virtual variant send_call(api_id_type api_id, string method_name, variants args=variants()) override
Definition: websocket_api.cpp:83
fc::rpc::websocket_api_connection::on_request
response on_request(const variant &message)
Definition: websocket_api.cpp:184
fc::variant::is_object
bool is_object() const
Definition: variant.cpp:363
fc::rpc::websocket_api_connection::on_response
void on_response(const variant &message)
Definition: websocket_api.cpp:179
fc::rpc::response::result
optional< fc::variant > result
Definition: state.hpp:33
fc::http::reply::NoContent
@ NoContent
Definition: connection.hpp:28
fc::http::reply::status
int status
Definition: connection.hpp:36
json.hpp
fc::rpc::websocket_api_connection::send_notice
virtual void send_notice(uint64_t callback_id, variants args=variants()) override
Definition: websocket_api.cpp:112
fc::time_point::now
static time_point now()
Definition: time.cpp:13
FC_ASSERT
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
fc::variant::get_object
variant_object & get_object()
Definition: variant.cpp:554
fc::variant
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object's.
Definition: variant.hpp:198
fc::api_connection::receive_notice
void receive_notice(uint64_t callback_id, const variants &args=variants()) const
Definition: api_connection.hpp:270
fc::api_connection::receive_call
variant receive_call(api_id_type api_id, const string &method_name, const variants &args=variants()) const
Definition: api_connection.hpp:260
fc::variant::is_null
bool is_null() const
Definition: variant.cpp:309
fc::api_connection::_max_conversion_depth
const uint32_t _max_conversion_depth
Definition: api_connection.hpp:298
fc::api_connection::receive_callback
variant receive_callback(uint64_t callback_id, const variants &args=variants()) const
Definition: api_connection.hpp:265
fc::rpc::websocket_api_connection::_connection
std::shared_ptr< fc::http::websocket_connection > _connection
Definition: websocket_api.hpp:31
fc::rpc::websocket_api_connection::websocket_api_connection
websocket_api_connection(const std::shared_ptr< fc::http::websocket_connection > &c, uint32_t max_conversion_depth)
Definition: websocket_api.cpp:11
fc::http::reply
Definition: connection.hpp:23
fc::rpc::state::local_call
variant local_call(const string &method_name, const variants &args)
Definition: state.cpp:21
fc::optional
provides stack-based nullable value similar to boost::optional
Definition: optional.hpp:20
fc::api_connection
Definition: api_connection.hpp:240
fc::rpc::response::error
optional< error_object > error
Definition: state.hpp:34
fc::rpc::error_object
Definition: state.hpp:15
elog
#define elog(FORMAT,...)
Definition: logger.hpp:129