rest webservice cplus

Build Comics shop based on RESTful webservice (C++ Webservice Part 2)

BUILDING SHOP

We are going to design a dummy system in order to implementate our RESTful web service in C++. This system is very simple: An owner of a comics shop asked us to implementate a system for him. Our system will have only one entity/resource: The comic itself. A comic has:
+ A name;
+ A publisher;
+ A release date;
+ An edition number.
We are going to use MySQL to persist those data. You can dowload MySQL here. Alternativelly you can use command on Ubuntu:

sudo apt-get install MySQL-server

Let’s initialize MySQL by calling on terminal:

mysql -u USERNAME -p

Once logged in, create a new database named “comics_shop” by calling:

CREATE DATABASE comics_shop;

Now select the recently created database:

USE comics_shop;

And finally let’s create a table for “comic”:

CREATE TABLE comic(
  id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(30) NOT NULL,
  publisher VARCHAR(30) NOT NULL,
  date TIMESTAMP,
  edition INT
);

Aaaaaand the database part is done for now! Now, in order to establish the communication among our C++ programs and our MySQL database, we need to install the MySQL C++ connector. You can download it here. Once downloaded, unpack it and move the content of “lib” folder to your /usr/lib folder, while moving the content of “include” to /usr/local/include/mysqlcppconn folder.

Now go to /var/www/html and create a new folder called “comics”. The first method we are going to implementate is the CREATE/POST. For that reason, inside the recently created folder, create a new folder named “comic” and inside of it create a new .cpp file named “post.cpp”. Let’s start by creating the skeleton of our application:

#include <fstream>

#include <boost/date_time/posix_time/posix_time.hpp>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fastcgi++/request.hpp>
#include <fastcgi++/manager.hpp>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

void error_log(const char* msg)
{
  using namespace std;
  using namespace boost;
  static ofstream error;
  if(!error.is_open())
  {
      error.open("/tmp/errlog", ios_base::out | ios_base::app);
      error.imbue(locale(error.getloc(), new posix_time::time_facet()));
  }
  error << '[' << posix_time::second_clock::local_time() << "] " << msg << endl;
}

class PostComic : public Fastcgipp::Request<char>
{
  bool response()
  {
      // TODO
      return true;
  }
};

int main()
{
  try
  {
      Fastcgipp::Manager<PostComic> fcgi;
      fcgi.handler();
  }
  catch (std::exception& e)
  {
          error_log(e.what());  
  }
  return 0;
}

The method “error_log” is one created only for logging reasons. It creates a new file inside the /tmp/errlog folder containing the date and the error message.

The very first thing we are going to implementate is the connection with our database. Since it’s something which will be repeated for every HTTP method, let’s create a separated file named “connector.hpp” with the same skeleton as shown above:

#include <fstream>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fastcgi++/request.hpp>
#include <fastcgi++/manager.hpp>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

class Connector : public Fastcgipp::Request<char>
{

};

We will then, on constructor, simply initialize a new connection with the database. Here is constructor code (put inside above class):

public:
  Connector()
  {
      driver = get_driver_instance();
      con = driver->connect("tcp://127.0.0.1:3306", "USERNAME", "PASSWORD");
      con->setSchema("comics_shop");
  }
  virtual ~Connector()
  {
      delete con;
  }
protected:
  sql::Driver* driver;
  sql::Connection* con;

And after that, we’ll modify our PostComic class to extend the Connector class. You just modify above post.cpp.

#include <fstream>

#include <boost/date_time/posix_time/posix_time.hpp>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fastcgi++/request.hpp>
#include <fastcgi++/manager.hpp>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

#include "connector.hpp"

void error_log(const char* msg)
{
  using namespace std;
  using namespace boost;
  static ofstream error;
  if(!error.is_open())
  {
      error.open("/tmp/errlog", ios_base::out | ios_base::app);
      error.imbue(locale(error.getloc(), new posix_time::time_facet()));
  }
  error << '[' << posix_time::second_clock::local_time() << "] " << msg << endl;
}

