| |
| #include "XmlRpcClient.h" |
| |
| #include "XmlRpcSocket.h" |
| #include "XmlRpc.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| |
| using namespace XmlRpc; |
| |
| // Static data |
| const char XmlRpcClient::REQUEST_BEGIN[] = |
| "<?xml version=\"1.0\"?>\r\n" |
| "<methodCall><methodName>"; |
| const char XmlRpcClient::REQUEST_END_METHODNAME[] = "</methodName>\r\n"; |
| const char XmlRpcClient::PARAMS_TAG[] = "<params>"; |
| const char XmlRpcClient::PARAMS_ETAG[] = "</params>"; |
| const char XmlRpcClient::PARAM_TAG[] = "<param>"; |
| const char XmlRpcClient::PARAM_ETAG[] = "</param>"; |
| const char XmlRpcClient::REQUEST_END[] = "</methodCall>\r\n"; |
| const char XmlRpcClient::METHODRESPONSE_TAG[] = "<methodResponse>"; |
| const char XmlRpcClient::FAULT_TAG[] = "<fault>"; |
| |
| |
| |
| XmlRpcClient::XmlRpcClient(const char* host, int port, const char* uri/*=0*/) |
| { |
| XmlRpcUtil::log(1, "XmlRpcClient new client: host %s, port %d.", host, port); |
| |
| _host = host; |
| _port = port; |
| if (uri) |
| _uri = uri; |
| else |
| _uri = "/RPC2"; |
| _connectionState = NO_CONNECTION; |
| _executing = false; |
| _eof = false; |
| |
| // Default to keeping the connection open until an explicit close is done |
| setKeepOpen(); |
| } |
| |
| |
| XmlRpcClient::~XmlRpcClient() |
| { |
| } |
| |
| // Close the owned fd |
| void |
| XmlRpcClient::close() |
| { |
| XmlRpcUtil::log(4, "XmlRpcClient::close: fd %d.", getfd()); |
| _connectionState = NO_CONNECTION; |
| _disp.exit(); |
| _disp.removeSource(this); |
| XmlRpcSource::close(); |
| } |
| |
| |
| // Clear the referenced flag even if exceptions or errors occur. |
| struct ClearFlagOnExit { |
| ClearFlagOnExit(bool& flag) : _flag(flag) {} |
| ~ClearFlagOnExit() { _flag = false; } |
| bool& _flag; |
| }; |
| |
| // Execute the named procedure on the remote server. |
| // Params should be an array of the arguments for the method. |
| // Returns true if the request was sent and a result received (although the result |
| // might be a fault). |
| bool |
| XmlRpcClient::execute(const char* method, XmlRpcValue const& params, XmlRpcValue& result) |
| { |
| XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s (_connectionState %d).", method, _connectionState); |
| |
| // This is not a thread-safe operation, if you want to do multithreading, use separate |
| // clients for each thread. If you want to protect yourself from multiple threads |
| // accessing the same client, replace this code with a real mutex. |
| if (_executing) |
| return false; |
| |
| _executing = true; |
| ClearFlagOnExit cf(_executing); |
| |
| _sendAttempts = 0; |
| _isFault = false; |
| |
| if ( ! setupConnection()) |
| return false; |
| |
| if ( ! generateRequest(method, params)) |
| return false; |
| |
| result.clear(); |
| double msTime = -1.0; // Process until exit is called |
| _disp.work(msTime); |
| |
| if (_connectionState != IDLE || ! parseResponse(result)) |
| return false; |
| |
| XmlRpcUtil::log(1, "XmlRpcClient::execute: method %s completed.", method); |
| _response = ""; |
| return true; |
| } |
| |
| // XmlRpcSource interface implementation |
| // Handle server responses. Called by the event dispatcher during execute. |
| unsigned |
| XmlRpcClient::handleEvent(unsigned eventType) |
| { |
| if (eventType == XmlRpcDispatch::Exception) |
| { |
| if (_connectionState == WRITE_REQUEST && _bytesWritten == 0) |
| XmlRpcUtil::error("Error in XmlRpcClient::handleEvent: could not connect to server (%s).", |
| XmlRpcSocket::getErrorMsg().c_str()); |
| else |
| XmlRpcUtil::error("Error in XmlRpcClient::handleEvent (state %d): %s.", |
| _connectionState, XmlRpcSocket::getErrorMsg().c_str()); |
| return 0; |
| } |
| |
| if (_connectionState == WRITE_REQUEST) |
| if ( ! writeRequest()) return 0; |
| |
| if (_connectionState == READ_HEADER) |
| if ( ! readHeader()) return 0; |
| |
| if (_connectionState == READ_RESPONSE) |
| if ( ! readResponse()) return 0; |
| |
| // This should probably always ask for Exception events too |
| return (_connectionState == WRITE_REQUEST) |
| ? XmlRpcDispatch::WritableEvent : XmlRpcDispatch::ReadableEvent; |
| } |
| |
| |
| // Create the socket connection to the server if necessary |
| bool |
| XmlRpcClient::setupConnection() |
| { |
| // If an error occurred last time through, or if the server closed the connection, close our end |
| if ((_connectionState != NO_CONNECTION && _connectionState != IDLE) || _eof) |
| close(); |
| |
| _eof = false; |
| if (_connectionState == NO_CONNECTION) |
| if (! doConnect()) |
| return false; |
| |
| // Prepare to write the request |
| _connectionState = WRITE_REQUEST; |
| _bytesWritten = 0; |
| |
| // Notify the dispatcher to listen on this source (calls handleEvent when the socket is writable) |
| _disp.removeSource(this); // Make sure nothing is left over |
| _disp.addSource(this, XmlRpcDispatch::WritableEvent | XmlRpcDispatch::Exception); |
| |
| return true; |
| } |
| |
| |
| // Connect to the xmlrpc server |
| bool |
| XmlRpcClient::doConnect() |
| { |
| int fd = XmlRpcSocket::socket(); |
| if (fd < 0) |
| { |
| XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not create socket (%s).", XmlRpcSocket::getErrorMsg().c_str()); |
| return false; |
| } |
| |
| XmlRpcUtil::log(3, "XmlRpcClient::doConnect: fd %d.", fd); |
| this->setfd(fd); |
| |
| // Don't block on connect/reads/writes |
| if ( ! XmlRpcSocket::setNonBlocking(fd)) |
| { |
| this->close(); |
| XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not set socket to non-blocking IO mode (%s).", XmlRpcSocket::getErrorMsg().c_str()); |
| return false; |
| } |
| |
| if ( ! XmlRpcSocket::connect(fd, _host, _port)) |
| { |
| this->close(); |
| XmlRpcUtil::error("Error in XmlRpcClient::doConnect: Could not connect to server (%s).", XmlRpcSocket::getErrorMsg().c_str()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Encode the request to call the specified method with the specified parameters into xml |
| bool |
| XmlRpcClient::generateRequest(const char* methodName, XmlRpcValue const& params) |
| { |
| std::string body = REQUEST_BEGIN; |
| body += methodName; |
| body += REQUEST_END_METHODNAME; |
| |
| // If params is an array, each element is a separate parameter |
| if (params.valid()) { |
| body += PARAMS_TAG; |
| if (params.getType() == XmlRpcValue::TypeArray) |
| { |
| for (int i=0; i<params.size(); ++i) { |
| body += PARAM_TAG; |
| body += params[i].toXml(); |
| body += PARAM_ETAG; |
| } |
| } |
| else |
| { |
| body += PARAM_TAG; |
| body += params.toXml(); |
| body += PARAM_ETAG; |
| } |
| |
| body += PARAMS_ETAG; |
| } |
| body += REQUEST_END; |
| |
| std::string header = generateHeader(body); |
| XmlRpcUtil::log(4, "XmlRpcClient::generateRequest: header is %d bytes, content-length is %d.", |
| header.length(), body.length()); |
| |
| _request = header + body; |
| return true; |
| } |
| |
| // Prepend http headers |
| std::string |
| XmlRpcClient::generateHeader(std::string const& body) |
| { |
| std::string header = |
| "POST " + _uri + " HTTP/1.1\r\n" |
| "User-Agent: "; |
| header += XMLRPC_VERSION; |
| header += "\r\nHost: "; |
| header += _host; |
| |
| char buff[40]; |
| sprintf(buff,":%d\r\n", _port); |
| |
| header += buff; |
| header += "Content-Type: text/xml\r\nContent-length: "; |
| |
| sprintf(buff,"%d\r\n\r\n", body.size()); |
| |
| return header + buff; |
| } |
| |
| bool |
| XmlRpcClient::writeRequest() |
| { |
| if (_bytesWritten == 0) |
| XmlRpcUtil::log(5, "XmlRpcClient::writeRequest (attempt %d):\n%s\n", _sendAttempts+1, _request.c_str()); |
| |
| // Try to write the request |
| if ( ! XmlRpcSocket::nbWrite(this->getfd(), _request, &_bytesWritten)) { |
| XmlRpcUtil::error("Error in XmlRpcClient::writeRequest: write error (%s).",XmlRpcSocket::getErrorMsg().c_str()); |
| return false; |
| } |
| |
| XmlRpcUtil::log(3, "XmlRpcClient::writeRequest: wrote %d of %d bytes.", _bytesWritten, _request.length()); |
| |
| // Wait for the result |
| if (_bytesWritten == int(_request.length())) { |
| _header = ""; |
| _response = ""; |
| _connectionState = READ_HEADER; |
| } |
| return true; |
| } |
| |
| |
| // Read the header from the response |
| bool |
| XmlRpcClient::readHeader() |
| { |
| // Read available data |
| if ( ! XmlRpcSocket::nbRead(this->getfd(), _header, &_eof) || |
| (_eof && _header.length() == 0)) { |
| |
| // If we haven't read any data yet and this is a keep-alive connection, the server may |
| // have timed out, so we try one more time. |
| if (getKeepOpen() && _header.length() == 0 && _sendAttempts++ == 0) { |
| XmlRpcUtil::log(4, "XmlRpcClient::readHeader: re-trying connection"); |
| XmlRpcSource::close(); |
| _connectionState = NO_CONNECTION; |
| _eof = false; |
| return setupConnection(); |
| } |
| |
| XmlRpcUtil::error("Error in XmlRpcClient::readHeader: error while reading header (%s) on fd %d.", |
| XmlRpcSocket::getErrorMsg().c_str(), getfd()); |
| return false; |
| } |
| |
| XmlRpcUtil::log(4, "XmlRpcClient::readHeader: client has read %d bytes", _header.length()); |
| |
| char *hp = (char*)_header.c_str(); // Start of header |
| char *ep = hp + _header.length(); // End of string |
| char *bp = 0; // Start of body |
| char *lp = 0; // Start of content-length value |
| |
| for (char *cp = hp; (bp == 0) && (cp < ep); ++cp) { |
| if ((ep - cp > 16) && (strncasecmp(cp, "Content-length: ", 16) == 0)) |
| lp = cp + 16; |
| else if ((ep - cp > 4) && (strncmp(cp, "\r\n\r\n", 4) == 0)) |
| bp = cp + 4; |
| else if ((ep - cp > 2) && (strncmp(cp, "\n\n", 2) == 0)) |
| bp = cp + 2; |
| } |
| |
| // If we haven't gotten the entire header yet, return (keep reading) |
| if (bp == 0) { |
| if (_eof) // EOF in the middle of a response is an error |
| { |
| XmlRpcUtil::error("Error in XmlRpcClient::readHeader: EOF while reading header"); |
| return false; // Close the connection |
| } |
| |
| return true; // Keep reading |
| } |
| |
| // Decode content length |
| if (lp == 0) { |
| XmlRpcUtil::error("Error XmlRpcClient::readHeader: No Content-length specified"); |
| return false; // We could try to figure it out by parsing as we read, but for now... |
| } |
| |
| _contentLength = atoi(lp); |
| if (_contentLength <= 0) { |
| XmlRpcUtil::error("Error in XmlRpcClient::readHeader: Invalid Content-length specified (%d).", _contentLength); |
| return false; |
| } |
| |
| XmlRpcUtil::log(4, "client read content length: %d", _contentLength); |
| |
| // Otherwise copy non-header data to response buffer and set state to read response. |
| _response = bp; |
| _header = ""; // should parse out any interesting bits from the header (connection, etc)... |
| _connectionState = READ_RESPONSE; |
| return true; // Continue monitoring this source |
| } |
| |
| |
| bool |
| XmlRpcClient::readResponse() |
| { |
| // If we dont have the entire response yet, read available data |
| if (int(_response.length()) < _contentLength) { |
| if ( ! XmlRpcSocket::nbRead(this->getfd(), _response, &_eof)) { |
| XmlRpcUtil::error("Error in XmlRpcClient::readResponse: read error (%s).",XmlRpcSocket::getErrorMsg().c_str()); |
| return false; |
| } |
| |
| // If we haven't gotten the entire _response yet, return (keep reading) |
| if (int(_response.length()) < _contentLength) { |
| if (_eof) { |
| XmlRpcUtil::error("Error in XmlRpcClient::readResponse: EOF while reading response"); |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| // Otherwise, parse and return the result |
| XmlRpcUtil::log(3, "XmlRpcClient::readResponse (read %d bytes)", _response.length()); |
| XmlRpcUtil::log(5, "response:\n%s", _response.c_str()); |
| |
| _connectionState = IDLE; |
| |
| return false; // Stop monitoring this source (causes return from work) |
| } |
| |
| |
| // Convert the response xml into a result value |
| bool |
| XmlRpcClient::parseResponse(XmlRpcValue& result) |
| { |
| // Parse response xml into result |
| int offset = 0; |
| if ( ! XmlRpcUtil::findTag(METHODRESPONSE_TAG,_response,&offset)) { |
| XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no methodResponse. Response:\n%s", _response.c_str()); |
| return false; |
| } |
| |
| // Expect either <params><param>... or <fault>... |
| if ((XmlRpcUtil::nextTagIs(PARAMS_TAG,_response,&offset) && |
| XmlRpcUtil::nextTagIs(PARAM_TAG,_response,&offset)) || |
| (XmlRpcUtil::nextTagIs(FAULT_TAG,_response,&offset) && (_isFault = true))) |
| { |
| if ( ! result.fromXml(_response, &offset)) { |
| XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response value. Response:\n%s", _response.c_str()); |
| _response = ""; |
| return false; |
| } |
| } else { |
| XmlRpcUtil::error("Error in XmlRpcClient::parseResponse: Invalid response - no param or fault tag. Response:\n%s", _response.c_str()); |
| _response = ""; |
| return false; |
| } |
| |
| _response = ""; |
| return result.valid(); |
| } |
| |