OverSim
XmlRpcServer.cc
Go to the documentation of this file.
1 
7 #include "XmlRpcServer.h"
9 #include "XmlRpcServerMethod.h"
10 #include "XmlRpcSocket.h"
11 #include "XmlRpcUtil.h"
12 #include "XmlRpcException.h"
13 #include "XmlRpc.h"
14 
15 
16 using namespace XmlRpc;
17 
18 
19 // Static data
20 const char XmlRpcServer::METHODNAME_TAG[] = "<methodName>";
21 const char XmlRpcServer::PARAMS_TAG[] = "<params>";
22 const char XmlRpcServer::PARAMS_ETAG[] = "</params>";
23 const char XmlRpcServer::PARAM_TAG[] = "<param>";
24 const char XmlRpcServer::PARAM_ETAG[] = "</param>";
25 
26 const std::string XmlRpcServer::METHODNAME = "methodName";
27 const std::string XmlRpcServer::PARAMS = "params";
28 
29 const std::string XmlRpcServer::FAULTCODE = "faultCode";
30 const std::string XmlRpcServer::FAULTSTRING = "faultString";
31 
32 
33 
35 {
36  _introspectionEnabled = false;
37  _listMethods = 0;
38  _methodHelp = 0;
39 }
40 
41 
43 {
44  this->shutdown();
45  _methods.clear();
46  delete _listMethods;
47  delete _methodHelp;
48 }
49 
50 
51 // Add a command to the RPC server
52 void
54 {
55  _methods[method->getName()] = method;
56 }
57 
58 // Remove a command from the RPC server
59 void
61 {
62  MethodMap::iterator i = _methods.find(method->getName());
63  if (i != _methods.end())
64  _methods.erase(i);
65 }
66 
67 // Remove a command from the RPC server by name
68 void
69 XmlRpcServer::removeMethod(const std::string& methodName)
70 {
71  MethodMap::iterator i = _methods.find(methodName);
72  if (i != _methods.end())
73  _methods.erase(i);
74 }
75 
76 
77 // Look up a method by name
79 XmlRpcServer::findMethod(const std::string& name) const
80 {
81  MethodMap::const_iterator i = _methods.find(name);
82  if (i == _methods.end())
83  return 0;
84  return i->second;
85 }
86 
87 
88 // Create a socket, bind to the specified port, and
89 // set it in listen mode to make it available for clients.
90 bool
91 XmlRpcServer::bindAndListen(int port, int backlog /*= 5*/)
92 {
93  int fd = XmlRpcSocket::getSocket();
94  if (fd < 0)
95  {
96  XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str());
97  return false;
98  }
99 
100  this->setfd(fd);
101 
102  // Don't block on reads/writes
103  if ( ! XmlRpcSocket::setNonBlocking(fd))
104  {
105  this->close();
106  XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
107  return false;
108  }
109 
110  // Allow this port to be re-bound immediately so server re-starts are not delayed
111  if ( ! XmlRpcSocket::setReuseAddr(fd))
112  {
113  this->close();
114  XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set SO_REUSEADDR socket option (%s).", XmlRpcSocket::getErrorMsg().c_str());
115  return false;
116  }
117 
118  // Bind to the specified port on the default interface
119  if ( ! XmlRpcSocket::bind(fd, port))
120  {
121  this->close();
122  XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not bind to specified port (%s).", XmlRpcSocket::getErrorMsg().c_str());
123  return false;
124  }
125 
126  // Set in listening mode
127  if ( ! XmlRpcSocket::listen(fd, backlog))
128  {
129  this->close();
130  XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket in listening mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
131  return false;
132  }
133 
134  XmlRpcUtil::log(2, "XmlRpcServer::bindAndListen: server listening on port %d fd %d", port, fd);
135 
136  // Notify the dispatcher to listen on this source when we are in work()
138 
139  return true;
140 }
141 
142 
143 // Get port number that this server is listening on
144 int
146 {
147  return XmlRpcSocket::getPort(getfd());
148 }
149 
150 
151 
152 // Process client requests for the specified time
153 void
154 XmlRpcServer::work(double msTime)
155 {
156  XmlRpcUtil::log(2, "XmlRpcServer::work: waiting for a connection");
157  _disp.work(msTime);
158 }
159 
160 
161 
162 // Handle input on the server socket by accepting the connection
163 // and reading the rpc request.
164 unsigned
166 {
168  return XmlRpcDispatch::ReadableEvent; // Continue to monitor this fd
169 }
170 
171 
172 // Accept a client connection request and create a connection to
173 // handle method calls from the client.
174 void
176 {
177  int s = XmlRpcSocket::accept(this->getfd());
178  XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: socket %d", s);
179  if (s < 0)
180  {
181  //this->close();
182  XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not accept connection (%s).", XmlRpcSocket::getErrorMsg().c_str());
183  }
184  else if ( ! XmlRpcSocket::setNonBlocking(s))
185  {
187  XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
188  }
189  else // Notify the dispatcher to listen for input on this source when we are in work()
190  {
191  XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: creating a connection");
193  if (c) this->dispatchConnection(c);
194  }
195 }
196 
197 
198 // Create a new connection object for processing requests from a specific client.
201 {
202  // Specify that the connection object be deleted when it is closed
203  return new XmlRpcServerConnection(s, this, true);
204 }
205 
206 
207 // Hand off a new connection to a dispatcher
208 void
210 {
212 }
213 
214 
215 // Remove a connection. Called by the connection when it closes down.
216 void
218 {
219  _disp.removeSource(sc);
220 }
221 
222 
223 // Stop processing client requests
224 void
226 {
227  _disp.exit();
228 }
229 
230 
231 // Close the server socket file descriptor and stop monitoring connections
232 void
234 {
235  // This closes and destroys all connections as well as closing this socket
236  _disp.clear();
237 }
238 
239 
240 // Introspection support
241 static const std::string LIST_METHODS("system.listMethods");
242 static const std::string METHOD_HELP("system.methodHelp");
243 static const std::string MULTICALL("system.multicall");
244 
245 
246 // List all methods available on a server
248 {
249 public:
251 
252  void execute(XmlRpcValue& params, XmlRpcValue& result)
253  {
254  _server->listMethods(result);
255  }
256 
257  std::string help() { return std::string("List all methods available on a server as an array of strings"); }
258 };
259 
260 
261 // Retrieve the help string for a named method
263 {
264 public:
266 
267  void execute(XmlRpcValue& params, XmlRpcValue& result)
268  {
269  if (params[0].getType() != XmlRpcValue::TypeString)
270  throw XmlRpcException(METHOD_HELP + ": Invalid argument type");
271 
272  XmlRpcServerMethod* m = _server->findMethod(params[0]);
273  if ( ! m)
274  throw XmlRpcException(METHOD_HELP + ": Unknown method name");
275 
276  result = m->help();
277  }
278 
279  std::string help() { return std::string("Retrieve the help string for a named method"); }
280 };
281 
282 
283 // Specify whether introspection is enabled or not. Default is enabled.
284 void
286 {
287  if (_introspectionEnabled == enabled)
288  return;
289 
290  _introspectionEnabled = enabled;
291 
292  if (enabled)
293  {
294  if ( ! _listMethods)
295  {
296  _listMethods = new ListMethods(this);
297  _methodHelp = new MethodHelp(this);
298  } else {
301  }
302  }
303  else
304  {
307  }
308 }
309 
310 
311 void
313 {
314  int i = 0;
315  result.setSize(_methods.size()+1);
316  for (MethodMap::iterator it=_methods.begin(); it != _methods.end(); ++it)
317  result[i++] = it->first;
318 
319  // Multicall support is built into XmlRpcServer::executeRequest
320  result[i] = MULTICALL;
321 }
322 
323 
324 
325 // Parse the request, run the method, generate a response string.
326 std::string
327 XmlRpcServer::executeRequest(std::string const& request)
328 {
329  XmlRpcValue params, resultValue;
330  std::string methodName = parseRequest(request, params);
331  XmlRpcUtil::log(2, "XmlRpcServer::executeRequest: server calling method '%s'",
332  methodName.c_str());
333 
334  std::string response;
335  try {
336 
337  if ( ! executeMethod(methodName, params, resultValue) &&
338  ! executeMulticall(methodName, params, resultValue))
339  response = generateFaultResponse(methodName + ": unknown method name");
340  else
341  response = generateResponse(resultValue.toXml());
342 
343  } catch (const XmlRpcException& fault) {
344  XmlRpcUtil::log(2, "XmlRpcServer::executeRequest: fault %s.",
345  fault.getMessage().c_str());
346  response = generateFaultResponse(fault.getMessage(), fault.getCode());
347  }
348 
349  return response;
350 }
351 
352 // Parse the method name and the argument values from the request.
353 std::string
354 XmlRpcServer::parseRequest(std::string const& request, XmlRpcValue& params)
355 {
356  int offset = 0; // Number of chars parsed from the request
357 
358  std::string methodName = XmlRpcUtil::parseTag(METHODNAME_TAG, request, &offset);
359 
360  if (methodName.size() > 0 && XmlRpcUtil::findTag(PARAMS_TAG, request, &offset))
361  {
362  int nArgs = 0;
363  while (XmlRpcUtil::nextTagIs(PARAM_TAG, request, &offset)) {
364  params[nArgs++] = XmlRpcValue(request, &offset);
365  (void) XmlRpcUtil::nextTagIs(PARAM_ETAG, request, &offset);
366  }
367 
368  (void) XmlRpcUtil::nextTagIs(PARAMS_ETAG, request, &offset);
369  }
370 
371  return methodName;
372 }
373 
374 // Execute a named method with the specified params.
375 bool
376 XmlRpcServer::executeMethod(const std::string& methodName,
377  XmlRpcValue& params,
378  XmlRpcValue& result)
379 {
380  XmlRpcServerMethod* method = findMethod(methodName);
381 
382  if ( ! method) return false;
383 
384  method->execute(params, result);
385 
386  // Ensure a valid result value
387  if ( ! result.isValid())
388  result = std::string();
389 
390  return true;
391 }
392 
393 // Execute multiple calls and return the results in an array.
394 bool
395 XmlRpcServer::executeMulticall(const std::string& methodName,
396  XmlRpcValue& params,
397  XmlRpcValue& result)
398 {
399  if (methodName != MULTICALL) return false;
400 
401  // There ought to be 1 parameter, an array of structs
402  if (params.size() != 1 || params[0].getType() != XmlRpcValue::TypeArray)
403  throw XmlRpcException(MULTICALL + ": Invalid argument (expected an array)");
404 
405  int nc = params[0].size();
406  result.setSize(nc);
407 
408  for (int i=0; i<nc; ++i) {
409 
410  if ( ! params[0][i].hasMember(METHODNAME) ||
411  ! params[0][i].hasMember(PARAMS)) {
412  result[i][FAULTCODE] = -1;
413  result[i][FAULTSTRING] = MULTICALL +
414  ": Invalid argument (expected a struct with members methodName and params)";
415  continue;
416  }
417 
418  const std::string& methodName = params[0][i][METHODNAME];
419  XmlRpcValue& methodParams = params[0][i][PARAMS];
420 
421  XmlRpcValue resultValue;
422  resultValue.setSize(1);
423  try {
424  if ( ! executeMethod(methodName, methodParams, resultValue[0]) &&
425  ! executeMulticall(methodName, params, resultValue[0]))
426  {
427  result[i][FAULTCODE] = -1;
428  result[i][FAULTSTRING] = methodName + ": unknown method name";
429  }
430  else
431  result[i] = resultValue;
432 
433  } catch (const XmlRpcException& fault) {
434  result[i][FAULTCODE] = fault.getCode();
435  result[i][FAULTSTRING] = fault.getMessage();
436  }
437  }
438 
439  return true;
440 }
441 
442 
443 // Create a response from results xml
444 std::string
445 XmlRpcServer::generateResponse(std::string const& resultXml)
446 {
447  const char RESPONSE_1[] =
448  "<?xml version=\"1.0\"?>\r\n"
449  "<methodResponse><params><param>\r\n\t";
450  const char RESPONSE_2[] =
451  "\r\n</param></params></methodResponse>\r\n";
452 
453  std::string body = RESPONSE_1 + resultXml + RESPONSE_2;
454  std::string header = generateHeader(body);
455  std::string response = header + body;
456 
457  XmlRpcUtil::log(5, "XmlRpcServer::generateResponse:\n%s\n", response.c_str());
458  return response;
459 }
460 
461 
462 // Prepend http headers
463 std::string
464 XmlRpcServer::generateHeader(std::string const& body)
465 {
466  std::string header =
467  "HTTP/1.1 200 OK\r\n"
468  "Server: ";
469  header += XMLRPC_VERSION;
470  header += "\r\n"
471  "Content-Type: text/xml\r\n"
472  "Content-length: ";
473 
474  char buffLen[40];
475  sprintf(buffLen,"%zu\r\n\r\n", body.size());
476 
477  return header + buffLen;
478 }
479 
480 
481 std::string
482 XmlRpcServer::generateFaultResponse(std::string const& errorMsg, int errorCode)
483 {
484  const char RESPONSE_1[] =
485  "<?xml version=\"1.0\"?>\r\n"
486  "<methodResponse><fault>\r\n\t";
487  const char RESPONSE_2[] =
488  "\r\n</fault></methodResponse>\r\n";
489 
490  XmlRpcValue faultStruct;
491  faultStruct[FAULTCODE] = errorCode;
492  faultStruct[FAULTSTRING] = errorMsg;
493  std::string body = RESPONSE_1 + faultStruct.toXml() + RESPONSE_2;
494  std::string header = generateHeader(body);
495 
496  return header + body;
497 }
498