Tuesday, May 7, 2013

Creating a REST web service using webtoolkit (aka witty)

I previously introduced witty as a powerful web framework for C++ developers, mostly those one that don't want to re-invent the wheel and become productive.  Witty uses a resource-based approach to register services that listen on specific paths (resource paths), these resources are registered on the witty server first and the server is started later.

So the steps of an standalone witty w/ custom resource server:

  1. create a server
  2. register a custom resource on a desired path
  3. start the server, hence its resources
  4. wait for server shutdown signal
  5. stop the server

Here is the snippet of the main.cpp,  ignore all log-related plumbing:


#include <Wt/WServer>
#include <iostream>
#include "MyResource.h"
#include "log.h"

using namespace std;
using namespace Wt;

int main(int argc, char **argv) {
 WLogger logger;
 configLogger(logger);
 try {
  WServer server(argv[0], "");
  try {
   server.setServerConfiguration(argc, argv);
   MyResource dr;
   server.addResource(&dr, "/resource");
   info(logger, "Starting resource server.");
   if (server.start()) {
    WServer::waitForShutdown();
    server.stop();
   } else {
    fatal(logger, "Fatal error starting resource server.");
    return 1;
   }
   return 0;
  } catch (std::exception& e) {
   fatal(logger, "Fatal error starting resource server.", e.what());
   return 1;
  }
 } catch (WServer::Exception& e) {
  fatal(logger, "Fatal error creating WServer.", e.what());
  return 1;
 } catch (exception& e) {
  fatal(logger, "Fatal error occurred.", e.what());
  return 1;
 }
}

You may notice the creation and registration of MyResource, this is place where you provide the service implementation, specifically on the handleRequest() method, take a look at MyResource.h:

#ifndef MYRESOURCE_H_
#define MYRESOURCE_H_

#include <Wt/WResource>

using namespace Wt;
using namespace Wt::Http;

class MyResource: public WResource {
 public:
  MyResource();
  virtual ~MyResource();
 protected:
  virtual void handleRequest(const Request &request, Response &response);
};

#endif /* MYPRESOURCE_H_ */

The implemantation is very trivial, it's just an echo of the request content also including some extra data like content type, content length and request method: GET, POST, etc. Enjoy MyResource.cpp:
#include <iostream>
#include <Wt/Http/Response>
#include "MyResource.h"

using namespace std;
using namespace Wt::Http;

MyResource::MyResource() {
}

MyResource::~MyResource() {
}

void MyResource::handleRequest(const Request& request, Response& response) {
 string method = request.method();
 string contentType = request.contentType();
 int contentLength = request.contentLength();
 char* buffer = new char[contentLength + 1];
 request.in().read(buffer, contentLength);
 buffer[contentLength] = 0;
 response.setMimeType("application/xml");
 ostream& out = response.out();
 out << "<?xml version='1.0' encoding='utf-8' ?>" << endl;
 out << "<reply>" << endl;
 out << "<method>" << method << "</method>" << endl; 
 out << "<contentType>" << contentType << "</contentType>" << endl;
 out << "<contentLenght>" << contentLength << "</contentLenght>" << endl;
 out << "<body>" << buffer << "</body>" << endl;
 out << "</reply>";
 delete[] buffer;
}


The reamining stuff is just plumbing as I said before, here you got log.h:
#ifndef LOG_H_
#define LOG_H_

#include <Wt/WLogger>

using namespace std;
using namespace Wt;

void info(WLogger& logger, const string& message);
void fatal(WLogger& logger, const string& message, const char* what);
void fatal(WLogger& logger, const string& message);
void configLogger(WLogger& logger);

#endif /* LOG_H_ */

And log.cpp:
#include "log.h"
#include <iostream>

void info(WLogger& logger, const string& message) {
 WLogEntry entry = logger.entry("info");
 entry << WLogger::timestamp << WLogger::sep << WLogger::sep << '[' << "info"
   << ']' << WLogger::sep << message;
}
void fatal(WLogger& logger, const string& message, const char* what) {
 WLogEntry entry = logger.entry("fatal");
 entry << WLogger::timestamp << WLogger::sep << WLogger::sep << '['
  << "fatal" << ']' << WLogger::sep << message << what;
}

void fatal(WLogger& logger, const string& message) {
 fatal(logger, message, "");
}

void configLogger(WLogger& logger) {
 logger.addField("datetime", false);
 logger.addField("type", false);
 logger.addField("message", true);
 logger.setFile("/var/log/resource.log");
}

Now compile an run the server, here in this case using standalone server mode instead of fast cgi mode, but also works w/ fast cgi variant:
$ g++ log.cpp MyResource.cpp main.cpp -lwthttp -oresource

Run the server:
$ ./resource --http-address 0.0.0.0 --http-port 80 --docroot=.
INFO: Opened log file (/var/log/resource.log).
[2013-May-07 19:52:11.046985] 9658 - [info] "config: reading Wt config file: /etc/wt/wt_config.xml (location = './resource')"
[2013-May-07 19:52:11.047757] 9658 - [info] "WServer/wthttp: initializing built-in wthttpd"
[2013-May-07 19:52:11.048027] 9658 - [info] "wthttp: started server: http://0.0.0.0:80"

Finally test the service using a simple call:
$ curl -X POST -H "Content-Type: application/xml" -d"<payload>PAYLOAD GOES HERE!</payload>" http://localhost/resource
<?xml version='1.0' encoding='utf-8' ?>
<reply>
<method>POST</method>
<contentType>application/xml</contentType>
<contentLenght>37</contentLenght>
<body><payload>PAYLOAD GOES HERE!</payload></body>
</reply>
Very simple as you can see!

3 comments: