fastcgi++
Echo

Tutorial

Our goal here will be to make a FastCGI application that responds to clients with an echo of all environment().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. You'll have to compile with: pkg-config --libs --cflags fastcgi++

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()
   {
      using namespace Fastcgipp;

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 cookieString[] = { '<', '"', 0x0440, 0x0443, 0x0441, 0x0441, 0x043a, 0x0438, 0x0439, '"', '>', ';', 0x0000 };
      out << "Set-Cookie: echoCookie=" << encoding(URL) << cookieString << encoding(NONE) << "; path=/\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 environment().data. We'll start with the non-post environment().data. This data is defined and initialized in the environment().object which is of type Fastcgipp::Http::Environment.

      out << "<h1>Environment Parameters</h1>";
      out << "<p><b>FastCGI Version:</b> " << Protocol::version << "<br />";
      out << "<b>fastcgi++ Version:</b> " << version << "<br />";
      out << "<b>Hostname:</b> " << encoding(HTML) << environment().host << encoding(NONE) << "<br />";
      out << "<b>User Agent:</b> " << encoding(HTML) << environment().userAgent << encoding(NONE) << "<br />";
      out << "<b>Accepted Content Types:</b> " << encoding(HTML) << environment().acceptContentTypes << encoding(NONE) << "<br />";
      out << "<b>Accepted Languages:</b> " << encoding(HTML) << environment().acceptLanguages << encoding(NONE) << "<br />";
      out << "<b>Accepted Characters Sets:</b> " << encoding(HTML) << environment().acceptCharsets << encoding(NONE) << "<br />";
      out << "<b>Referer:</b> " << encoding(HTML) << environment().referer << encoding(NONE) << "<br />";
      out << "<b>Content Type:</b> " << encoding(HTML) << environment().contentType << encoding(NONE) << "<br />";
      out << "<b>Root:</b> " << encoding(HTML) << environment().root << encoding(NONE) << "<br />";
      out << "<b>Script Name:</b> " << encoding(HTML) << environment().scriptName << encoding(NONE) << "<br />";
      out << "<b>Request URI:</b> " << encoding(HTML) << environment().requestUri << encoding(NONE) << "<br />";
      out << "<b>Request Method:</b> " << encoding(HTML) << environment().requestMethod << encoding(NONE) << "<br />";
      out << "<b>Content Length:</b> " << encoding(HTML) << environment().contentLength << encoding(NONE) << "<br />";
      out << "<b>Keep Alive Time:</b> " << encoding(HTML) << environment().keepAlive << encoding(NONE) << "<br />";
      out << "<b>Server Address:</b> " << encoding(HTML) << environment().serverAddress << encoding(NONE) << "<br />";
      out << "<b>Server Port:</b> " << encoding(HTML) << environment().serverPort << encoding(NONE) << "<br />";
      out << "<b>Client Address:</b> " << encoding(HTML) << environment().remoteAddress << encoding(NONE) << "<br />";
      out << "<b>Client Port:</b> " << encoding(HTML) << environment().remotePort << encoding(NONE) << "<br />";
      out << "<b>If Modified Since:</b> " << encoding(HTML) << environment().ifModifiedSince << encoding(NONE) << "</p>";

First we will take a look at what path info we were sent

      out << "<h1>Path Data</h1>";
      if(environment().pathInfo.size())
      {
         std::wstring preTab;
         for(Http::Environment<wchar_t>::PathInfo::const_iterator it=environment().pathInfo.begin(); it!=environment().pathInfo.end(); ++it)
         {
            out << preTab << encoding(HTML) << *it << encoding(NONE) << "<br />";
            preTab += L"&nbsp;&nbsp;&nbsp;";
         }
      }
      else
         out << "<p>No Path Info</p>";

Now let's take a look at the GET data. The GET data is stored in the associative container environment().gets which is of type std::map< std::basic_string< charT >, std::basic_string< charT > > links names to values.

      out << "<h1>GET Data</h1>";
      if(environment().gets.size())
         for(Fastcgipp::Http::Environment<wchar_t>::Gets::const_iterator it=environment().gets.begin(); it!=environment().gets.end(); ++it)
            out << "<b>" << encoding(HTML) << it->first << encoding(NONE) << ":</b> " << encoding(HTML) << it->second << encoding(NONE) << "<br />";
      else
         out << "<p>No GET data</p>";

Now let's take a look at the cookie data. The cookie data is stored in the associative container environment().cookies which is of type std::map< std::basic_string< charT >, std::basic_string< charT > > links names to values.

      out << "<h1>Cookie Data</h1>";
      if(environment().cookies.size())
         for(Fastcgipp::Http::Environment<wchar_t>::Cookies::const_iterator it=environment().cookies.begin(); it!=environment().cookies.end(); ++it)
            out << "<b>" << encoding(HTML) << it->first << encoding(NONE) << ":</b> " << encoding(HTML) << it->second << encoding(NONE) << "<br />";
      else
         out << "<p>No Cookie data</p>";

Next, we will make a little loop to output the post. The post data is stored in the associative container environment().posts of type Fastcgipp::Http::Environment::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(environment().posts.size())
         for(Fastcgipp::Http::Environment<wchar_t>::Posts::const_iterator it=environment().posts.begin(); it!=environment().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> " << encoding(HTML) << it->second.value << encoding(NONE) << "</p>";
            }
            
            else
            {
               out << "<p><b>Type:</b> file<br />";

When the post type is a file, we have some additional information

               out << "<b>Filename:</b> " << encoding(HTML) << it->second.filename << encoding(NONE) << "<br />";
               out << "<b>Content Type:</b> " << encoding(HTML) << it->second.contentType << encoding(NONE) << "<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()
   {
      using namespace Fastcgipp;
      wchar_t cookieString[] = { '<', '"', 0x0440, 0x0443, 0x0441, 0x0441, 0x043a, 0x0438, 0x0439, '"', '>', ';', 0x0000 };

      out << "Set-Cookie: echoCookie=" << encoding(URL) << cookieString << encoding(NONE) << "; path=/\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>Environment Parameters</h1>";
      out << "<p><b>FastCGI Version:</b> " << Protocol::version << "<br />";
      out << "<b>fastcgi++ Version:</b> " << version << "<br />";
      out << "<b>Hostname:</b> " << encoding(HTML) << environment().host << encoding(NONE) << "<br />";
      out << "<b>User Agent:</b> " << encoding(HTML) << environment().userAgent << encoding(NONE) << "<br />";
      out << "<b>Accepted Content Types:</b> " << encoding(HTML) << environment().acceptContentTypes << encoding(NONE) << "<br />";
      out << "<b>Accepted Languages:</b> " << encoding(HTML) << environment().acceptLanguages << encoding(NONE) << "<br />";
      out << "<b>Accepted Characters Sets:</b> " << encoding(HTML) << environment().acceptCharsets << encoding(NONE) << "<br />";
      out << "<b>Referer:</b> " << encoding(HTML) << environment().referer << encoding(NONE) << "<br />";
      out << "<b>Content Type:</b> " << encoding(HTML) << environment().contentType << encoding(NONE) << "<br />";
      out << "<b>Root:</b> " << encoding(HTML) << environment().root << encoding(NONE) << "<br />";
      out << "<b>Script Name:</b> " << encoding(HTML) << environment().scriptName << encoding(NONE) << "<br />";
      out << "<b>Request URI:</b> " << encoding(HTML) << environment().requestUri << encoding(NONE) << "<br />";
      out << "<b>Request Method:</b> " << encoding(HTML) << environment().requestMethod << encoding(NONE) << "<br />";
      out << "<b>Content Length:</b> " << encoding(HTML) << environment().contentLength << encoding(NONE) << "<br />";
      out << "<b>Keep Alive Time:</b> " << encoding(HTML) << environment().keepAlive << encoding(NONE) << "<br />";
      out << "<b>Server Address:</b> " << encoding(HTML) << environment().serverAddress << encoding(NONE) << "<br />";
      out << "<b>Server Port:</b> " << encoding(HTML) << environment().serverPort << encoding(NONE) << "<br />";
      out << "<b>Client Address:</b> " << encoding(HTML) << environment().remoteAddress << encoding(NONE) << "<br />";
      out << "<b>Client Port:</b> " << encoding(HTML) << environment().remotePort << encoding(NONE) << "<br />";
      out << "<b>If Modified Since:</b> " << encoding(HTML) << environment().ifModifiedSince << encoding(NONE) << "</p>";

      out << "<h1>Path Data</h1>";
      if(environment().pathInfo.size())
      {
         std::wstring preTab;
         for(Http::Environment<wchar_t>::PathInfo::const_iterator it=environment().pathInfo.begin(); it!=environment().pathInfo.end(); ++it)
         {
            out << preTab << encoding(HTML) << *it << encoding(NONE) << "<br />";
            preTab += L"&nbsp;&nbsp;&nbsp;";
         }
      }
      else
         out << "<p>No Path Info</p>";

      out << "<h1>GET Data</h1>";
      if(environment().gets.size())
         for(Http::Environment<wchar_t>::Gets::const_iterator it=environment().gets.begin(); it!=environment().gets.end(); ++it)
            out << "<b>" << encoding(HTML) << it->first << encoding(NONE) << ":</b> " << encoding(HTML) << it->second << encoding(NONE) << "<br />";
      else
         out << "<p>No GET data</p>";
      
      out << "<h1>Cookie Data</h1>";
      if(environment().cookies.size())
         for(Http::Environment<wchar_t>::Cookies::const_iterator it=environment().cookies.begin(); it!=environment().cookies.end(); ++it)
            out << "<b>" << encoding(HTML) << it->first << encoding(NONE) << ":</b> " << encoding(HTML) << it->second << encoding(NONE) << "<br />";
      else
         out << "<p>No Cookie data</p>";

      out << "<h1>POST Data</h1>";
      if(environment().posts.size())
      {
         for(Http::Environment<wchar_t>::Posts::const_iterator it=environment().posts.begin(); it!=environment().posts.end(); ++it)
         {
            out << "<h2>" << encoding(HTML) << it->first << encoding(NONE) << "</h2>";
            if(it->second.type==Http::Post<wchar_t>::form)
            {
               out << "<p><b>Type:</b> form data<br />";
               out << "<b>Value:</b> " << encoding(HTML) << it->second.value << encoding(NONE) << "</p>";
            }
            
            else
            {
               out << "<p><b>Type:</b> file<br />";
               out << "<b>Filename:</b> " << encoding(HTML) << it->second.filename << encoding(NONE) << "<br />";
               out << "<b>Content Type:</b> " << encoding(HTML) << it->second.contentType << encoding(NONE) << "<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;
   }
};

// The main function is easy to set up
int main()
{
   try
   {
      Fastcgipp::Manager<Echo> fcgi;
      fcgi.handler();
   }
   catch(std::exception& e)
   {
      error_log(e.what());
   }
}