Writing Client - adedov/libiqxmlrpc GitHub Wiki
Lets start with simple example that will demonstrate basic XML-RPC client. This client example designed to be compatible with server sample.
#include <iostream>
#include <libiqxmlrpc/libiqxmlrpc.h>
#include <libiqxmlrpc/client.h>
#include <libiqxmlrpc/http_client.h>
int main()
{
using namespace iqxmlrpc;
// 1: Create a client object
Client<Http_client_connection> client(iqnet::Inet_addr(3344));
// 2: Fill method parameters
Param_list pl;
pl.push_back(Struct());
pl[0].insert("var1", 1);
pl[0].insert("var2", "value");
// 3: Call a remote method
Response r = client.execute("echo", pl);
// 4: Process method output
assert(r.value()["var1"].get_int() == 1);
assert(r.value()["var2"].get_string() == "value");
}
Comments with figures state different phases of a process of writing client code with Libiqxmlrpc. Here they are:
- Create and configure client object
- Make remote invocations
- Construct a set of parameters
- Call a remote method
- Treat method output
A following sections say more about those phases.
An abstract base class iqxmlrpc::Client_base provides interface for client configuration and execution of remote methods. In summary, this interface allows one to:
- Configure client connections:
- Specify Proxy Settings
- Specify Connection Timeout
- Specify Keep-alive Flag
- Manage Client Authorization
- Method Execution
However, the first thing a developer need, is to create an instance of Client_base. Concrete sub-classes of Client_base take care of a transport used to communicate remote service. There is template class iqxmlrpc::Client that should be used for instantiating Client_base children. User must specify appropriate TRANSPORT parameter for his situation, depending on transport required (HTTP or HTTPS).
Possible options are:
- iqxmlrpc::Http_client_connection
- iqxmlrpc::Https_client_connection
In a common case client creation code will look like a following:
iqxmlrpc::Client<TRANSPORT> client( iqnet::Inet_addr(host, port), "/RPC2" );
An /RPC2 string is an URI that can be configured by a owner of a remote service.
One can fearlessly substitute TRANSPORT template argument with Http_client_connection in an example above. However, enabling HTTPS needs some additional magic from a developer. I.e. one must initialize SSL subsystem before creating any SSL connection:
#include <libiqxmlrpc/libiqxmlrpc.h>
#include <libiqxmlrpc/https_client.h>
using namespace iqxmlrpc;
Client_base*
create_https_client(iqnet::Inet_addr addr, const std::string& uri)
{
// Initialize SSL subsystem
iqnet::ssl::ctx = iqnet::ssl::Ctx::client_only();
return new Client<Https_client_connection>(addr, uri);
}
Client_base::set_proxy
: This method allows one set an HTTP proxy host for all client's outgoing connections.
Client<Http_client_connection> client(Inet_addr("host.com", 8080));
client.set_proxy(Inet_addr("my.proxy.com", 80));
Client_base::set_timeout
: This method sets connection timeout in seconds. A negative value means no timeout. Library throws iqxmlrpc::Client_timeout exception during processing remote call in case when time is out.
void timed_call(Client_base& client, int timeout)
{
client.set_timeout(timeout);
try {
Response r = client.execute("foo", Value());
// work with r ...
} catch(const Client_timeout&) {
// handle connection timeout
}
}
Client_base::set_keep_alive
: This method manages HTTP keep-alive facility on a client connection. If one enables this flag and remote server supports this facility then all subsequent method execution request will go through the same open connection.
void banch_of_requests(Client_base& client)
{
// Ask server to not close connection after response
client.set_keep_alive(true);
client.execute("foo", Value());
// Hope, the same connection.
// Depending on server implementation.
client.execute("bar", Value());
}
Client_base::set_authinfo
: This method sets user/password pair to pass through HTTP authentication mechanism.
client.set_authinfo("admin", "12345");
Note: Though library allows use set_authinfo with plain a HTTP connections, one should not rely on it because in this case your credential information may become available to traffic sniffers.
XML-RPC standard allows transfer a number of input parameters to a remote procedure. Actually, quite often XML-RPC APIs take in account only first of them, and require transferring parameters in a key-value form. Just because this method is much more convenient and reflects to a dynamic nature of XML-RPC it-self.
Library provides comfortable way for both approaches. The iqxmlrpc::Client provides two overloaded interface for calling remote methods:
namespace iqxmlrpc {
typedef std::vector<Value> Param_list;
class Client {
public:
// ...
Response execute( const std::string& method, const Param_list& );
Response execute( const std::string& method, const Value& );
};
} // namespace iqxmlrpc
Both methods accept name of calling method as a first parameter. A former allows transfer an array of input parameters (as the standard suggests).
A second one puts specified value into first element of array of parameters and just calls a first one.
An XML-RPC response may be of two types: method output, and failure report. Objects of iqxmlrpc::Response class serve a destination for result of a remote call and may hold either type of response.
A Response::value method gives user access to a received result. And Response::is_fault allows user check if received XML-RPC packet contained failure notice. If user try access response value with value method and an object is actually containing an XML-RPC failure notice then exception that contain all information about failure would be thrown. This behavior gives user ability write a code in optimistic manner.
Response::fault_code and Response::fault_string methods gives an alternative way to retrieve information about failure.
Note: Please note that Response objects may contain only XML-RPC failures. Failures form a network layer or some HTTP issues are always exceptions.
std::string get_name_of_object(Client_base& client, int object_id)
{
try {
Response r = client("get_object_name", object_id);
return r.value().get_string();
} catch (const Client_timeout&) {
std::cerr << "Timeout" << std::endl;
exit(1);
} catch (const Exception& e) {
std::cerr
<< "XML-RPC error "
<< e.code() << ": " << e.what()
<< std::endl;
exit(2);
}
}