aboutsummaryrefslogtreecommitdiff
path: root/xmlrpc++/src/XmlRpcClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xmlrpc++/src/XmlRpcClient.cpp')
-rw-r--r--xmlrpc++/src/XmlRpcClient.cpp413
1 files changed, 413 insertions, 0 deletions
diff --git a/xmlrpc++/src/XmlRpcClient.cpp b/xmlrpc++/src/XmlRpcClient.cpp
new file mode 100644
index 0000000..e706d0a
--- /dev/null
+++ b/xmlrpc++/src/XmlRpcClient.cpp
@@ -0,0 +1,413 @@
+
+#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();
+}
+