MIRA
JSONRPCBackend.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2012 by
3  * MetraLabs GmbH (MLAB), GERMANY
4  * and
5  * Neuroinformatics and Cognitive Robotics Labs (NICR) at TU Ilmenau, GERMANY
6  * All rights reserved.
7  *
8  * Contact: info@mira-project.org
9  *
10  * Commercial Usage:
11  * Licensees holding valid commercial licenses may use this file in
12  * accordance with the commercial license agreement provided with the
13  * software or, alternatively, in accordance with the terms contained in
14  * a written agreement between you and MLAB or NICR.
15  *
16  * GNU General Public License Usage:
17  * Alternatively, this file may be used under the terms of the GNU
18  * General Public License version 3.0 as published by the Free Software
19  * Foundation and appearing in the file LICENSE.GPL3 included in the
20  * packaging of this file. Please review the following information to
21  * ensure the GNU General Public License version 3.0 requirements will be
22  * met: http://www.gnu.org/copyleft/gpl.html.
23  * Alternatively you may (at your option) use any later version of the GNU
24  * General Public License if such license has been publicly approved by
25  * MLAB and NICR (or its successors, if any).
26  *
27  * IN NO EVENT SHALL "MLAB" OR "NICR" BE LIABLE TO ANY PARTY FOR DIRECT,
28  * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
29  * THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF "MLAB" OR
30  * "NICR" HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  * "MLAB" AND "NICR" SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING,
33  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
34  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
35  * ON AN "AS IS" BASIS, AND "MLAB" AND "NICR" HAVE NO OBLIGATION TO
36  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR MODIFICATIONS.
37  */
38 
47 #ifndef _MIRA_JSONRPCBACKEND_H_
48 #define _MIRA_JSONRPCBACKEND_H_
49 
50 #ifndef Q_MOC_RUN
51 #include <boost/thread/future.hpp>
52 #include <boost/algorithm/string.hpp>
53 #endif
54 
56 #include <rpc/RPCError.h>
57 #include <rpc/JSONRPCResponse.h>
58 #include <utils/ToString.h>
59 
60 #include <thread/Atomic.h>
61 
62 namespace mira {
63 
65 
67 
68 template <typename R>
69 inline bool jsonRPChasExceptionHelper(boost::promise<R>& promise, json::Value& value)
70 {
71  auto it = value.get_obj().find("error");
72  if(it!=value.get_obj().end()) {
73  auto& obj = it->second;
74  auto messageit = obj.get_obj().find("message");
75  if(messageit==obj.get_obj().end()) {
76  promise.set_exception(boost::copy_exception(
77  XRPC("RPC client response has invalid error format: message missing")));
78  return true;
79  }
80 
81  XRPC ex(messageit->second.get_str());
82 
83  auto stackit = obj.get_obj().find("callstack");
84  if(stackit==obj.get_obj().end()) {
85  promise.set_exception(boost::copy_exception(ex));
86  return true;
87  }
88 
89  auto threadit = obj.get_obj().find("thread");
90  if(threadit==obj.get_obj().end()) {
91  ex.addInfo("RPC client response has invalid error format: callstack without thread id");
92  }
93 
94  CallStack stack;
95  ThreadID thread;
96 
97  try {
98  JSONDeserializer js(stackit->second);
99  js.deserialize(stack);
100  }
101  catch(XIO&) {
102  ex.addInfo("RPC client response has invalid error format: callstack unreadable");
103  // do not expect remaining entries to be readable
104  promise.set_exception(boost::copy_exception(ex));
105  return true;
106  }
107 
108  if(threadit!=obj.get_obj().end()) {
109  try {
110  thread = threadit->second.get_uint64();
111  }
112  catch(std::runtime_error&) {
113  ex.addInfo("RPC client response has invalid error format: thread id unreadable");
114  }
115  }
116 
117  // at least the stack was read, thread might be uninitialized, but the exception informs about it
118  ex.addExternalStackInfo<XRPC>(stack, thread);
119 
120  // try to deserialize and add the original exception
121  auto excit = obj.get_obj().find("exception");
122  if(excit!=obj.get_obj().end()) {
123  try {
124  SerializableException* origEx;
125  JSONDeserializer js(excit->second);
126  js.deserialize(origEx);
127  ex.setOrigException(origEx);
128  }
129  catch(...) {} // ignore errors, it could e.g. be an unknown exception type
130  }
131 
132  promise.set_exception(boost::copy_exception(ex));
133  return true;
134  }
135 
136  it = value.get_obj().find("result");
137  if(it==value.get_obj().end()) {
138  promise.set_exception(boost::copy_exception(
139  XRPC("RPC client response has invalid result format")));
140  return true;
141  }
142  return false;
143 }
144 
145 template <typename R>
146 inline void jsonRPCreturnHelper(boost::promise<R>& promise, json::Value& value)
147 {
148  if (jsonRPChasExceptionHelper(promise, value))
149  return;
150  auto it = value.get_obj().find("result");
151  R ret;
152  JSONDeserializer d(it->second);
153  d.deserialize(ret);
154  promise.set_value(std::move(ret));
155 }
156 
157 template <>
158 inline void jsonRPCreturnHelper(boost::promise<JSONRPCResponse>& promise, json::Value& value)
159 {
160  promise.set_value(JSONRPCResponse(value));
161 }
162 
163 template <>
164 inline void jsonRPCreturnHelper(boost::promise<void>& promise, json::Value& value)
165 {
166  if (jsonRPChasExceptionHelper(promise, value))
167  return;
168  promise.set_value();
169 }
171 
172 
178 {
179 public:
180 
185  {
186  ParseError = -32700,
187  InvalidRequest = -32600,
188  MethodNotFound = -32601,
189  InvalidParams = -32602,
190  InternalError = -32603,
191  ServerError = -32099,
192  };
193 
195 
206  {
207  public:
208 
210  mValue(value) {}
211 
217  void getHeader(std::string& oCallId) const
218  {
219  auto it = mValue->get_obj().find("id");
220  if(it== mValue->get_obj().end())
221  MIRA_THROW(XRPC, "Server response is missing an 'id'. Response = "
222  << json::write(*mValue));
223  oCallId = json::write(it->second);
224  }
225 
233  template<typename R>
234  void getReturn(boost::promise<R>& promise) const
235  {
236  jsonRPCreturnHelper(promise, *mValue);
237  }
238 
239  private:
240  json::Value* mValue;
241  };
242 
252  {
253  public:
254 
256  mValue(value),
257  mParams(NULL)
258  {
259  *mValue = json::Object();
260  mValue->get_obj()["jsonrpc"] = json::Value("2.0");
261  }
262 
267  std::string generateCallID() {
268  static uint32 sID = 0;
269  json::Value v(std::string("JSONCR") + toString(atomic::inc(&sID)));
270  return json::write(v, false);
271  }
272 
279  void setHeader(const std::string& callId, const std::string& service,
280  const RPCSignature& signature)
281  {
282  std::string method = service + "." + signature.name;
283  mValue->get_obj()["method"] = json::Value(method);
284  json::Value id;
285  json::read(callId, id);
286  mValue->get_obj()["id"] = id;
287  }
288 
294  template <typename P>
295  void setParameter(const P& param)
296  {
297  // if no params inserted yet do so
298  if (mParams == NULL)
299  {
300  mValue->get_obj()["params"] = json::Array();
301  mParams = &mValue->get_obj()["params"];
302  }
303  mParams->get_array().push_back(mOut.serialize(param));
304  }
305 
306  private:
307  json::Value* mValue;
308  json::Value* mParams;
309  JSONSerializer mOut;
310  };
311 
321  {
322  public:
323 
325  mValue(value)
326  {
327  *mValue = json::Object();
328  mValue->get_obj()["jsonrpc"] = json::Value("2.0");
329  }
330 
338  void setHeader(const std::string& callId)
339  {
340  json::Value id;
341  json::read(callId, id);
342  mValue->get_obj()["id"] = id;
343  }
344 
351  void returnException(RPCError reason, const std::string& message)
352  {
353  json::Object o;
354  ErrorCode code;
355  switch (reason)
356  {
357  case RPC_INVALID_REQUEST : code = InvalidRequest; break;
358  case RPC_METHOD_NOT_FOUND : code = MethodNotFound; break;
359  case RPC_INVALID_PARAMS : code = InvalidParams; break;
360  case RPC_EXCEPTION_IN_CALL : code = InternalError; break;
361  default : code = ServerError; break;
362  }
363  o["code"] = json::Value(code);
364  o["message"] = json::Value(message);
365  mValue->get_obj()["error"] = o;
366  }
367 
374  void returnException(RPCError reason, const std::string& message,
375  const CallStack& callstack)
376  {
377  json::Object o;
378  ErrorCode code;
379  switch (reason)
380  {
381  case RPC_INVALID_REQUEST : code = InvalidRequest; break;
382  case RPC_METHOD_NOT_FOUND : code = MethodNotFound; break;
383  case RPC_INVALID_PARAMS : code = InvalidParams; break;
384  case RPC_EXCEPTION_IN_CALL : code = InternalError; break;
385  default : code = ServerError; break;
386  }
387  o["code"] = json::Value(code);
388  o["message"] = json::Value(message);
389  o["callstack"] = mOut.serialize(callstack);
390  o["thread"] = json::Value((boost::uint64_t)getCurrentThreadID());
391  mValue->get_obj()["error"] = o;
392  }
393 
401  {
402  json::Object o;
403  ErrorCode code;
404  switch (reason)
405  {
406  case RPC_INVALID_REQUEST : code = InvalidRequest; break;
407  case RPC_METHOD_NOT_FOUND : code = MethodNotFound; break;
408  case RPC_INVALID_PARAMS : code = InvalidParams; break;
409  case RPC_EXCEPTION_IN_CALL : code = InternalError; break;
410  default : code = ServerError; break;
411  }
412  o["code"] = json::Value(code);
413  o["message"] = json::Value(ex.what());
414  o["callstack"] = mOut.serialize(ex.callStack());
415  o["thread"] = json::Value((boost::uint64_t)getCurrentThreadID());
416  o["exception"] = mOut.serialize(&ex);
417  mValue->get_obj()["error"] = o;
418  }
419 
425  template<typename R>
426  void returnResult(const R& res)
427  {
428  mValue->get_obj()["result"] = mOut.serialize(res);
429  }
430 
436  void returnVoid()
437  {
438  mValue->get_obj()["result"] = json::Value();
439  }
440 
441  private:
442  json::Value* mValue;
443  JSONSerializer mOut;
444  };
445 
456  {
457  public:
458 
459  ServerRequest(const json::Value* value) :
460  mValue(value),
461  mParams(NULL),
462  mIndex(0)
463  {}
464 
465  public:
469  static std::string getCallId(const json::Value* request)
470  {
471  auto it = request->get_obj().find("id");
472  if(it==request->get_obj().end())
473  MIRA_THROW(XRPC,"The JSON request is missing an 'id'. Request = "
474  << json::write(*request));
475  return json::write(it->second);
476  }
477 
483  static void getServiceMethod(const json::Value* request, std::string& oService, std::string& oMethod)
484  {
485  auto it = request->get_obj().find("method");
486  if(it==request->get_obj().end())
487  MIRA_THROW(XRPC,"The JSON request is missing a 'method'. Request = "
488  << json::write(*request));
489 
490  std::string method = it->second.get_str();
491  if(method.empty())
492  MIRA_THROW(XRPC,"The JSON request has an empty 'method'. Request = "
493  << json::write(*request));
494 
495  std::vector<std::string> strs;
496  boost::split(strs, method, boost::is_from_range('.','.'));
497  oService = *strs.begin();
498  oMethod = *strs.rbegin();
499  }
500 
509  void getHeader(std::string& oCallId, std::string& oService)
510  {
511  oCallId = getCallId(mValue);
512  getServiceMethod(mValue, oService, mSignature.name);
513 
514  auto it = mValue->get_obj().find("params");
515  // method has no parameters
516  if(it==mValue->get_obj().end())
517  return;
518  mParams = &it->second;
519 
520  for(std::size_t i=0;i<mParams->get_array().size(); ++i)
521  {
522  const json::Value& v = mParams->get_array()[i];
523  std::string type;
524 
525  switch(v.type()) {
526  case json_spirit::obj_type: type="object"; break;
527  case json_spirit::array_type: type="array"; break;
528  case json_spirit::str_type: type="string"; break;
529  case json_spirit::bool_type: type="bool"; break;
530  case json_spirit::int_type: type="int"; break;
531  case json_spirit::real_type: type="float"; break;
532  default: break;
533  }
534 
535  mSignature.parameterTypes.push_back(type);
536  }
537  }
538 
543  bool checkSignature(const RPCSignature& signature)
544  {
545  return signature.name == mSignature.name &&
546  signature.parameterTypes.size() == mSignature.parameterTypes.size();
547  }
548 
554  {
555  return mSignature;
556  }
557 
562  template <typename P>
563  void getParameter(P& oParam)
564  {
565  try
566  {
567  if (mParams != NULL)
568  {
569  assert(mIndex < (uint32)mParams->get_array().size());
570  const json::Value& v = mParams->get_array()[mIndex++];
571  JSONDeserializer d(v);
572  // no need to do type check here, since it is done by JSONDeserializer
573  d.deserialize(oParam);
574  }
575  }
576  catch(std::exception& ex)
577  {
578  MIRA_THROW(XRPC,"Parameter must be a valid object of type '" << typeName<P>()
579  << "'. " << ex.what());
580  }
581  catch(...)
582  {
583  MIRA_THROW(XRPC,"Parameter must be a valid object of type '" << typeName<P>() << "'");
584  }
585  }
586 
587  private:
588  const json::Value* mValue;
589  const json::Value* mParams;
590  uint32 mIndex;
591  RPCSignature mSignature;
592  };
593 
594 };
596 
597 } // namespace
598 
599 #endif
Serializer for serializing objects in JSON format.
Definition: JSONSerializer.h:95
void write(const Value &value, std::ostream &ioStream, bool formatted=false, int precision=-1)
Writes a json::Value into a given stream using the JSON format.
void deserialize(T &value)
Definition: JSONSerializer.h:436
Definition: JSONRPCBackend.h:188
std::string generateCallID()
Generates a unique call ID for a client request of this backend.
Definition: JSONRPCBackend.h:267
void setHeader(const std::string &callId)
Write the response header consisting of the ID of the call.
Definition: JSONRPCBackend.h:338
Invalid parameters were specified for the method.
Definition: RPCError.h:68
Provides JSON client and server side requests and responses.
Definition: JSONRPCBackend.h:177
Definition: JSONRPCBackend.h:191
ServerRequest(const json::Value *value)
Definition: JSONRPCBackend.h:459
json_spirit::mArray Array
A representation of an array (vector) in JSON.
Definition: JSON.h:188
json::Value ContainerType
Definition: JSONRPCBackend.h:194
specialize cv::DataType for our ImgPixel and inherit from cv::DataType<Vec>
Definition: IOService.h:67
Error codes for reasons of errors/exceptions while processing an rpc call.
Definition: JSONRPCBackend.h:189
void getReturn(boost::promise< R > &promise) const
Obtain return value from response and set it using promise.set_value() or set exception using promise...
Definition: JSONRPCBackend.h:234
void read(const std::string &s, Value &oValue)
Read a json::Value from a string that contains JSON format.
uint32_t uint32
Definition: Types.h:64
Contains toString functions for converting data types to strings.
uint32 ThreadID
Platform independent thread ID.
Definition: ThreadID.h:64
static void getServiceMethod(const json::Value *request, std::string &oService, std::string &oMethod)
Read service name and the method name from the given request.
Definition: JSONRPCBackend.h:483
JSON client-side response.
Definition: JSONRPCBackend.h:205
ClientResponse(json::Value *value)
Definition: JSONRPCBackend.h:209
void serialize(const std::string &name, const T &value, const std::string &comment="")
Serializes the specified object value under the given name.
Definition: Serializer.h:204
#define MIRA_THROW(ex, msg)
Macro for throwing an exception.
Definition: Exception.h:78
ClientRequest(json::Value *value)
Definition: JSONRPCBackend.h:255
std::string toString(const T &value, int precision=-1)
Converts any data type to string (the data type must support the stream << operator).
Definition: ToString.h:158
void setParameter(const P &param)
Write the value of the next parameter to the request.
Definition: JSONRPCBackend.h:295
ServerResponse(json::Value *value)
Definition: JSONRPCBackend.h:324
Deserializer for serializing objects from JSON format.
Definition: JSONSerializer.h:406
ErrorCode
Error codes as defined by the JSON RPC standard.
Definition: JSONRPCBackend.h:184
An exception that is thrown by the RPCServer if an RPC call fails.
Definition: RPCError.h:76
An exception has occurred within the method that was called.
Definition: RPCError.h:69
Definition: JSONRPCBackend.h:187
void getParameter(P &oParam)
Read and deserialize the next parameter from the request.
Definition: JSONRPCBackend.h:563
PropertyHint type(const std::string &t)
Sets the attribute "type" to the specified value.
Definition: PropertyHint.h:296
ThreadID getCurrentThreadID()
Get the ID of the current thread.
Definition: ThreadID.h:72
const RPCSignature & getSignature()
Return the signature, that was read from the request header in the previous getHeader() call above...
Definition: JSONRPCBackend.h:553
JSON client-side request.
Definition: JSONRPCBackend.h:251
bool checkSignature(const RPCSignature &signature)
Check, if the passed signature is compatible with the signature that was read from the request header...
Definition: JSONRPCBackend.h:543
const CallStack & callStack() const
Returns the state of the callstack at the moment when the exception was thrown.
Definition: Exception.h:258
Special response type for JSON RPC calls.
std::string name
The method&#39;s name.
Definition: RPCSignature.h:130
void returnException(RPCError reason, SerializableException &ex)
Write exception as result an RPC call.
Definition: JSONRPCBackend.h:400
json_spirit::mObject Object
A representation of an object (class, struct) in JSON.
Definition: JSON.h:181
RPCError
enumeration of possible reasons for errors/exceptions while performing an RPC call ...
Definition: RPCError.h:64
Definition: JSONRPCBackend.h:190
void getHeader(std::string &oCallId) const
Read the response header (i.e.
Definition: JSONRPCBackend.h:217
json_spirit::mValue Value
A value is an abstract description of data in JSON (underlying data can either be one of the JSON bas...
Definition: JSON.h:174
void returnResult(const R &res)
Write result of an RPC call.
Definition: JSONRPCBackend.h:426
Contains methods for simple atomic operations (such as increment, etc.) that are thread-safe without ...
void setHeader(const std::string &callId, const std::string &service, const RPCSignature &signature)
Write the request header consisting of the call ID, the service name and the signature of the request...
Definition: JSONRPCBackend.h:279
Encapsulates unix call stack functionality.
Definition: CallStack.h:75
JSON server-side response.
Definition: JSONRPCBackend.h:320
JSON server-side request.
Definition: JSONRPCBackend.h:455
void getHeader(std::string &oCallId, std::string &oService)
Read the request header including the call ID, the service name and the signature of the method...
Definition: JSONRPCBackend.h:509
virtual const char * what() const MIRA_NOEXCEPT_OR_NOTHROW
Returns the text of exception containing the information given in MIRA_THROW and MIRA_RETHROW as well...
Requested method was not found.
Definition: RPCError.h:67
void returnException(RPCError reason, const std::string &message)
Write exception as result an RPC call.
Definition: JSONRPCBackend.h:351
Definition: JSONRPCBackend.h:186
Stores the signature of an RPC method including the methods name and its parameter types...
Definition: RPCSignature.h:64
The request is invalid or can not be parsed.
Definition: RPCError.h:66
void returnException(RPCError reason, const std::string &message, const CallStack &callstack)
Write exception as result an RPC call.
Definition: JSONRPCBackend.h:374
uint32 inc(volatile uint32 *var)
Increments the value of the variable pointed to by var and returns its old value. ...
Definition: Atomic.h:82
Definition: Exceptions.h:85
Serializer and Deserializer for JSON format.
static std::string getCallId(const json::Value *request)
Read call ID from the given request.
Definition: JSONRPCBackend.h:469
void returnVoid()
Write successful end of an RPC call.
Definition: JSONRPCBackend.h:436
ParameterTypes parameterTypes
Vector of the type of each parameter.
Definition: RPCSignature.h:136