From 922cb5559b9f2f97279fa24cc9c5862c8b666495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Tue, 8 Mar 2005 10:23:43 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r2603, which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/iem/iemxmlrpc/; revision=2604 --- xmlrpc++/src/XmlRpcClient.cpp | 413 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 xmlrpc++/src/XmlRpcClient.cpp (limited to 'xmlrpc++/src/XmlRpcClient.cpp') 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 +#include + + +using namespace XmlRpc; + +// Static data +const char XmlRpcClient::REQUEST_BEGIN[] = + "\r\n" + ""; +const char XmlRpcClient::REQUEST_END_METHODNAME[] = "\r\n"; +const char XmlRpcClient::PARAMS_TAG[] = ""; +const char XmlRpcClient::PARAMS_ETAG[] = ""; +const char XmlRpcClient::PARAM_TAG[] = ""; +const char XmlRpcClient::PARAM_ETAG[] = ""; +const char XmlRpcClient::REQUEST_END[] = "\r\n"; +const char XmlRpcClient::METHODRESPONSE_TAG[] = ""; +const char XmlRpcClient::FAULT_TAG[] = ""; + + + +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; igetfd(), _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 ... or ... + 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(); +} + -- cgit v1.2.1