class PostComic : public Connector
{
  bool response()
  {
      // TODO
      return true;
  }
};

int main()
{
  try
  {
      Fastcgipp::Manager<PostComic> fcgi;
      fcgi.handler();
  }
  catch (std::exception& e)
  {
          error_log(e.what());  
  }
  return 0;
}

Now let’s get the POST parameters. We expect the parameters to be the same of the table attributes (except for ID). We can access the post parameters through the environment().posts list:

bool response()
{
  out << "Content-Type: application/json; charset=ISO-8859-1\r\n\r\n";
  std::map<std::string, std::string> parameters;
  if (environment().posts.size())
  {
      for (Fastcgipp::Http::Environment<char>::Posts::const_iterator it = environment().posts.begin(); it != environment().posts.end(); ++it)
      {
          parameters[it->first] = it->second.value;
      }
  }
  return true;
}

In the above example, we are saving the parameters in a map. Now let’s check if all parameters are OK. Put this code in post.cpp.

inline void sendError(const std::string& errorMsg)
{
  out << "{ \"success\" : 0, \"message\" : \"" + errorMsg + "\" }" << std::endl;
}
inline void sendSuccess()
{
  out << "{ \"success\" : 1 }" << std::endl;
}
bool response()
{
  out << "Content-Type: application/json; charset=ISO-8859-1\r\n\r\n";
  std::map<std::string, std::string> parameters;
  for (Fastcgipp::Http::Environment<char>::Posts::const_iterator it = environment().posts.begin(); it != environment().posts.end(); ++it)
  {
      parameters[it->first] = it->second.value;
  }
  if (parameters.find("name") == parameters.end())
  {
      sendError("Name is missing");
  }
  else if (parameters.find("publisher") == parameters.end())
  {
      sendError("Publisher is missing");
  }
  else if (parameters.find("date") == parameters.end())
  {
      sendError("Date is missing");
  }
  else if (parameters.find("edition") == parameters.end())
  {    
      sendError("Edition is missing");
  }
  else
  {        
      // TODO
  }
  return true;
}

As you can notice, we are sending a text in JSON format indicating if the operation was successful or not. Using JSON or XML formats as a protocol of communitation is another remarking aspect of REST architectures.

Now compile it using:

sudo g++ post.cpp -I/usr/local/include/mysqlcppconn/ -lfastcgipp -lboost_date_time -lboost_system -lboost_thread -lmysqlcppconn -o post.fcgi

The other methods follow a very similar logic. For example, here’s the PUT (edit) method in file put.cpp:

#include <fstream>

#include <boost/date_time/posix_time/posix_time.hpp>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fastcgi++/request.hpp>
#include <fastcgi++/manager.hpp>
#include <fastcgi++/http.hpp>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

#include "connector.hpp"

void error_log(const char* msg)
{
  using namespace std;
  using namespace boost;
  static ofstream error;
  if(!error.is_open())
  {
      error.open("/tmp/errlog", ios_base::out | ios_base::app);
      error.imbue(locale(error.getloc(), new posix_time::time_facet()));
  }
  error << '[' << posix_time::second_clock::local_time() << "] " << msg << endl;
}

