BitShares-Core  5.0.0
BitShares blockchain implementation and command-line interface 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
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:638
const auto response
virtual variant send_call(api_id_type api_id, string method_name, variants args=variants()) override
T as(uint32_t max_depth) const
Definition: variant.hpp:336
variants params
Definition: state.hpp:11
void on_response(const variant &message)
void receive_notice(uint64_t callback_id, const variants &args=variants()) const
An order-perserving dictionary of variant&#39;s.
bool is_array() const
Definition: variant.cpp:368
#define elog(FORMAT,...)
Definition: logger.hpp:129
variant local_call(const string &method_name, const variants &args)
Definition: state.cpp:21
microseconds milliseconds(int64_t s)
Definition: time.hpp:35
variant wait_for_response(const variant &request_id)
Definition: state.cpp:52
variant receive_call(api_id_type api_id, const string &method_name, const variants &args=variants()) const
bool is_null() const
Definition: variant.cpp:309
Used to generate a useful error report when an exception is thrown.At each level in the stack where t...
Definition: exception.hpp:56
std::vector< variant > variants
Definition: variant.hpp:170
bool valid() const
Definition: optional.hpp:186
response on_message(const std::string &message)
uint64_t as_uint64() const
Definition: variant.cpp:398
#define wlog(FORMAT,...)
Definition: logger.hpp:123
optional< fc::variant > result
Definition: state.hpp:33
const uint32_t _max_conversion_depth
void on_unhandled(const std::function< variant(const string &, const variants &)> &unhandled)
Definition: state.cpp:64
provides stack-based nullable value similar to boost::optional
Definition: optional.hpp:20
variant_object & get_object()
Definition: variant.cpp:554
virtual void send_notice(uint64_t callback_id, variants args=variants()) override
request start_remote_call(const string &method_name, variants args)
Definition: state.cpp:46
virtual variant send_callback(uint64_t callback_id, variants args=variants()) override
websocket_api_connection(const std::shared_ptr< fc::http::websocket_connection > &c, uint32_t max_conversion_depth)
response on_request(const variant &message)
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
optional< variant > id
Definition: state.hpp:9
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object&#39;s.
Definition: variant.hpp:198
fc::signal< void()> closed
bool is_object() const
Definition: variant.cpp:363
optional< std::string > jsonrpc
Definition: state.hpp:32
optional< std::string > jsonrpc
Definition: state.hpp:12
void add_method(const std::string &name, method m)
Definition: state.cpp:11
uint32_t api_id_type
Definition: api.hpp:122
variant receive_callback(uint64_t callback_id, const variants &args=variants()) const
Definition: api.hpp:15
std::shared_ptr< fc::http::websocket_connection > _connection
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
optional< error_object > error
Definition: state.hpp:34
std::string body_as_string
Definition: connection.hpp:39
optional< variant > id
Definition: state.hpp:31
static time_point now()
Definition: time.cpp:13
void handle_reply(const response &response)
Definition: state.cpp:30
bool contains(const char *key) const
std::string method
Definition: state.hpp:10