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