class PutComic : public Connector
{
  inline void sendError(const std::string& errorMsg)
  {
      out << "{ \"success\" : 0, \"message\" : \"" + errorMsg + "\" }" << std::endl;
  }
  inline void sendSuccess()
  {
      out << "{ \"success\" : 1 }" << std::endl;
  }
  bool response()
  {
      out << "Content-Type: application/json; charset=ISO-8859-1\r\n\r\n";
      std::map<std::string, std::string> parameters;
      for (Fastcgipp::Http::Environment<char>::Posts::const_iterator it = environment().posts.begin(); it != environment().posts.end(); ++it)
      {
          parameters[it->first] = it->second.value;
      }
      if (parameters.find("id") == parameters.end())
      {
          sendError("Missing id");
      }
      else
      {
          std::map<std::string, std::string> columns;
          if (parameters.find("name") != parameters.end())
          {
              columns["name"] = "\"" + parameters["name"] + "\"";
          }
          if (parameters.find("publisher") != parameters.end())
          {
              columns["publisher"] = "\"" + parameters["publisher"] + "\"";
          }
          if (parameters.find("date") != parameters.end())
          {
              columns["date"] = "FROM_UNIXTIME('" + parameters["date"] + "')";
          }
          if (parameters.find("edition") != parameters.end())
          {    
              columns["edition"] = parameters["edition"];
          }        
          if (columns.empty())
          {
              sendError("There is no column to be updated");
          }
          else
          {
              std::string query = "UPDATE comic SET ";
              for (std::map<std::string, std::string>::iterator it = columns.begin(); it != columns.end(); ++it)
              {
                  if (it != columns.begin()) query += ", ";
                  query += it->first + "=" + it->second;
              }
              query += " WHERE id=" + parameters["id"];
              sql::Statement* stmt = con->createStatement();
              try
              {
                  stmt->execute(query);
                  sendSuccess();
              } catch (sql::SQLException& e)
              {
                  sendError(e.what());
              }
              delete stmt;
          }
      }
      return true;
  }
};

int main()
{
  try
  {
      Fastcgipp::Manager<PutComic> fcgi;
      fcgi.handler();
  }
  catch (std::exception& e)
  {
          error_log(e.what());  
  }
  return 0;
}

We are simply getting a row by ID and then updating the columns from which values exist on the POST request (unhappily, FastCGI++ doesn’t provide support for PUT requests, so we have to implement it as a POST request).

The DELETE method is even simpler (in delete.cpp):

#include <fstream>

#include <boost/date_time/posix_time/posix_time.hpp>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fastcgi++/request.hpp>
#include <fastcgi++/manager.hpp>
#include <fastcgi++/http.hpp>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

#include "connector.hpp"

void error_log(const char* msg)
{
  using namespace std;
  using namespace boost;
  static ofstream error;
  if(!error.is_open())
  {
      error.open("/tmp/errlog", ios_base::out | ios_base::app);
      error.imbue(locale(error.getloc(), new posix_time::time_facet()));
  }
  error << '[' << posix_time::second_clock::local_time() << "] " << msg << endl;
}

class PutComic : public Connector
{
  inline void sendError(const std::string& errorMsg)
  {
      out << "{ \"success\" : 0, \"message\" : \"" + errorMsg + "\" }" << std::endl;
  }
  inline void sendSuccess()
  {
      out << "{ \"success\" : 1 }" << std::endl;
  }
  bool response()
  {
      out << "Content-Type: application/json; charset=ISO-8859-1\r\n\r\n";
      std::map<std::string, std::string> parameters;
      for (Fastcgipp::Http::Environment<char>::Posts::const_iterator it = environment().posts.begin(); it != environment().posts.end(); ++it)
      {
          parameters[it->first] = it->second.value;
      }
      if (parameters.find("id") == parameters.end())
      {
          sendError("Missing id");
      }
      else
      {

          sql::Statement* stmt = con->createStatement();
          try
          {
              stmt->execute("DELETE FROM comic WHERE id = " + parameters["id"]);
              sendSuccess();
          } catch (sql::SQLException& e)
          {
              sendError(e.what());
          }
          delete stmt;
      }
      return true;
  }
};

int main()
{
  try
  {
      Fastcgipp::Manager<PutComic> fcgi;
      fcgi.handler();
  }
  catch (std::exception& e)
  {
          error_log(e.what());  
  }
  return 0;
}

Now the only method missing is the GET, to retrieve informations of a comic by ID. That’s also very simple (put in get.cpp):

#include <fstream>

#include <boost/date_time/posix_time/posix_time.hpp>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fastcgi++/request.hpp>
#include <fastcgi++/manager.hpp>
#include <fastcgi++/http.hpp>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/resultset.h>
#include <cppconn/statement.h>

#include "connector.hpp"

