fastcgi++
Display The Gnu

Tutorial

Our goal here is simple and easy. All we want to do is show the gnu.png file and effectively utilize client caching.

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 Logging

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're just outputting an image, we don't need to bother with Unicode and can pass char as the template parameter.

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
class ShowGnu: public Fastcgipp::Request<char>
{

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 std;
using namespace boost;

We are going to use boost::posix_time::ptime to communicate the images modification time for cache purposes.

posix_time::ptime modTime;
int fileSize;
int etag;

We'll use the POSIX stat() function (man 2 stat) to get the modification time, file size and inode number.

{
struct stat fileStat;
stat("gnu.png", &fileStat);
fileSize = fileStat.st_size;
modTime = posix_time::from_time_t(fileStat.st_mtime);

Fastcgipp::Http::Environment implements the etag variable as an integer for better processing efficiency.

etag = fileStat.st_ino;
}

We will need to call Fastcgipp::Request::setloc() to set a facet in our requests locale regarding how to format the date upon insertion. It needs to conform to the HTTP standard. When setting locales for the streams, make sure to use the Fastcgipp::Request::setloc() function instead of directly imbueing them. This insures that the UTF-8 code conversion still functions properly if used.

setloc(locale(getloc(), new posix_time::time_facet("%a, %d %b %Y %H:%M:%S GMT")));

If the modification time of the file is older or equal to the if-modified-since value sent to us from the client and the etag matches, we don't need to send the image to them.

if(!environment().ifModifiedSince.is_not_a_date_time() && etag==environment().etag && modTime<=environment().ifModifiedSince)
{
out << "Status: 304 Not Modified\r\n\r\n";
return true;
}

We're going to use std::fstream to read the file data.

ifstream image("gnu.png");

Now we transmit our HTTP header containing the modification data, file size and etag value.

out << "Last-Modified: " << modTime << '\n';
out << "Etag: " << etag << '\n';
out << "Content-Length: " << fileSize << '\n';
out << "Content-Type: image/png\r\n\r\n";

Now that the header is sent, we can transmit the actual image. To send raw binary data to the client, the streams have a dump function that bypasses the stream buffer and it's code conversion. The function is overloaded to either Fastcgipp::Fcgistream::dump(std::basic_istream<char>& stream) or Fastcgipp::Fcgistream::dump(char* data, size_t size). Remember that if we are using wide characters internally, the stream converts anything sent into the stream to UTF-8 before transmitting to the client. If we want to send binary data, we definitely don't want any code conversion so that is why this function exists.

out.dump(image);

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.

int main()
{
try
{
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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.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 ShowGnu: public Fastcgipp::Request<char>
{
bool response()
{
using namespace std;
using namespace boost;
posix_time::ptime modTime;
int fileSize;
int etag;
{
struct stat fileStat;
stat("gnu.png", &fileStat);
fileSize = fileStat.st_size;
modTime = posix_time::from_time_t(fileStat.st_mtime);
etag = fileStat.st_ino;
}
setloc(locale(getloc(), new posix_time::time_facet("%a, %d %b %Y %H:%M:%S GMT")));
if(!environment().ifModifiedSince.is_not_a_date_time() && etag==environment().etag && modTime<=environment().ifModifiedSince)
{
out << "Status: 304 Not Modified\r\n\r\n";
return true;
}
std::ifstream image("gnu.png");
out << "Last-Modified: " << modTime << '\n';
out << "Etag: " << etag << '\n';
out << "Content-Length: " << fileSize << '\n';
out << "Content-Type: image/png\r\n\r\n";
out.dump(image);
return true;
}
};
int main()
{
try
{
fcgi.handler();
}
catch(std::exception& e)
{
error_log(e.what());
}
}