Echo

Tutorial

Our goal here will be to make a FastCGI application that responds to clients with an echo of all session data that was processed. This will include HTTP header data along with post data that was transmitted by the client. Since we want to be able to echo any alphabets, our best solution is to use UTF-32 wide characters internally and have the library code convert it to UTF-8 before sending it to the client. Your going to need the boost C++ libraries for this. At least version 1.35.0.

All code and data is located in the examples directory of the tarball. Make sure to link this with the following library options: -lfastcgipp -lboost_thread

Error Handling

Our first step will be setting up an error logging system. Although requests can log errors directly to the HTTP server error log, I like to have an error logging system that's separate from the library when testing applications. Let's set up a function that takes a c style string and logs it to a file with a timestamp. Since everyone has access to the /tmp directory, I set it up to send error messages to /tmp/errlog. You can change it if you want to.

#include <fstream>
#include <boost/date_time/posix_time/posix_time.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;
}

Request Handler

Now we need to write the code that actually handles the request. Quite simply, all we need to do is derive from Fastcgipp::Request and define the Fastcgipp::Request::response() function. Since we've decided to use wide Unicode characters, we need to pass wchar_t as the template parameter to the Request class as opposed to char.

#include <fastcgi++/request.hpp>

class HelloWorld: public Fastcgipp::Request<wchar_t>
{

Now we can define our response function. It is this function that is called to generate a response for the client. It isn't a good idea to define the response() function inline as it is called from numerous spots, but for the examples readability we will make an exception.

   bool response()
   {

First thing we need to do is output our HTTP header. Let us start with settting a cookie in the client using a Unicode string. Just to see how it echoes.

      wchar_t langString[] = { 0x0440, 0x0443, 0x0441, 0x0441, 0x043a, 0x0438, 0x0439, 0x0000 };
      out << "Set-Cookie: lang=" << langString << '\n';

Next of course we need to output our content type part of the header. Note the charset=utf-8. Remember that HTTP headers must be terminated with "\r\n\r\n". Not just "\n\n".

      out << "Content-Type: text/html; charset=utf-8\r\n\r\n";

Next we'll get some initial HTML stuff out of the way

      out << "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' />";
      out << "<title>fastcgi++: Echo in UTF-8</title></head><body>";

Now we are ready to start outputting session data. We'll start with the non-post session data. This data is defined and initialized in the session object which is of type Fastcgipp::Http::Session.

      out << "<h1>Session Parameters</h1>";
      out << "<p><b>Hostname:</b> " << session.host << "<br />";
      out << "<b>User Agent:</b> " << session.userAgent << "<br />";
      out << "<b>Accepted Content Types:</b> " << session.acceptContentTypes << "<br />";
      out << "<b>Accepted Languages:</b> " << session.acceptLanguages << "<br />";
      out << "<b>Accepted Characters Sets:</b> " << session.acceptCharsets << "<br />";
      out << "<b>Referer:</b> " << session.referer << "<br />";
      out << "<b>Content Type:</b> " << session.contentType << "<br />";
      out << "<b>Query String:</b> " << session.queryString << "<br />";
      out << "<b>Cookies:</b> " << session.cookies << "<br />";
      out << "<b>Root:</b> " << session.root << "<br />";
      out << "<b>Script Name:</b> " << session.scriptName << "<br />";
      out << "<b>Content Length:</b> " << session.contentLength << "<br />";
      out << "<b>Keep Alive Time:</b> " << session.keepAlive << "<br />";
      out << "<b>Server Address:</b> " << session.serverAddress << "<br />";
      out << "<b>Server Port:</b> " << session.serverPort << "<br />";
      out << "<b>Client Address:</b> " << session.remoteAddress << "<br />";
      out << "<b>Client Port:</b> " << session.remotePort << "<br />";
      out << "<b>If Modified Since:</b> " << session.ifModifiedSince << "</p>";

Next, we will make a little loop to output the post data. The post data is stored in the associative container session.posts of type Fastcgipp::Http::Session::Posts linking field names to Fastcgipp::Http::Post objects.

      out << "<h1>Post Data</h1>";

If there isn't any POST data, we'll just say so

      if(session.posts.size())
         for(Fastcgipp::Http::Session<wchar_t>::Posts::iterator it=session.posts.begin(); it!=session.posts.end(); ++it)
         {
            out << "<h2>" << it->first << "</h2>";

            if(it->second.type==Fastcgipp::Http::Post<wchar_t>::form)
            {
               out << "<p><b>Type:</b> form data<br />";
               out << "<b>Value:</b> " << it->second.value << "</p>";
            }
            
            else
            {
               out << "<p><b>Type:</b> file<br />";

When the post type is a file, the filename is stored in Post::value

               out << "<b>Filename:</b> " << it->second.value << "<br />";
               out << "<b>Size:</b> " << it->second.size << "<br />";
               out << "<b>Data:</b></p><pre>";

We will use Fastcgipp::Fcgistream::dump to send the file raw data directly to the client

               out.dump(it->second.data.get(), it->second.size);
               out << "</pre>";
            }
         }
      else
         out << "<p>No post data</p>";

And we're basically done defining our response! All we need to do is return a boolean value. Always return true if you are done. This will let apache and the manager know we are done so they can destroy the request and free it's resources. Return false if you are not finished but want to relinquish control and allow other requests to operate. You would do this if the request needed to wait for a message to be passed back to it through the task manager.

      return true;
   }
};