void error_log(const char* msg)
{
  using namespace std;
  using namespace boost;
  static ofstream error;
  if(!error.is_open())
  {
      error.open("/tmp/errlog", ios_base::out | ios_base::app);
      error.imbue(locale(error.getloc(), new posix_time::time_facet()));
  }
  error << '[' << posix_time::second_clock::local_time() << "] " << msg << endl;
}

class PutComic : public Connector
{
  inline void sendError(const std::string& errorMsg)
  {
      out << "{ \"success\" : 0, \"message\" : \"" + errorMsg + "\" }" << std::endl;
  }
  bool response()
  {
      out << "Content-Type: application/json; charset=ISO-8859-1\r\n\r\n";
      std::map<std::string, std::string> parameters;
      for (Fastcgipp::Http::Environment<char>::Gets::const_iterator it = environment().gets.begin(); it != environment().gets.end(); ++it)
      {
          parameters[it->first] = it->second;
      }
      if (parameters.find("id") == parameters.end())
      {
          sendError("Missing id");
      }
      else
      {
          sql::Statement* stmt = con->createStatement();
          try
          {
              sql::ResultSet* res = stmt->executeQuery("SELECT name, publisher, UNIX_TIMESTAMP(date) as date, edition FROM comic WHERE id = " + parameters["id"]);
              if (!res->next())
              {
                  sendError("Could not found comic with id = " + parameters["id"]);
              }
              else
              {
                  std::string result = "{ \"success\" : 1, ";
                  result += "\"name\": \"" + res->getString("name") + "\",";
                  result += "\"publisher\": \"" + res->getString("publisher") + "\",";
                  result += "\"date\": " + res->getString("date") + ",";
                  result += "\"edition\": " + res->getString("edition");
                  result += "}";
                  delete res;
                  out << result << std::endl;
              }
          } catch (sql::SQLException& e)
          {
              sendError(e.what());
          }
          delete stmt;
      }
      return true;
  }
};

int main()
{
  try
  {
      Fastcgipp::Manager<PutComic> fcgi;
      fcgi.handler();
  }
  catch (std::exception& e)
  {
          error_log(e.what());  
  }
  return 0;
}

Instead of environment().posts, now we are using environment().gets. Also, “get” parameters are a pair of string-string (instead of a POST, where the second value of the pair is an object), that’s why we don’t need to use it->second.value. sql::ResultSet representates a set of retrieved rows. Since we are indexing by the primary key, it will just return either 0 or 1 rows (that’s why we don’t need to put it on a loop). If the method getNext() return false, it indicates which none row with given ID was found, otherwise, we build a JSON string with the columns values and then output it to the user.

REWRITING URLS

So we finally finished our four methods (GET, POST, PUT and DELETE), but one of annoying thing is that you must put the “.fcgi” extension in order to access the page. A more elegant solution would be, instead of GET /comics/comic.fcgi?id=10, the following: GET /comics/10. It’s shorter and now the user don’t need to know we are using a FCGI script. URL rewriting is completely possible on Apache Web Server. You just need to do the following:

sudo a2enmod rewrite
sudo service apache2 restart

Now, on folder “comics” located on “/var/www/html”, create a new .htaccess file, and inside of it write the following:

RewriteEngine on
RewriteRule comic/([0-9]+)$ comic/get.fcgi?id=$1

Now if you save it, you’ll notice that calling the URL http://localhost/comics/comic/10 has the same effect as http://localhost/comics/comic/get.fcgi?id=10. The reason is simple: We created a mapping rule where, if you call comics/comic/<A number>, it will internally call the right URL defined on RewriteRule. $1 is the Regex ID (1 = the first one), from which the value will be ‘copied’. The other mapping rules are much easier:

RewriteEngine on
RewriteRule comic/([0-9]+)$ comic/get.fcgi?id=$1
RewriteRule comic/put comic/put.fcgi
RewriteRule comic/delete comic/delete.fcgi
RewriteRule comic/post comic/post.fcgi

Webservice C++: Part 1

Thank you Abner Matheus for helpful article about C++ Webservice.