XmlRpcClient.cpp
Go to the documentation of this file.
1 
3 
5 #include "xmlrpcpp/XmlRpc.h"
6 
7 #include <stdio.h>
8 #include <stdlib.h>
9 #ifndef _WINDOWS
10  # include <strings.h>
11 #endif
12 #include <string.h>
13 
14 
15 using namespace XmlRpc;
16 
17 // Static data
18 const char XmlRpcClient::REQUEST_BEGIN[] =
19  "<?xml version=\"1.0\"?>\r\n"
20  "<methodCall><methodName>";
21 const char XmlRpcClient::REQUEST_END_METHODNAME[] = "</methodName>\r\n";
22 const char XmlRpcClient::PARAMS_TAG[] = "<params>";
23 const char XmlRpcClient::PARAMS_ETAG[] = "</params>";
24 const char XmlRpcClient::PARAM_TAG[] = "<param>";
25 const char XmlRpcClient::PARAM_ETAG[] = "</param>";
26 const char XmlRpcClient::REQUEST_END[] = "</methodCall>\r\n";
27 const char XmlRpcClient::METHODRESPONSE_TAG[] = "<methodResponse>";
28 const char XmlRpcClient::FAULT_TAG[] = "<fault>";
29 
30 
31 
32 XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri/*=0*/)
33 {
34  XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port);
35 
36  _host = host;
37  _port = port;
38  if (uri)
39  _uri = uri;
40  else
41  _uri = "/RPC2";
43  _executing = false;
44  _eof = false;
45 
46  // Default to keeping the connection open until an explicit close is done
47  setKeepOpen();
48 }
49 
50 
52 {
53  this->close();
54 }
55 
56 // Close the owned fd
57 void
59 {
60  XmlRpcUtil::log(4, "XmlRpcClient::close: fd %d.", getfd());
62  _disp.exit();
63  _disp.removeSource(this);
65 }
66 
67 
68 // Clear the referenced flag even if exceptions or errors occur.
70  ClearFlagOnExit(bool& flag) : _flag(flag) {}
71  ~ClearFlagOnExit() { _flag = false; }
72  bool& _flag;
73 };
74 
75 // Execute the named procedure on the remote server.
76 // Params should be an array of the arguments for the method.
77 // Returns true if the request was sent and a result received (although the result
78 // might be a fault).
79 bool
80 XmlRpcClient::execute(const char* method, XmlRpcValue const& params, XmlRpcValue& result)
81 {
82  XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s (_connectionState %d).", method, _connectionState);
83 
84  // This is not a thread-safe operation, if you want to do multithreading, use separate
85  // clients for each thread. If you want to protect yourself from multiple threads
86  // accessing the same client, replace this code with a real mutex.
87  if (_executing)
88  return false;
89 
90  _executing = true;
92 
93  _sendAttempts = 0;
94  _isFault = false;
95 
96  if ( ! setupConnection())
97  return false;
98 
99  if ( ! generateRequest(method, params))
100  return false;
101 
102  result.clear();
103  double msTime = -1.0; // Process until exit is called
104  _disp.work(msTime);
105 
106  if (_connectionState != IDLE || ! parseResponse(result))
107  return false;
108 
109  XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s completed.", method);
110  _header = "";
111  _response = "";
112  return true;
113 }
114 
115 // Execute the named procedure on the remote server, non-blocking.
116 // Params should be an array of the arguments for the method.
117 // Returns true if the request was sent and a result received (although the result
118 // might be a fault).
119 bool
120 XmlRpcClient::executeNonBlock(const char* method, XmlRpcValue const& params)
121 {
122  XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s (_connectionState %d).", method, _connectionState);
123 
124  // This is not a thread-safe operation, if you want to do multithreading, use separate
125  // clients for each thread. If you want to protect yourself from multiple threads
126  // accessing the same client, replace this code with a real mutex.
127  if (_executing)
128  return false;
129 
130  _executing = true;
132 
133  _sendAttempts = 0;
134  _isFault = false;
135 
136  if ( ! setupConnection())
137  return false;
138 
139  if ( ! generateRequest(method, params))
140  return false;
141 
142  return true;
143 }
144 
145 bool
147 {
148  result.clear();
149  // Are we done yet?
150  if (_connectionState != IDLE)
151  return false;
152  if (! parseResponse(result))
153  {
154  // Hopefully the caller can determine that parsing failed.
155  }
156  //XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s completed.", method);
157  _response = "";
158  return true;
159 }
160 
161 // XmlRpcSource interface implementation
162 // Handle server responses. Called by the event dispatcher during execute.
163 unsigned
164 XmlRpcClient::handleEvent(unsigned eventType)
165 {
166  if (eventType == XmlRpcDispatch::Exception)
167  {
169  XmlRpcUtil::error("Error in XmlRpcClient::handleEvent: could not connect to server (%s).",
170  XmlRpcSocket::getErrorMsg().c_str());
171  else
172  XmlRpcUtil::error("Error in XmlRpcClient::handleEvent (state %d): %s.",
174  return 0;
175  }
176 
178  if ( ! writeRequest()) return 0;
179 
181  if ( ! readHeader()) return 0;
182 
184  if ( ! readResponse()) return 0;
185 
186  // This should probably always ask for Exception events too
187  return (_connectionState == WRITE_REQUEST)
189 }
190 
191 
192 // Create the socket connection to the server if necessary
193 bool
195 {
196  // If an error occurred last time through, or if the server closed the connection, close our end
198  close();
199 
200  _eof = false;
202  if (! doConnect())
203  return false;
204 
205  // Prepare to write the request
207  _bytesWritten = 0;
208 
209  // Notify the dispatcher to listen on this source (calls handleEvent when the socket is writable)
210  _disp.removeSource(this); // Make sure nothing is left over
212 
213  return true;
214 }
215 
216 
217 // Connect to the xmlrpc server
218 bool
220 {
221  int fd = XmlRpcSocket::socket();
222  if (fd < 0)
223  {
224  XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str());
225  return false;
226  }
227 
228  XmlRpcUtil::log(3, "XmlRpcClient::doConnect: fd %d.", fd);
229  this->setfd(fd);
230 
231  // Don't block on connect/reads/writes
232  if ( ! XmlRpcSocket::setNonBlocking(fd))
233  {
234  this->close();
235  XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not set socket to non-blocking IO mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
236  return false;
237  }
238 
239  if ( ! XmlRpcSocket::connect(fd, _host, _port))
240  {
241  this->close();
242  XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not connect to server (%s).", XmlRpcSocket::getErrorMsg().c_str());
243  return false;
244  }
245 
246  return true;
247 }
248 
249 // Encode the request to call the specified method with the specified parameters into xml
250 bool
251 XmlRpcClient::generateRequest(const char* methodName, XmlRpcValue const& params)
252 {
253  std::string body = REQUEST_BEGIN;
254  body += methodName;
255  body += REQUEST_END_METHODNAME;
256 
257  // If params is an array, each element is a separate parameter
258  if (params.valid()) {
259  body += PARAMS_TAG;
260  if (params.getType() == XmlRpcValue::TypeArray)
261  {
262  for (int i=0; i<params.size(); ++i) {
263  body += PARAM_TAG;
264  body += params[i].toXml();
265  body += PARAM_ETAG;
266  }
267  }
268  else
269  {
270  body += PARAM_TAG;
271  body += params.toXml();
272  body += PARAM_ETAG;
273  }
274 
275  body += PARAMS_ETAG;
276  }
277  body += REQUEST_END;
278 
279  std::string header = generateHeader(body);
280  XmlRpcUtil::log(4, "XmlRpcClient::generateRequest: header is %d bytes, content-length is %d.",
281  header.length(), body.length());
282 
283  _request = header + body;
284  // Limit the size of the request to avoid integer overruns
285  if (_request.length() > size_t(__INT_MAX__)) {
286  XmlRpcUtil::error("XmlRpcClient::generateRequest: request length (%u) exceeds maximum allowed size (%u).",
287  _request.length(), __INT_MAX__);
288  _request.clear();
289  return false;
290  }
291  return true;
292 }
293 
294 // Prepend http headers
295 std::string
296 XmlRpcClient::generateHeader(std::string const& body)
297 {
298  std::string header =
299  "POST " + _uri + " HTTP/1.1\r\n"
300  "User-Agent: ";
301  header += XMLRPC_VERSION;
302  header += "\r\nHost: ";
303  header += _host;
304 
305  char buff[40];
306 #ifdef _MSC_VER
307  sprintf_s(buff,40,":%d\r\n", _port);
308 #else
309  sprintf(buff,":%d\r\n", _port);
310 #endif
311 
312  header += buff;
313  header += "Content-Type: text/xml\r\nContent-length: ";
314 
315 #ifdef _MSC_VER
316  sprintf_s(buff,40,"%d\r\n\r\n", (int)body.size());
317 #else
318  sprintf(buff,"%d\r\n\r\n", (int)body.size());
319 #endif
320 
321  return header + buff;
322 }
323 
324 bool
326 {
327  if (_bytesWritten == 0)
328  XmlRpcUtil::log(5, "XmlRpcClient::writeRequest (attempt %d):\n%s\n", _sendAttempts+1, _request.c_str());
329 
330  // Try to write the request
331  if ( ! XmlRpcSocket::nbWrite(this->getfd(), _request, &_bytesWritten)) {
332  XmlRpcUtil::error("Error in XmlRpcClient::writeRequest: write error (%s).",XmlRpcSocket::getErrorMsg().c_str());
333  return false;
334  }
335 
336  XmlRpcUtil::log(3, "XmlRpcClient::writeRequest: wrote %d of %d bytes.", _bytesWritten, _request.length());
337 
338  // Wait for the result
339  if (_bytesWritten == int(_request.length())) {
340  _header = "";
341  _response = "";
343  }
344  return true;
345 }
346 
347 
348 // Read the header from the response
349 bool
351 {
352  // Read available data
353  if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &_eof) ||
354  (_eof && _header.length() == 0)) {
355 
356  // If we haven't read any data yet and this is a keep-alive connection, the server may
357  // have timed out, so we try one more time.
358  if (getKeepOpen() && _header.length() == 0 && _sendAttempts++ == 0) {
359  XmlRpcUtil::log(4, "XmlRpcClient::readHeader: re-trying connection");
362  _eof = false;
363  return setupConnection();
364  }
365 
366  XmlRpcUtil::error("Error in XmlRpcClient::readHeader: error while reading header (%s) on fd %d.",
367  XmlRpcSocket::getErrorMsg().c_str(), getfd());
368  return false;
369  }
370 
371  XmlRpcUtil::log(4, "XmlRpcClient::readHeader: client has read %d bytes", _header.length());
372 
373  char *hp = (char*)_header.c_str(); // Start of header
374  char *ep = hp + _header.length(); // End of string
375  char *bp = 0; // Start of body
376  char *lp = 0; // Start of content-length value
377 
378  for (char *cp = hp; (bp == 0) && (cp < ep); ++cp) {
379  if ((ep - cp > 16) && (strncasecmp(cp, "Content-length: ", 16) == 0))
380  lp = cp + 16;
381  else if ((ep - cp > 4) && (strncmp(cp, "\r\n\r\n", 4) == 0))
382  bp = cp + 4;
383  else if ((ep - cp > 2) && (strncmp(cp, "\n\n", 2) == 0))
384  bp = cp + 2;
385  }
386 
387  // If we haven't gotten the entire header yet, return (keep reading)
388  if (bp == 0) {
389  if (_eof) // EOF in the middle of a response is an error
390  {
391  XmlRpcUtil::error("Error in XmlRpcClient::readHeader: EOF while reading header");
392  return false; // Close the connection
393  }
394 
395  return true; // Keep reading
396  }
397 
398  // Decode content length
399  if (lp == 0) {
400  XmlRpcUtil::error("Error XmlRpcClient::readHeader: No Content-length specified");
401  return false; // We could try to figure it out by parsing as we read, but for now...
402  }
403 
404  // avoid overly large or improperly formatted content-length
405  long int clength = 0;
406  clength = strtol(lp, NULL, 10);
407  if ((clength <= 0) || (clength > __INT_MAX__)) {
408  XmlRpcUtil::error("Error in XmlRpcClient::readHeader: Invalid Content-length specified.");
409  return false;
410  }
411  _contentLength = int(clength);
412 
413  XmlRpcUtil::log(4, "client read content length: %d", _contentLength);
414 
415  // Otherwise copy non-header data to response buffer and set state to read response.
416  _response = bp;
418  return true; // Continue monitoring this source
419 }
420 
421 
422 bool
424 {
425  // If we dont have the entire response yet, read available data
426  if (int(_response.length()) < _contentLength) {
427  if ( ! XmlRpcSocket::nbRead(this->getfd(), _response, &_eof)) {
428  XmlRpcUtil::error("Error in XmlRpcClient::readResponse: read error (%s).",XmlRpcSocket::getErrorMsg().c_str());
429  return false;
430  }
431 
432  // Avoid an overly large response
433  if (_response.length() > size_t(__INT_MAX__)) {
434  XmlRpcUtil::error("XmlRpcClient::readResponse: response length (%u) exceeds the maximum allowed size (%u).",
435  _response.length(), __INT_MAX__);
436  _response.clear();
437  close();
438  return false;
439  }
440  // If we haven't gotten the entire _response yet, return (keep reading)
441  if (int(_response.length()) < _contentLength) {
442  if (_eof) {
443  XmlRpcUtil::error("Error in XmlRpcClient::readResponse: EOF while reading response");
444  return false;
445  }
446  return true;
447  }
448  }
449 
450  // Otherwise, parse and return the result
451  XmlRpcUtil::log(3, "XmlRpcClient::readResponse (read %d bytes)", _response.length());
452  XmlRpcUtil::log(5, "response:\n%s", _response.c_str());
453 
455 
456  return false; // Stop monitoring this source (causes return from work)
457 }
458 
459 
460 // Convert the response xml into a result value
461 bool
463 {
464  // Parse response xml into result
465  int offset = 0;
467  XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no methodResponse. Response:\n%s", _response.c_str());
468  _header = "";
469  return false;
470  }
471 
472  // Expect either <params><param>... or <fault>...
475  (XmlRpcUtil::nextTagIs(FAULT_TAG,_response,&offset) && (_isFault = true)))
476  {
477  if ( ! result.fromXml(_response, &offset)) {
478  XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response value. Response:\n%s", _response.c_str());
479  _response = "";
480  _header = "";
481  return false;
482  }
483  } else {
484  XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no param or fault tag. Response:\n%s", _response.c_str());
485  _response = "";
486  _header = "";
487  return false;
488  }
489 
490  _response = "";
491 
492  // Close connection if protocol is HTTP/1.0
493  if (_header.rfind("HTTP/1.0", 0) == 0) {
494  setKeepOpen(false);
495  close();
496  }
497 
498  _header = "";
499  return result.valid();
500 }
501 
static const char REQUEST_BEGIN[]
Definition: XmlRpcClient.h:29
virtual bool generateRequest(const char *method, XmlRpcValue const &params)
Definition: XmlRpc.h:39
bool getKeepOpen() const
Return whether the file descriptor should be kept open if it is no longer monitored.
Definition: XmlRpcSource.h:32
virtual bool parseResponse(XmlRpcValue &result)
RPC method arguments and results are represented by Values.
Definition: XmlRpcValue.h:24
const char XMLRPC_VERSION[]
Version identifier.
Definition: XmlRpcUtil.cpp:24
connected/data can be written without blocking
static const char PARAMS_TAG[]
Definition: XmlRpcClient.h:31
int size() const
Return the size for string, base64, array, and struct values.
virtual bool readHeader()
bool valid() const
Return true if the value has been set to something.
Definition: XmlRpcValue.h:114
void removeSource(XmlRpcSource *source)
static const char REQUEST_END[]
Definition: XmlRpcClient.h:35
void setKeepOpen(bool b=true)
Specify whether the file descriptor should be kept open if it is no longer monitored.
Definition: XmlRpcSource.h:34
static void error(const char *fmt,...)
Dump error messages somewhere.
Definition: XmlRpcUtil.cpp:95
static bool findTag(const char *tag, std::string const &xml, int *offset)
Returns true if the tag is found and updates offset to the char after the tag.
Definition: XmlRpcUtil.cpp:129
static bool nbRead(int socket, std::string &s, bool *eof)
Read text from the specified socket. Returns false on error.
std::string _request
Definition: XmlRpcClient.h:103
static bool connect(int socket, std::string &host, int port)
Connect a socket to a server (from a client)
Type const & getType() const
Return the type of the value stored.
Definition: XmlRpcValue.h:117
void clear()
Erase the current value.
Definition: XmlRpcValue.h:79
static std::string getErrorMsg()
Returns message corresponding to last error.
virtual void close()
Close the connection.
XmlRpcDispatch _disp
Definition: XmlRpcClient.h:127
static const char PARAM_ETAG[]
Definition: XmlRpcClient.h:34
static const char REQUEST_END_METHODNAME[]
Definition: XmlRpcClient.h:30
int getfd() const
Return the file descriptor being monitored.
Definition: XmlRpcSource.h:27
ClientConnectionState _connectionState
Definition: XmlRpcClient.h:91
virtual void close()
Close the owned fd. If deleteOnClose was specified at construction, the object is deleted...
void setfd(int fd)
Specify the file descriptor to monitor.
Definition: XmlRpcSource.h:29
std::string toXml() const
Encode the Value in xml.
void work(double msTime)
static const char PARAMS_ETAG[]
Definition: XmlRpcClient.h:32
XmlRpcClient(const char *host, int port, const char *uri=0)
virtual ~XmlRpcClient()
Destructor.
virtual bool doConnect()
static const char PARAM_TAG[]
Definition: XmlRpcClient.h:33
bool fromXml(std::string const &valueXml, int *offset)
Decode xml. Destroys any existing value.
static int socket()
Creates a stream (TCP) socket. Returns -1 on failure.
virtual std::string generateHeader(std::string const &body)
virtual unsigned handleEvent(unsigned eventType)
static bool nbWrite(int socket, std::string &s, int *bytesSoFar)
Write text to the specified socket. Returns false on error.
static const char METHODRESPONSE_TAG[]
Definition: XmlRpcClient.h:37
bool execute(const char *method, XmlRpcValue const &params, XmlRpcValue &result)
void addSource(XmlRpcSource *source, unsigned eventMask)
std::string _response
Definition: XmlRpcClient.h:105
bool executeNonBlock(const char *method, XmlRpcValue const &params)
void exit()
Exit from work routine.
static bool setNonBlocking(int socket)
Sets a stream (TCP) socket to perform non-blocking IO. Returns false on failure.
virtual bool setupConnection()
virtual bool readResponse()
bool executeCheckDone(XmlRpcValue &result)
virtual bool writeRequest()
static void log(int level, const char *fmt,...)
Dump messages somewhere.
Definition: XmlRpcUtil.cpp:80
ClearFlagOnExit(bool &flag)
static const char FAULT_TAG[]
Definition: XmlRpcClient.h:38
static bool nextTagIs(const char *tag, std::string const &xml, int *offset)
Definition: XmlRpcUtil.cpp:145


xmlrpcpp
Author(s): Chris Morley, Konstantin Pilipchuk, Morgan Quigley, Austin Hendrix, Dirk Thomas
autogenerated on Mon Nov 2 2020 03:52:24