SOAP and REST are two ways to export functionality over the web. Both have their strongness and weakness. SOAP enforces a strict type checking on input and output parameters. It works with a formal description file called WSDL that specifies all the exposed functionality and how to invoke this. Many tools exist that can read WSDL files and create client code that uses the exposed functions.
REST on the other hand, is much easier to use ad hoc. It passes the arguments to the invoked functions in the URL and uses standard GET, POST and PUT methods of the HTTP protocol.
libzeep is mainly focussed to using SOAP, but allows to access the exported functionality in a REST like way. Not all functions can be accessed this way, if the input parameters are some complex type, you're out of luck. This documentation will focus on SOAP, but if the function is simple, you can test it using REST from a browser.
Creating a SOAP server using libzeep is very easy. The bulk of the work is done by libzeep, you only have to specify what methods to expose and optionally what datatypes.
To demonstrate this, we will create a simple SOAP server that allows the client to search for documents in a databank. Lets start with the initial code, the declaration of our server object.
#include <zeep/server.hpp>
using namespace std;
class MyServer : public zeep::server
{
public:
struct MyHit
{
long id;
float score;
string title;
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar & BOOST_SERIALIZATION_NVP(id) & BOOST_SERIALIZATION_NVP(score) & BOOST_SERIALIZATION_NVP(title);
}
};
enum MyAlgorithm
{
algVector, algDice, algJaccard
};
MyServer();
void CountDocuments(long& outCount);
void GetDocument(long inID, string& outDocument);
void FindDocument(const vector<string>& inTerms, MyAlgorithm inAlgorithm,
vector<MyHit>& outResponse);
};
Nothing special so far. Apart from inheriting from zeep::server
, this code could have
been code you already had lying around. The addition of the serialize
method to MyHit
may also have been new to the code. The implementation of the actual server
methods are also straightforward:
void MyServer::CountDocuments(long& outCount) { long count = 1; // real code should return something more sensible of course outCount = count; } void MyServer::GetDocument(long inID, string& outDocument) { if (inID == 1) outDocument = "The first document!"; else throw zeep::exception("document %ld not found", inID); } void MyServer::FindDocument(const vector<string>& inTerms, MyAlgorithm inAlgorithm, vector<MyHit>& outResponse) { if (inTerms.size() == 1 and inAlgorithm == algVector) { MyHit hit = { 1, 1.0f, "The first hit" }; outResponse.push_back(hit); } }
Not very useful code, but it gives you an idea how simple it is to create a server. You don't have to do anything special, it's still code you could have written for some other purpose. Note that the GetDocument method throws an exception. The result in a SOAP server will be a SOAP Fault being returned containing the text 'document x not found'.
Unfortunately, this is not all that needs to be done, we still have to tell
libzeep what methods and what datatypes to expose. That's what we do in the
constructor for MyServer
.
MyServer::MyServer()
: zeep::server
("http://www.example.org/MyServer", "searchMyServer")
{
// first export the data types, start with MyHit
zeep::xml::serialize_struct<MyHit>::set_struct_name("hit");
// and then the MyAlgorithm enum
zeep::xml::enum_map<MyAlgorithm>::instance("algorithm").add_enum()
("vector", algVector)
("dice", algDice)
("jaccard", algJaccard);
// Now export the methods, start with CountDocuments
const char* kCountDocumentsParamNames[] = { "response" };
register_action("CountDocuments", this, &MyServer::CountDocuments, kCountDocumentsParamNames);
// then GetDocument
const char* kGetDocumentParamNames[] = { "id", "response" };
register_action("GetDocument", this, &MyServer::GetDocument, kGetDocumentParamNames);
const char* kFindDocumentParamNames[] = { "terms", "algorithm", "response" };
register_action("FindDocument", this, &MyServer::FindDocument, kFindDocumentParamNames);
}
We start the constructor by calling the constructor of our base class, zeep::server
. We then continue by
exporting the data types. Our MyHit datatype is exported under the name 'hit'
and MyAlgorithm is exported as 'algorithm'. The various values of MyAlgorithm
are exported under a new name as well.
After exporting the datatypes, we export the methods. We do this by calling
register_action
specifying
the parameters for the exported method name, the callback to make and the
names for the parameters. And that's all. All that's left is to write a
main
.
int main() { MyServer server; server.bind("0.0.0.0", 80); server.run(1); }
And that will run our code in a single threaded server. If you run this code on your local machine you can test the REST versions of the code by visiting the following URL's with a web browser:
http://localhost/rest/CountDocuments Will return a SOAP envelope containing 1 in a response element
http://localhost/rest/GetDocument/id/1
http://localhost/rest/GetDocument/id/2 Will return a SOAP Fault
http://localhost/rest/FindDocument/terms/bla/algorithm/vector