1
2#include "XmlRpcClient.h"
3
4#include "XmlRpcSocket.h"
5#include "XmlRpc.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9
10
11using namespace XmlRpc;
12
13// Static data
14const char XmlRpcClient::REQUEST_BEGIN[] =
15  "<?xml version=\"1.0\"?>\r\n"
16  "<methodCall><methodName>";
17const char XmlRpcClient::REQUEST_END_METHODNAME[] = "</methodName>\r\n";
18const char XmlRpcClient::PARAMS_TAG[] = "<params>";
19const char XmlRpcClient::PARAMS_ETAG[] = "</params>";
20const char XmlRpcClient::PARAM_TAG[] = "<param>";
21const char XmlRpcClient::PARAM_ETAG[] =  "</param>";
22const char XmlRpcClient::REQUEST_END[] = "</methodCall>\r\n";
23const char XmlRpcClient::METHODRESPONSE_TAG[] = "<methodResponse>";
24const char XmlRpcClient::FAULT_TAG[] = "<fault>";
25
26
27
28XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri/*=0*/)
29{
30  XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port);
31
32  _host = host;
33  _port = port;
34  if (uri)
35    _uri = uri;
36  else
37    _uri = "/RPC2";
38  _connectionState = NO_CONNECTION;
39  _executing = false;
40  _eof = false;
41
42  // Default to keeping the connection open until an explicit close is done
43  setKeepOpen();
44}
45
46
47XmlRpcClient::~XmlRpcClient()
48{
49}
50
51// Close the owned fd
52void
53XmlRpcClient::close()
54{
55  XmlRpcUtil::log(4, "XmlRpcClient::close: fd %d.", getfd());
56  _connectionState = NO_CONNECTION;
57  _disp.exit();
58  _disp.removeSource(this);
59  XmlRpcSource::close();
60}
61
62
63// Clear the referenced flag even if exceptions or errors occur.
64struct ClearFlagOnExit {
65  ClearFlagOnExit(bool& flag) : _flag(flag) {}
66  ~ClearFlagOnExit() { _flag = false; }
67  bool& _flag;
68};
69
70// Execute the named procedure on the remote server.
71// Params should be an array of the arguments for the method.
72// Returns true if the request was sent and a result received (although the result
73// might be a fault).
74bool
75XmlRpcClient::execute(const char* method, XmlRpcValue const& params, XmlRpcValue& result)
76{
77  XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s (_connectionState %d).", method, _connectionState);
78
79  // This is not a thread-safe operation, if you want to do multithreading, use separate
80  // clients for each thread. If you want to protect yourself from multiple threads
81  // accessing the same client, replace this code with a real mutex.
82  if (_executing)
83    return false;
84
85  _executing = true;
86  ClearFlagOnExit cf(_executing);
87
88  _sendAttempts = 0;
89  _isFault = false;
90
91  if ( ! setupConnection())
92    return false;
93
94  if ( ! generateRequest(method, params))
95    return false;
96
97  result.clear();
98  double msTime = -1.0;   // Process until exit is called
99  _disp.work(msTime);
100
101  if (_connectionState != IDLE || ! parseResponse(result))
102    return false;
103
104  XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s completed.", method);
105  _response = "";
106  return true;
107}
108
109// XmlRpcSource interface implementation
110// Handle server responses. Called by the event dispatcher during execute.
111unsigned
112XmlRpcClient::handleEvent(unsigned eventType)
113{
114  if (eventType == XmlRpcDispatch::Exception)
115  {
116    if (_connectionState == WRITE_REQUEST && _bytesWritten == 0)
117      XmlRpcUtil::error("Error in XmlRpcClient::handleEvent: could not connect to server (%s).",
118                       XmlRpcSocket::getErrorMsg().c_str());
119    else
120      XmlRpcUtil::error("Error in XmlRpcClient::handleEvent (state %d): %s.",
121                        _connectionState, XmlRpcSocket::getErrorMsg().c_str());
122    return 0;
123  }
124
125  if (_connectionState == WRITE_REQUEST)
126    if ( ! writeRequest()) return 0;
127
128  if (_connectionState == READ_HEADER)
129    if ( ! readHeader()) return 0;
130
131  if (_connectionState == READ_RESPONSE)
132    if ( ! readResponse()) return 0;
133
134  // This should probably always ask for Exception events too
135  return (_connectionState == WRITE_REQUEST)
136        ? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent;
137}
138
139
140// Create the socket connection to the server if necessary
141bool
142XmlRpcClient::setupConnection()
143{
144  // If an error occurred last time through, or if the server closed the connection, close our end
145  if ((_connectionState != NO_CONNECTION && _connectionState != IDLE) || _eof)
146    close();
147
148  _eof = false;
149  if (_connectionState == NO_CONNECTION)
150    if (! doConnect())
151      return false;
152
153  // Prepare to write the request
154  _connectionState = WRITE_REQUEST;
155  _bytesWritten = 0;
156
157  // Notify the dispatcher to listen on this source (calls handleEvent when the socket is writable)
158  _disp.removeSource(this);       // Make sure nothing is left over
159  _disp.addSource(this, XmlRpcDispatch::WritableEvent | XmlRpcDispatch::Exception);
160
161  return true;
162}
163
164
165// Connect to the xmlrpc server
166bool
167XmlRpcClient::doConnect()
168{
169  int fd = XmlRpcSocket::socket();
170  if (fd < 0)
171  {
172    XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str());
173    return false;
174  }
175
176  XmlRpcUtil::log(3, "XmlRpcClient::doConnect: fd %d.", fd);
177  this->setfd(fd);
178
179  // Don't block on connect/reads/writes
180  if ( ! XmlRpcSocket::setNonBlocking(fd))
181  {
182    this->close();
183    XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not set socket to non-blocking IO mode (%s).", XmlRpcSocket::getErrorMsg().c_str());
184    return false;
185  }
186
187  if ( ! XmlRpcSocket::connect(fd, _host, _port))
188  {
189    this->close();
190    XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not connect to server (%s).", XmlRpcSocket::getErrorMsg().c_str());
191    return false;
192  }
193
194  return true;
195}
196
197// Encode the request to call the specified method with the specified parameters into xml
198bool
199XmlRpcClient::generateRequest(const char* methodName, XmlRpcValue const& params)
200{
201  std::string body = REQUEST_BEGIN;
202  body += methodName;
203  body += REQUEST_END_METHODNAME;
204
205  // If params is an array, each element is a separate parameter
206  if (params.valid()) {
207    body += PARAMS_TAG;
208    if (params.getType() == XmlRpcValue::TypeArray)
209    {
210      for (int i=0; i<params.size(); ++i) {
211        body += PARAM_TAG;
212        body += params[i].toXml();
213        body += PARAM_ETAG;
214      }
215    }
216    else
217    {
218      body += PARAM_TAG;
219      body += params.toXml();
220      body += PARAM_ETAG;
221    }
222
223    body += PARAMS_ETAG;
224  }
225  body += REQUEST_END;
226
227  std::string header = generateHeader(body);
228  XmlRpcUtil::log(4, "XmlRpcClient::generateRequest: header is %d bytes, content-length is %d.",
229                  header.length(), body.length());
230
231  _request = header + body;
232  return true;
233}
234
235// Prepend http headers
236std::string
237XmlRpcClient::generateHeader(std::string const& body)
238{
239  std::string header =
240    "POST " + _uri + " HTTP/1.1\r\n"
241    "User-Agent: ";
242  header += XMLRPC_VERSION;
243  header += "\r\nHost: ";
244  header += _host;
245
246  char buff[40];
247  sprintf(buff,":%d\r\n", _port);
248
249  header += buff;
250  header += "Content-Type: text/xml\r\nContent-length: ";
251
252  sprintf(buff,"%d\r\n\r\n", (int)body.size());
253
254  return header + buff;
255}
256
257bool
258XmlRpcClient::writeRequest()
259{
260  if (_bytesWritten == 0)
261    XmlRpcUtil::log(5, "XmlRpcClient::writeRequest (attempt %d):\n%s\n", _sendAttempts+1, _request.c_str());
262
263  // Try to write the request
264  if ( ! XmlRpcSocket::nbWrite(this->getfd(), _request, &_bytesWritten)) {
265    XmlRpcUtil::error("Error in XmlRpcClient::writeRequest: write error (%s).",XmlRpcSocket::getErrorMsg().c_str());
266    return false;
267  }
268
269  XmlRpcUtil::log(3, "XmlRpcClient::writeRequest: wrote %d of %d bytes.", _bytesWritten, _request.length());
270
271  // Wait for the result
272  if (_bytesWritten == int(_request.length())) {
273    _header = "";
274    _response = "";
275    _connectionState = READ_HEADER;
276  }
277  return true;
278}
279
280
281// Read the header from the response
282bool
283XmlRpcClient::readHeader()
284{
285  // Read available data
286  if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &_eof) ||
287       (_eof && _header.length() == 0)) {
288
289    // If we haven't read any data yet and this is a keep-alive connection, the server may
290    // have timed out, so we try one more time.
291    if (getKeepOpen() && _header.length() == 0 && _sendAttempts++ == 0) {
292      XmlRpcUtil::log(4, "XmlRpcClient::readHeader: re-trying connection");
293      XmlRpcSource::close();
294      _connectionState = NO_CONNECTION;
295      _eof = false;
296      return setupConnection();
297    }
298
299    XmlRpcUtil::error("Error in XmlRpcClient::readHeader: error while reading header (%s) on fd %d.",
300                      XmlRpcSocket::getErrorMsg().c_str(), getfd());
301    return false;
302  }
303
304  XmlRpcUtil::log(4, "XmlRpcClient::readHeader: client has read %d bytes", _header.length());
305
306  char *hp = (char*)_header.c_str();  // Start of header
307  char *ep = hp + _header.length();   // End of string
308  char *bp = 0;                       // Start of body
309  char *lp = 0;                       // Start of content-length value
310
311  for (char *cp = hp; (bp == 0) && (cp < ep); ++cp) {
312    if ((ep - cp > 16) && (strncasecmp(cp, "Content-length: ", 16) == 0))
313      lp = cp + 16;
314    else if ((ep - cp > 4) && (strncmp(cp, "\r\n\r\n", 4) == 0))
315      bp = cp + 4;
316    else if ((ep - cp > 2) && (strncmp(cp, "\n\n", 2) == 0))
317      bp = cp + 2;
318  }
319
320  // If we haven't gotten the entire header yet, return (keep reading)
321  if (bp == 0) {
322    if (_eof)          // EOF in the middle of a response is an error
323    {
324      XmlRpcUtil::error("Error in XmlRpcClient::readHeader: EOF while reading header");
325      return false;   // Close the connection
326    }
327
328    return true;  // Keep reading
329  }
330
331  // Decode content length
332  if (lp == 0) {
333    XmlRpcUtil::error("Error XmlRpcClient::readHeader: No Content-length specified");
334    return false;   // We could try to figure it out by parsing as we read, but for now...
335  }
336
337  _contentLength = atoi(lp);
338  if (_contentLength <= 0) {
339    XmlRpcUtil::error("Error in XmlRpcClient::readHeader: Invalid Content-length specified (%d).", _contentLength);
340    return false;
341  }
342
343  XmlRpcUtil::log(4, "client read content length: %d", _contentLength);
344
345  // Otherwise copy non-header data to response buffer and set state to read response.
346  _response = bp;
347  _header = "";   // should parse out any interesting bits from the header (connection, etc)...
348  _connectionState = READ_RESPONSE;
349  return true;    // Continue monitoring this source
350}
351
352
353bool
354XmlRpcClient::readResponse()
355{
356  // If we dont have the entire response yet, read available data
357  if (int(_response.length()) < _contentLength) {
358    if ( ! XmlRpcSocket::nbRead(this->getfd(), _response, &_eof)) {
359      XmlRpcUtil::error("Error in XmlRpcClient::readResponse: read error (%s).",XmlRpcSocket::getErrorMsg().c_str());
360      return false;
361    }
362
363    // If we haven't gotten the entire _response yet, return (keep reading)
364    if (int(_response.length()) < _contentLength) {
365      if (_eof) {
366        XmlRpcUtil::error("Error in XmlRpcClient::readResponse: EOF while reading response");
367        return false;
368      }
369      return true;
370    }
371  }
372
373  // Otherwise, parse and return the result
374  XmlRpcUtil::log(3, "XmlRpcClient::readResponse (read %d bytes)", _response.length());
375  XmlRpcUtil::log(5, "response:\n%s", _response.c_str());
376
377  _connectionState = IDLE;
378
379  return false;    // Stop monitoring this source (causes return from work)
380}
381
382
383// Convert the response xml into a result value
384bool
385XmlRpcClient::parseResponse(XmlRpcValue& result)
386{
387  // Parse response xml into result
388  int offset = 0;
389  if ( ! XmlRpcUtil::findTag(METHODRESPONSE_TAG,_response,&offset)) {
390    XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no methodResponse. Response:\n%s", _response.c_str());
391    return false;
392  }
393
394  // Expect either <params><param>... or <fault>...
395  if ((XmlRpcUtil::nextTagIs(PARAMS_TAG,_response,&offset) &&
396       XmlRpcUtil::nextTagIs(PARAM_TAG,_response,&offset)) ||
397      (XmlRpcUtil::nextTagIs(FAULT_TAG,_response,&offset) && (_isFault = true)))
398  {
399    if ( ! result.fromXml(_response, &offset)) {
400      XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response value. Response:\n%s", _response.c_str());
401      _response = "";
402      return false;
403    }
404  } else {
405    XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no param or fault tag. Response:\n%s", _response.c_str());
406    _response = "";
407    return false;
408  }
409
410  _response = "";
411  return result.valid();
412}
413
414