Requests Manager

Now we need to make our main() function. Really all one needs to do is create a Fastcgipp::Manager object with the new class we made as a template parameter, then call it's handler. Let's go one step further though and set up a try/catch loop in case we get any exceptions and log them with our error_log function.

#include <fastcgi++/manager.hpp>

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

Full Source Code

#include <fstream>
#include "boost/date_time/posix_time/posix_time.hpp"
#include <fastcgi++/request.hpp>
#include <fastcgi++/manager.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 Echo: public Fastcgipp::Request<wchar_t>
{
   bool response()
   {
      wchar_t langString[] = { 0x0440, 0x0443, 0x0441, 0x0441, 0x043a, 0x0438, 0x0439, 0x0000 };
      out << "Set-Cookie: lang=" << langString << '\n';

      out << "Content-Type: text/html; charset=utf-8\r\n\r\n";

      out << "<html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8' />";
      out << "<title>fastcgi++: Echo in UTF-8</title></head><body>";

      out << "<h1>Session Parameters</h1>";
      out << "<p><b>Hostname:</b> " << session.host << "<br />";
      out << "<b>User Agent:</b> " << session.userAgent << "<br />";
      out << "<b>Accepted Content Types:</b> " << session.acceptContentTypes << "<br />";
      out << "<b>Accepted Languages:</b> " << session.acceptLanguages << "<br />";
      out << "<b>Accepted Characters Sets:</b> " << session.acceptCharsets << "<br />";
      out << "<b>Referer:</b> " << session.referer << "<br />";
      out << "<b>Content Type:</b> " << session.contentType << "<br />";
      out << "<b>Query String:</b> " << session.queryString << "<br />";
      out << "<b>Cookies:</b> " << session.cookies << "<br />";
      out << "<b>Root:</b> " << session.root << "<br />";
      out << "<b>Script Name:</b> " << session.scriptName << "<br />";
      out << "<b>Content Length:</b> " << session.contentLength << "<br />";
      out << "<b>Keep Alive Time:</b> " << session.keepAlive << "<br />";
      out << "<b>Server Address:</b> " << session.serverAddress << "<br />";
      out << "<b>Server Port:</b> " << session.serverPort << "<br />";
      out << "<b>Client Address:</b> " << session.remoteAddress << "<br />";
      out << "<b>Client Port:</b> " << session.remotePort << "<br />";
      out << "<b>If Modified Since:</b> " << session.ifModifiedSince << "</p>";

      out << "<h1>Post Data</h1>";
      if(session.posts.size())
         for(Fastcgipp::Http::Session<wchar_t>::Posts::iterator it=session.posts.begin(); it!=session.posts.end(); ++it)
         {
            out << "<h2>" << it->first << "</h2>";
            if(it->second.type==Fastcgipp::Http::Post<wchar_t>::form)
            {
               out << "<p><b>Type:</b> form data<br />";
               out << "<b>Value:</b> " << it->second.value << "</p>";
            }
            
            else
            {
               out << "<p><b>Type:</b> file<br />";
               out << "<b>Filename:</b> " << it->second.value << "<br />";
               out << "<b>Size:</b> " << it->second.size << "<br />";
               out << "<b>Data:</b></p><pre>";
               out.dump(it->second.data.get(), it->second.size);
               out << "</pre>";
            }
         }
      else
         out << "<p>No post data</p>";

      out << "</body></html>";
      return true;
   }
};

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

Generated on Thu Dec 11 01:47:28 2008 for fastcgi++ by  doxygen 1.5.7.1