This chapter explains the basics in using this library. To learn more about the details please read the api handbook which you can either download from the homepage or generate yourself if you have doxygen installed. To generate yourself use the config-file in the top level directory of the source distribution.
Please also try the example files in the subdirectory tests
which are compiled with
make check
ulxmlrpcpp
implements all basic variable types as classes which are derived from
a common type Value
.
Used for the boolean values true
and false
.
Directly corresponds to the C++ type bool
.
Intended for signed integer values that need at most 32bit to store.
The C++ type is system dependent and usually an int
.
The type of choice for floating point numbers. The range is system dependent but usually around +-10E356.
Take this type to pass messages. There is a little pitfall since there
is no commeon census how characters have to be interpreted and every system uses
its own encoding. For that reason ulxmlrpcpp
(and expat
as well) uses
unicode throughout the complete library.
The internal storage is done
in UTF-8
which is a compromise in favour of memory consumption to speed.
Due to this all you have to take care on how to move your strings into our
String
type as you must know your encoding when you have characters
beyond the ascii range. If you have iconv
installed on your system you may
simply convert from your encoding to utf8 with
ulxr::encodeToUtf8("your-string", "encoding-name")
There is no limitation in the length of the strings nor is there something like a termination character ('\0' in C).
This type is similar to String
but is intended for binary data.
Since XML has some limitations in using characters such binary data must be
converted for transportation, in this case
base64.
For the user this is transparent and you simply store/retrieve your values and the library does the rest.
This type is also similar to String
but is intended for time values
according to the
ISO8601 format..
Currently the user is responsible for the conversion to and from this format as the library just takes care about the type and not the content.
This is not really a data type but a possiblity to collect variables. Unlike arrays in many other programming languages an Array may contain different data types. You access the members by their index.
Similar to the Array type this is also a collection of variables. The difference is that you don't get access by an index but by an identifier. So this is like an associative array or a map in C++.
For convenience reasons there is also the possiblity to use streaming-like methods to add parameters to structs and arrays. Similar to the way you would output content to std::cout. To avoid abiguities it is by default not possible to add intrinsic values like int or double. Use Integer() or Double() if possible. If you really want to handle the built-in types add the following line before the ulxr headers in your sources:
#define ULXR_USE_INTRINSIC_VALUE_TYPES
Handling the data types might look like this:
using namespace ulxr;
// fill Array with some content
Array arr;
arr.addItem(Integer(1));
arr.addItem(String("My String in the array"));
arr.addItem(Base64("base64-encoded string"));
// access array content, convert to "native" data type
Integer xi = arr.getItem(0);
int i = xi.getInteger();
String xs = arr.getItem(1);
string s = xs.getString();
// fill Struct
Struct st;
st.addMember("first member", Double(3.1415));
st.addMember("second member", Boolean(true));
st << ulxr::make_member("ima_wstring", String(L"wstring"))
<< ulxr::make_member("ima_double", Double(1.0))
<< ulxr::make_member("ima_bool", Boolean(true));
// access struct content
Double d = st.getMember("first member");
A minimal implementation comprises a mechanism that waits for a call, dispatches it to the desired function and finally returns the method result.
The following examples omit error checking for ease of understanding. A connection is established and the call is processed.
If you have methods that take more than one or two parameters it is an error prone taks to create
the needed signature string manually. For that purpose there a class Signature
that handles
the parameters and generates the signature strings. It is also possible to "stream-in" the
parameters.
// expect calls from 127.0.0.1 (localhost)
HttpConnection conn (0x7f000001, 32000);
HttpProtocol prot(&conn, conn.getHostName());
Dispatcher server(&prot);
server.addMethod(&testcall,
"struct",
"testcall",
"int",
"Returns input, difference and result");
server.addMethod(&testcall_with_many_params,
"struct",
"testcall_with_many_params",
Signature() << Integer()
<< String()
<< Double(),
"Performs action xyz");
MethodCall call = server.waitForCall();
MethodResponse resp = server.dispatchCall(call);
server.sendResponse(resp);
And now the method that does to real work. In our case it just takes a number as input, adds a value and returns a Struct that contains the original value, the difference and the result.
MethodResponse testcall (const MethodCall &calldata)
{
Integer i = calldata.getParam(0);
Struct st;
st.addMember("before", i);
st.addMember("difference", Integer(1111));
st.addMember("after", Integer(i.getInteger()+1111));
return MethodResponse(st);
}
The rpc method may also also be implemented as a normal member function. To access it you need some wrapper function. By using "make_method" this is done automatically:
For convenience and clearity there is also a "make_method" for static class methods or simple functions.
class TestWorker
{
public:
TestWorker ()
{}
ulxr::MethodResponse testcall1 (const ulxr::MethodCall &calldata)
{
...
}
};
ulxr::MethodResponse testcall (const ulxr::MethodCall &calldata)
{
....
}
server.addMethod(make_method(&testcall),
ulxr::Signature(ulxr::Integer()),
"testcall_function",
ulxr::Signature(ulxr::Integer())
.addParam(ulxr::Integer()),
"Testcase with a c-function");
TestWorker worker;
server.addMethod(make_method(worker, &TestWorker::testcall1),
ulxr::Signature(ulxr::Struct()),
"testcall_in_class_dynamic",
ulxr::Signature(ulxr::Integer()),
"Testcase with a dynamic method in a class");
Setting up a client is easy, too. Just select the connection, describe the method and the parameters and call it. The example does no further processing but just shows the result as XML output.
In rare cases you may want to send a call without the need to check for a response.
Then you invoke transmit()
instead of call()
. In this case
the server will omit the response. Network problems, on the other hand, will be reported as
usual.
But caution: This is an ulxmlrpcpp
extension. Servers from other implementations
will always send a response so you might receive garbage from the next call or maybe
generate a lot of error messages on the remote server.
HttpConnection conn ("localhost", 32000);
HttpProtocol prot(&conn, conn.getHostName());
Requester client(&prot);
// our method needs a single number as input
MethodCall testcall ("testcall");
testcall.addParam(Integer(123));
MethodResponse resp = client.call(testcall);
cout << "call result: \n"
<< resp.getXml(0);
Another, more flexible possibility is it, to set up a simple web server, that handles complete http transactions.
As a side effect it does not only handle XML-RPC requests but also passes files to a web browser or stores/deletes files which is also part of the HTTP standard.
// accept connects from "localhost" on port 80 (web browser access)
HttpConnection conn (0x7f000001, 80);
HttpProtocol prot(&conn, conn.getHostName());
HttpServer http_server (&prot);
// this the internal root of the http server
http_server.setHttpRoot("/http-directory/public_html");
// setup an xml-rpc dispatcher
Dispatcher rpc_server(&conn);
// add method including signature for parameters and description
rpc_server.addMethod(&testcall,
"struct",
"testcall",
"int, int"
"Testcase with a c-function");
// let the http server know about the rpc-dispatcher
http_server.setRpcDispatcher(&rpc_server);
// run forever...
http_server.runPicoHttpd();
The client finally may access the server via the http protocol to store, retrieve and delete files. At the same time it might of course send xml-rpc requests.
HttpConnection conn ("localhost", 32000);
HttpProtocol prot(&conn, conn.getHostName());
HttpClient client(&prot);
string s;
// simulate web browser and get html file
s = client.msgGET("/index.html");
// store a file on the server
client.msgPUT("hallo put", "text/plain", "/putmsg.txt");
// delete a file on the server
client.doDELETE("/putmsg.txt");
The default implementation of the http server class only handles requests to existing files below the http root directory. This means that only static content on the hard disk can be processed.
But often you must process virtual content which is derived from volatile data in the memory of some application. You can extend the functionality in several ways. One of them might be to derive a new class and handle everything "manually".
But instead of this you should use the helper classes that come with
ulxmlrpcpp
: handlers for http requests and for processing form data and
html content.
There are several types of requests you can send to a http server. The most common ones are the following.
You can add handlers for each of these methods to a http server object. Each handler is usually responsible for a master resource. You can think of a master resource as some sort of directory. Each master resource contains one or more subresources which are similar to files.
class RpcInfoHandler : public ulxr::HtmlFormHandler
{
public:
// create a handler for resources below "/rpc-info/"
RpcInfoHandler()
: ulxr::HtmlFormHandler("/rpc-info/")
{
// add subresources for several names which are routed to member methods
addSubResource("", this, &RpcInfoHandler::handle_index);
addSubResource("index.html", this, &RpcInfoHandler::handle_index);
}
ulxr::CppString handle_index(const ulxr::HtmlFormData &formdata, ulxr::CppString &mimetype)
{
// set mime type for returned data
mimetype = "text/html";
ulxr::CppString s;
// move "index" data to return string
return s;
}
}
...
// add handlers for requests to html resources
http_server.addHttpHandler("get", ulxr::make_methodhandler(rpcinfo, &RpcInfoHandler::handler));
http_server.addHttpHandler("post", ulxr::make_methodhandler(rpcinfo, &RpcInfoHandler::handler));
Dealing with html files is often an error prone task. The helper class contains methods to generate the most common html sequences for anchors, forms and more.
// create a standard html start sequence
resp = getHeader("Index");
// append a check box with unique name and according value
resp += makeCheckBox(makeIdent("method_enable_", i), "on", meth->isEnabled());
// append the standard html tail sequence
resp += getTail();
When you have to process form data you have to extract the according values. This may happen in the following manner:
ulxr::CppString handle_calc(const ulxr::HtmlFormData &formdata, ulxr::CppString &mimetype)
{
mimetype = "text/html";
// check for existing and complete form data
if ( formdata.size() != 0
&& formdata.hasElement("lhs")
&& formdata.hasElement("rhs")
&& formdata.hasElement("operator")
)
{
// extract the values
int l = getLong(formdata.getElement("lhs")[0]);
int r = getLong(formdata.getElement("rhs")[0]);
// and process the values
...
}
// generate response based on new data
...
}
ulxmlrpcpp
provides two ways to add security to your transmissions.
First, you can use basic authentication as used with HTTP.
But caution: This offers very little security. Think of it only as a protection against unintentional access, for example from web browsers. This way the browser shows the user an authentication dialog and he is aware of the forbidden area.
// The server adds some users to it's own realm.
// "realm" usually describes a subdirectory on the server.
string realm = "SecureRPCRealm";
ulxr::HttpConnection conn (true, 0x7f000001, 32001);
ulxr::HttpProtocol prot(&conn, conn.getHostName());
ulxr::HttpServer http_server (&prot);
http_server.addRealm("/RPC-Sec", "SecureRPCRealm");
http_server.addAuthentication("ali-baba", "open-sesame", "SecureRPCRealm");
...
// The rpc client uses the authenticated server this way
ulxr::MethodResponse resp = client.call(secureDispatcher, "/SecureRPC", "ali-baba", "open-sesame");
// A http client may set user and password for all http transmissions like this:
client.setMessageAuthentication("ali-baba", "open-sesame");
The second and more important step is to encrypt your calls and responses with strong cryptography.
The idea with ulxmlrpcpp is to:
This way you can use the same methods as usual. It is just that you put your confidental data encrypted in an envelope which you can send visibly over public networks. The machine on the other side decrypts the data and does a second processing stage.
With ulxmlrpcpp
you do nothing special, you just repeat some steps.
See secure_server.cpp
and secure_client.cpp
for almost complete
examples how the whole might look like.
The client part is almost equal to the single threaded version. The only thing you have to do is to provide an additional parameter which contains a method that handles the response. This can be a normal function or a member of an object:
ulxr::MethodCall testcall ("testcall");
ulxr::HttpConnection conn (false, host, port);
ulxr::HttpProtocol prot(&conn, conn.getHostName());
ulxr::Requester client(&prot);
client.call(testcall, "/RPC2", ulxr::make_receiver(lister_func));
The call()
) method
sends the call and then creates a thread to start this method which is expected to handle the
return value or maybe an error message. So the response handler should
look something like this:
void lister_func (const ulxr::MethodResponse &resp)
{
if (!resp.isOK())
{
// handle this error
...
return;
}
// Lock this area if neccessary
ulxr::Mutex::Locker locker(lister_mutex);
// extract and process the return values
unsigned num = ulxr::Integer(resp.getResult()).getInteger();
....
}
Starting the multithreaded server is a bit more complicated than the single threaded. First you have to create the server object and tell it how many threads you want to have. Then you start the dispatcher threads. Afterwards you can either wait for all the threads to terminate or do something more useful until the termination.
// add rpc methods
...
// create the server object
ulxr::MultiThreadRpcServer server(protocol, num_threads);
// start all threads
unsigned started = server.dispatchAsync();
// do something useful
// wait for all threads to finish before terminating the application
server.waitAsync(false, true);
One problem is it currently to terminate the server. Since I have not yet found a portable and good solution to make the threads terminate regularly while waiting for connections I usually use a shutdown method which I invoke via xml rpc. This method has to be sent to all threads. See the server example files. One big drawbacks is that you have to secure this method against attacks.
To create a multithreaded http server you need to create an according server object with the number of desired threads. Then you start all the threads and after maybe doing something useful you must wait for the termination of all the threads.
// setup connection and server object
ulxr::HttpConnection conn (true, host, port);
ulxr::HttpProtocol prot(&conn, conn.getHostName());
ulxr::HttpServer http_server (&prot, num_threads);
// setup the xml rpc dispatcher which is handled by the http server
ulxr::Dispatcher rpc_server;
http_server.setRpcDispatcher(&rpc_server);
rpc_server.addMethod(&testcall,
ulxr::Struct::getValueName(),
"testcall",
ulxr::Integer::getValueName() + "," + ulxr::Integer::getValueName(),
"Testcase with a c-function");
// add some handlers for requests to html resources
http_server.addHttpHandler("get", ulxr::make_methodhandler(rpcinfo, &RpcInfoHandler::handler));
http_server.addHttpHandler("post", ulxr::make_methodhandler(rpcinfo, &RpcInfoHandler::handler));
// realms and authentication data
http_server.addRealm("/", "http-root-resource");
http_server.addAuthentication("ali-baba",
"open-sesame",
"http-root-resource");
http_server.addRealm("/RPC2", "rpc2-resource");
http_server.addAuthentication("ali-baba-rpc",
"open-sesame-rpc",
"rpc2-resource");
// set the http root
ulxr::CppString root_dir = "/usr/local/ulxmlrpcpp/public_html";
http_server.setHttpRoot(root_dir);
// start the threads
unsigned started = http_server.runPicoHttpd();
// do something useful
// wait for all the threads to terminate
http_server.waitAsync(false, true);
}
ulxmlrpcpp
uses expat
as parser for xml. There are some helper classes.
One of them forms a C++ wrapper to the C-functions
in expat
and one supports parsing using a simple state machine. Nested xml elements
are modeled by deriving parser classes: "outer" elements derive from "inner" elements.
Parsing of a start tag is done in the following steps:
Character data between the xml tags is stored for later use.
An ending tag is handled similarly:
For examples on the working method see the various *parse.cpp files.
There is one pitfall: The first constructor in the chain must push an appropriate element onto the state stack because the parser methods rely upon being able to retrieve information about the current state.
ULXR_EXPORT ValueParser::ValueParser()
{
states.push(new ValueState(eNone));
}