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
11 using namespace XmlRpc;
12
13 // Static data
14 const char XmlRpcClient::REQUEST_BEGIN[] =
15 "<?xml version=\"1.0\"?>\r\n"
16 "<methodCall><methodName>";
17 const char XmlRpcClient::REQUEST_END_METHODNAME[] = "</methodName>\r\n";
18 const char XmlRpcClient::PARAMS_TAG[] = "<params>";
19 const char XmlRpcClient::PARAMS_ETAG[] = "</params>";
20 const char XmlRpcClient::PARAM_TAG[] = "<param>";
21 const char XmlRpcClient::PARAM_ETAG[] = "</param>";
22 const char XmlRpcClient::REQUEST_END[] = "</methodCall>\r\n";
23 const char XmlRpcClient::METHODRESPONSE_TAG[] = "<methodResponse>";
24 const char XmlRpcClient::FAULT_TAG[] = "<fault>";
25
26
27
XmlRpcClient(const char * host,int port,const char * uri)28 XmlRpcClient::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
~XmlRpcClient()47 XmlRpcClient::~XmlRpcClient()
48 {
49 }
50
51 // Close the owned fd
52 void
close()53 XmlRpcClient::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.
64 struct ClearFlagOnExit {
ClearFlagOnExitClearFlagOnExit65 ClearFlagOnExit(bool& flag) : _flag(flag) {}
~ClearFlagOnExitClearFlagOnExit66 ~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).
74 bool
execute(const char * method,XmlRpcValue const & params,XmlRpcValue & result)75 XmlRpcClient::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.
111 unsigned
handleEvent(unsigned eventType)112 XmlRpcClient::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
141 bool
setupConnection()142 XmlRpcClient::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
166 bool
doConnect()167 XmlRpcClient::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
198 bool
generateRequest(const char * methodName,XmlRpcValue const & params)199 XmlRpcClient::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
236 std::string
generateHeader(std::string const & body)237 XmlRpcClient::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", body.size());
253
254 return header + buff;
255 }
256
257 bool
writeRequest()258 XmlRpcClient::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
282 bool
readHeader()283 XmlRpcClient::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
353 bool
readResponse()354 XmlRpcClient::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
384 bool
parseResponse(XmlRpcValue & result)385 XmlRpcClient::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