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  JSONSerializer js;
390  o["callstack"] = js.serialize(callstack);
391  o["thread"] = json::Value((boost::uint64_t)getCurrentThreadID());
392  mValue->get_obj()["error"] = o;
393  }
394 
402  {
403  json::Object o;
404  ErrorCode code;
405  switch (reason)
406  {
407  case RPC_INVALID_REQUEST : code = InvalidRequest; break;
408  case RPC_METHOD_NOT_FOUND : code = MethodNotFound; break;
409  case RPC_INVALID_PARAMS : code = InvalidParams; break;
410  case RPC_EXCEPTION_IN_CALL : code = InternalError; break;
411  default : code = ServerError; break;
412  }
413  o["code"] = json::Value(code);
414  o["message"] = json::Value(ex.what());
415  JSONSerializer js;
416  o["callstack"] = js.serialize(ex.callStack());
417  o["thread"] = json::Value((boost::uint64_t)getCurrentThreadID());
418  o["exception"] = js.serialize(&ex);
419  mValue->get_obj()["error"] = o;
420  }
421 
427  template<typename R>
428  void returnResult(const R& res)
429  {
430  mValue->get_obj()["result"] = mOut.serialize(res);
431  }
432 
438  void returnVoid()
439  {
440  mValue->get_obj()["result"] = json::Value();
441  }
442 
443  private:
444  json::Value* mValue;
445  JSONSerializer mOut;
446  };
447 
458  {
459  public:
460 
461  ServerRequest(const json::Value* value) :
462  mValue(value),
463  mParams(NULL),
464  mIndex(0)
465  {}
466 
467  public:
471  static std::string getCallId(const json::Value* request)
472  {
473  auto it = request->get_obj().find("id");
474  if(it==request->get_obj().end())
475  MIRA_THROW(XRPC,"The JSON request is missing an 'id'. Request = "
476  << json::write(*request));
477  return json::write(it->second);
478  }
479 
485  static void getServiceMethod(const json::Value* request, std::string& oService, std::string& oMethod)
486  {
487  auto it = request->get_obj().find("method");
488  if(it==request->get_obj().end())
489  MIRA_THROW(XRPC,"The JSON request is missing a 'method'. Request = "
490  << json::write(*request));
491 
492  std::string method = it->second.get_str();
493  if(method.empty())
494  MIRA_THROW(XRPC,"The JSON request has an empty 'method'. Request = "
495  << json::write(*request));
496 
497  std::vector<std::string> strs;
498  boost::split(strs, method, boost::is_from_range('.','.'));
499  oService = *strs.begin();
500  oMethod = *strs.rbegin();
501  }
502 
511  void getHeader(std::string& oCallId, std::string& oService)
512  {
513  oCallId = getCallId(mValue);
514  getServiceMethod(mValue, oService, mSignature.name);
515 
516  auto it = mValue->get_obj().find("params");
517  // method has no parameters
518  if(it==mValue->get_obj().end())
519  return;
520  mParams = &it->second;
521 
522  for(std::size_t i=0;i<mParams->get_array().size(); ++i)
523  {
524  const json::Value& v = mParams->get_array()[i];
525  std::string type;
526 
527  switch(v.type()) {
528  case json_spirit::obj_type: type="object"; break;
529  case json_spirit::array_type: type="array"; break;
530  case json_spirit::str_type: type="string"; break;
531  case json_spirit::bool_type: type="bool"; break;
532  case json_spirit::int_type: type="int"; break;
533  case json_spirit::real_type: type="float"; break;
534  default: break;
535  }
536 
537  mSignature.parameterTypes.push_back(type);
538  }
539  }
540 
545  bool checkSignature(const RPCSignature& signature)
546  {
547  return signature.name == mSignature.name &&
548  signature.parameterTypes.size() == mSignature.parameterTypes.size();
549  }
550 
556  {
557  return mSignature;
558  }
559 
564  template <typename P>
565  void getParameter(P& oParam)
566  {
567  try
568  {
569  if (mParams != NULL)
570  {
571  assert(mIndex < (uint32)mParams->get_array().size());
572  const json::Value& v = mParams->get_array()[mIndex++];
573  JSONDeserializer d(v);
574  // no need to do type check here, since it is done by JSONDeserializer
575  d.deserialize(oParam);
576  }
577  }
578  catch(std::exception& ex)
579  {
580  MIRA_THROW(XRPC,"Parameter must be of type '" << typeName<P>() << "'. " << ex.what());
581  }
582  catch(...)
583  {
584  MIRA_THROW(XRPC,"Parameter must be of type '" << typeName<P>() << "'");
585  }
586  }
587 
588  private:
589  const json::Value* mValue;
590  const json::Value* mParams;
591  uint32 mIndex;
592  RPCSignature mSignature;
593  };
594 
595 };
597 
598 } // namespace
599 
600 #endif
Serializer for serializing objects in JSON format.
Definition: JSONSerializer.h:93
void deserialize(T &value)
Definition: JSONSerializer.h:427
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:461
json_spirit::mArray Array
A representation of an array (vector) in JSON.
Definition: JSON.h:190
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
Contains toString and fromString functions for converting data types to strings and the other way rou...
uint32 ThreadID
Platform independent thread ID.
Definition: ThreadID.h:68
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:485
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:81
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:256
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
MIRA_BASE_EXPORT 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.
Deserializer for serializing objects from JSON format.
Definition: JSONSerializer.h:400
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 deserializes the next parameter from the request.
Definition: JSONRPCBackend.h:565
PropertyHint type(const std::string &t)
Sets the attribute "type" to the specified value.
Definition: PropertyHint.h:295
const RPCSignature & getSignature()
Return the signature, that was read from the request header in the previous getHeader() call above...
Definition: JSONRPCBackend.h:555
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:545
MIRA_BASE_EXPORT void read(const std::string &s, Value &oValue)
Read a json::Value from a string that contains JSON format.
const CallStack & callStack() const
Returns the state of the callstack at the moment when the exception was thrown.
Definition: Exception.h:252
Special response type for JSON RPC calls.
std::string name
The method&#39;s name.
Definition: RPCSignature.h:134
void returnException(RPCError reason, SerializableException &ex)
Write exception as result an RPC call.
Definition: JSONRPCBackend.h:401
json_spirit::mObject Object
A representation of an object (class, struct) in JSON.
Definition: JSON.h:183
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:176
void returnResult(const R &res)
Write result of an RPC call.
Definition: JSONRPCBackend.h:428
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:86
JSON server-side response.
Definition: JSONRPCBackend.h:320
JSON server-side request.
Definition: JSONRPCBackend.h:457
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:511
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:68
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:471
void returnVoid()
Write successful end of an RPC call.
Definition: JSONRPCBackend.h:438
ParameterTypes parameterTypes
Vector of the type of each parameter.
Definition: RPCSignature.h:140