1
2#include "XmlRpcServer.h"
3#include "XmlRpcServerConnection.h"
4#include "XmlRpcServerMethod.h"
5#include "XmlRpcSocket.h"
6#include "XmlRpcUtil.h"
7#include "XmlRpcException.h"
8
9
10using namespace XmlRpc;
11
12
13XmlRpcServer::XmlRpcServer()
14{
15  _introspectionEnabled = false;
16  _listMethods = 0;
17  _methodHelp = 0;
18}
19
20
21XmlRpcServer::~XmlRpcServer()
22{
23  this->shutdown();
24  _methods.clear();
25  delete _listMethods;
26  delete _methodHelp;
27}
28
29
30// Add a command to the RPC server
31void
32XmlRpcServer::addMethod(XmlRpcServerMethod* method)
33{
34  _methods[method->name()] = method;
35}
36
37// Remove a command from the RPC server
38void
39XmlRpcServer::removeMethod(XmlRpcServerMethod* method)
40{
41  MethodMap::iterator i = _methods.find(method->name());
42  if (i != _methods.end())
43    _methods.erase(i);
44}
45
46// Remove a command from the RPC server by name
47void
48XmlRpcServer::removeMethod(const std::string& methodName)
49{
50  MethodMap::iterator i = _methods.find(methodName);
51  if (i != _methods.end())
52    _methods.erase(i);
53}
54
55
56// Look up a method by name
57XmlRpcServerMethod*
58XmlRpcServer::findMethod(const std::string& name) const
59{
60  MethodMap::const_iterator i = _methods.find(name);
61  if (i == _methods.end())
62    return 0;
63  return i->second;
64}
65
66
67// Create a socket, bind to the specified port, and
68// set it in listen mode to make it available for clients.
69bool
70XmlRpcServer::bindAndListen(int port, int backlog /*= 5*/)
71{
72  int fd = XmlRpcSocket::socket();
73  if (fd < 0)
74  {
75    XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str());
76    return false;
77  }
78
79  this->setfd(fd);
80
81  // Don't block on reads/writes
82  if ( ! XmlRpcSocket::setNonBlocking(fd))
83  {
84    this->close();
85    XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
86    return false;
87  }
88
89  // Allow this port to be re-bound immediately so server re-starts are not delayed
90  if ( ! XmlRpcSocket::setReuseAddr(fd))
91  {
92    this->close();
93    XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set SO_REUSEADDR socket option (%s).", XmlRpcSocket::getErrorMsg().c_str());
94    return false;
95  }
96
97  // Bind to the specified port on the default interface
98  if ( ! XmlRpcSocket::bind(fd, port))
99  {
100    this->close();
101    XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not bind to specified port (%s).", XmlRpcSocket::getErrorMsg().c_str());
102    return false;
103  }
104
105  // Set in listening mode
106  if ( ! XmlRpcSocket::listen(fd, backlog))
107  {
108    this->close();
109    XmlRpcUtil::error("XmlRpcServer::bindAndListen: Could not set socket in listening mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
110    return false;
111  }
112
113  XmlRpcUtil::log(2, "XmlRpcServer::bindAndListen: server listening on port %d fd %d", port, fd);
114
115  // Notify the dispatcher to listen on this source when we are in work()
116  _disp.addSource(this, XmlRpcDispatch::ReadableEvent);
117
118  return true;
119}
120
121
122// Process client requests for the specified time
123void
124XmlRpcServer::work(double msTime)
125{
126  XmlRpcUtil::log(2, "XmlRpcServer::work: waiting for a connection");
127  _disp.work(msTime);
128}
129
130
131
132// Handle input on the server socket by accepting the connection
133// and reading the rpc request.
134unsigned
135XmlRpcServer::handleEvent(unsigned /* mask */)
136{
137  acceptConnection();
138  return XmlRpcDispatch::ReadableEvent;		// Continue to monitor this fd
139}
140
141
142// Accept a client connection request and create a connection to
143// handle method calls from the client.
144void
145XmlRpcServer::acceptConnection()
146{
147  int s = XmlRpcSocket::accept(this->getfd());
148  XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: socket %d", s);
149  if (s < 0)
150  {
151    //this->close();
152    XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not accept connection (%s).", XmlRpcSocket::getErrorMsg().c_str());
153  }
154  else if ( ! XmlRpcSocket::setNonBlocking(s))
155  {
156    XmlRpcSocket::close(s);
157    XmlRpcUtil::error("XmlRpcServer::acceptConnection: Could not set socket to non-blocking input mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
158  }
159  else  // Notify the dispatcher to listen for input on this source when we are in work()
160  {
161    XmlRpcUtil::log(2, "XmlRpcServer::acceptConnection: creating a connection");
162    _disp.addSource(this->createConnection(s), XmlRpcDispatch::ReadableEvent);
163  }
164}
165
166
167// Create a new connection object for processing requests from a specific client.
168XmlRpcServerConnection*
169XmlRpcServer::createConnection(int s)
170{
171  // Specify that the connection object be deleted when it is closed
172  return new XmlRpcServerConnection(s, this, true);
173}
174
175
176void
177XmlRpcServer::removeConnection(XmlRpcServerConnection* sc)
178{
179  _disp.removeSource(sc);
180}
181
182
183// Stop processing client requests
184void
185XmlRpcServer::exit()
186{
187  _disp.exit();
188}
189
190
191// Close the server socket file descriptor and stop monitoring connections
192void
193XmlRpcServer::shutdown()
194{
195  // This closes and destroys all connections as well as closing this socket
196  _disp.clear();
197}
198
199
200// Introspection support
201static const std::string LIST_METHODS("system.listMethods");
202static const std::string METHOD_HELP("system.methodHelp");
203static const std::string MULTICALL("system.multicall");
204
205
206// List all methods available on a server
207class ListMethods : public XmlRpcServerMethod
208{
209public:
210  ListMethods(XmlRpcServer* s) : XmlRpcServerMethod(LIST_METHODS, s) {}
211
212  void execute(XmlRpcValue& /* params */, XmlRpcValue& result)
213  {
214    _server->listMethods(result);
215  }
216
217  std::string help() { return std::string("List all methods available on a server as an array of strings"); }
218};
219
220
221// Retrieve the help string for a named method
222class MethodHelp : public XmlRpcServerMethod
223{
224public:
225  MethodHelp(XmlRpcServer* s) : XmlRpcServerMethod(METHOD_HELP, s) {}
226
227  void execute(XmlRpcValue& params, XmlRpcValue& result)
228  {
229    if (params[0].getType() != XmlRpcValue::TypeString)
230      throw XmlRpcException(METHOD_HELP + ": Invalid argument type");
231
232    XmlRpcServerMethod* m = _server->findMethod(params[0]);
233    if ( ! m)
234      throw XmlRpcException(METHOD_HELP + ": Unknown method name");
235
236    result = m->help();
237  }
238
239  std::string help() { return std::string("Retrieve the help string for a named method"); }
240};
241
242
243// Specify whether introspection is enabled or not. Default is enabled.
244void
245XmlRpcServer::enableIntrospection(bool enabled)
246{
247  if (_introspectionEnabled == enabled)
248    return;
249
250  _introspectionEnabled = enabled;
251
252  if (enabled)
253  {
254    if ( ! _listMethods)
255    {
256      _listMethods = new ListMethods(this);
257      _methodHelp = new MethodHelp(this);
258    } else {
259      addMethod(_listMethods);
260      addMethod(_methodHelp);
261    }
262  }
263  else
264  {
265    removeMethod(LIST_METHODS);
266    removeMethod(METHOD_HELP);
267  }
268}
269
270
271void
272XmlRpcServer::listMethods(XmlRpcValue& result)
273{
274  int i = 0;
275  result.setSize(_methods.size()+1);
276  for (MethodMap::iterator it=_methods.begin(); it != _methods.end(); ++it)
277    result[i++] = it->first;
278
279  // Multicall support is built into XmlRpcServerConnection
280  result[i] = MULTICALL;
281}
282
283
284
285