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!

9 comments:

  1. Very clear article. I like Wt, too.

    ReplyDelete
  2. This article is packed with useful information and actionable advice.business analyst course online

    ReplyDelete
  3. Build job-ready DevOps skills through an online DevOps course featuring live instructor-led sessions, practical assignments, and real-world implementation of DevOps concepts.

    ReplyDelete
  4. IBM DataStage training helps learners master enterprise ETL development and data integration using IBM's powerful platform. This ibm datastage training covers job development, parallel processing, metadata management, workflow automation, and performance tuning. Students gain hands-on experience through real-time projects and practical assignments.

    ReplyDelete
  5. Thanks for sharing this valuable information on MuleSoft training. A structured MuleSoft course with hands-on practice can help learners gain practical skills in designing APIs, connecting applications, and developing scalable integration solutions.mulesoft training

    ReplyDelete
  6. Finding the right Software Training Institute in Hyderabad helps learners get access to expert trainers, practical sessions, and job-oriented courses. Thanks for sharing this useful guide.Software Training institute in hyderabad

    ReplyDelete