PrevUpHomeNext

Creating a SOAP and REST server

Introduction
A real world example

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


PrevUpHomeNext