fastcgi++
Delayed response

Tutorial

Our goal here will be to make a FastCGI application that responds to clients with some text, waits five seconds and then sends more. We're going to use threading and boost::asio to handle our timer. 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++ -lboost_system

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. In this examples we still need to derive from Fastcgipp::Request and define the Fastcgipp::Request::response() function, but also some more. We're also going to need some member data to keep track of our requests execution state, a default constructor to initialize it and a global boost::asio::io_service object. In this example let's just use plain old ISO-8859-1 and pass char as the template parameter.

#include <cstring>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/scoped_ptr.hpp>
boost::asio::io_service io;
class Timer: public Fastcgipp::Request<char>
{
private:

We'll start with our data to keep track of the execution state and a pointer for our timer object.

enum State { START, FINISH } state;
boost::scoped_ptr<boost::asio::deadline_timer> t;

Now we can define our response function. It is this function that is called to generate a response for the client. We'll start it off with a switch statement that tests our execution state. 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()
{
switch(state)
{
case START:
{

First thing we'll do is output our HTTP header. Note the charset=ISO-8859-1. Remember that HTTP headers must be terminated with "\r\n\r\n". NOT just "\n\n".

out << "Content-Type: text/html; charset=ISO-8859-1\r\n\r\n";

Some standard HTML header output

out << "<html><head><meta http-equiv='Content-Type' content='text/html; charset=ISO-8859-1' />";
out << "<title>fastcgi++: Threaded Timer</title></head><body>";

Output a message saying we are starting the timer

out << "Starting Timer...<br />";

If we want to the client to see everything that we just outputted now instead of when the request is complete, we will have to flush the stream buffer as it's just sitting in there now.

out.flush();

Now let's make a five second timer.

t.reset(new boost::asio::deadline_timer(io, boost::posix_time::seconds(5)));

Now we work with our Fastcgipp::Request::callback(). It is a boost::function that takes a Fastcgipp::Message as a single argument. This callback function will pass the message on to this request thereby having Fastcgipp::Request::response() called function again. The callback function is thread safe. That means you can pass messages back to requests from other threads.

First we'll build the message we want sent back here. Normally the message would be built by whatever is calling the callback, but this is just a simple example. A type of 0 means a FastCGI record and is used internally. All other values we can use ourselves to define different message types (sql queries, file grabs, etc...). In this example we will use type=1 for timer stuff.

msg.type=1;
{
char cString[] = "I was passed between two threads!!";
msg.size=sizeof(cString);
msg.data.reset(new char[sizeof(cString)]);
std::strncpy(msg.data.get(), cString, sizeof(cString));
}

Now we can give our callback function to our timer.

t->async_wait(boost::bind(callback(), msg));

We need to set our state to FINISH so that when this response is called a second time, we don't repeat this.

state=FINISH;

Now we will return and allow the task manager to do other things (or sleep if there is nothing to do). We must return false if the request is not yet complete.

return false;
}

Next step, we define what's done when Fastcgipp::Request::responce() is called a second time.

case FINISH:
{

Whenever Fastcgipp::Request::response() is called, the Fastcgipp::Message that lead to it's calling is stored in Fastcgipp::Request::message.

out << "Timer Finished! Our message data was \"" << message.data.get() << "\"";
out << "</body></html>";

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 true;
}
}
return true;
}

Now we can define our default constructor.

public:
Timer(): state(START) {}
};

Requests Manager

Now we need to make our main() function. In addition to creating a Fastcgipp::Manager object with the new class we made as a template parameter and calling it's handler, we also need to set up our threads and io stuff. This isn't a boost::asio tutorial, check the boost documentation for clarification on it.

int main()
{
try
{
boost::asio::io_service::work w(io);
boost::thread t(boost::bind(&boost::asio::io_service::run, &io));
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 <cstring>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/scoped_ptr.hpp>
boost::asio::io_service io;
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 Timer: public Fastcgipp::Request<char>
{
private:
enum State { START, FINISH } state;
boost::scoped_ptr<boost::asio::deadline_timer> t;
bool response()
{
switch(state)
{
case START:
{
out << "Content-Type: text/html; charset=ISO-8859-1\r\n\r\n";
out << "<html><head><meta http-equiv='Content-Type' content='text/html; charset=ISO-8859-1' />";
out << "<title>fastcgi++: Threaded Timer</title></head><body>";
out << "Starting Timer...<br />";
out.flush();
t.reset(new boost::asio::deadline_timer(io, boost::posix_time::seconds(5)));
msg.type=1;
{
char cString[] = "I was passed between two threads!!";
msg.size=sizeof(cString);
msg.data.reset(new char[sizeof(cString)]);
std::strncpy(msg.data.get(), cString, sizeof(cString));
}
t->async_wait(boost::bind(callback(), msg));
state=FINISH;
return false;
}
case FINISH:
{
out << "Timer Finished! Our message data was \"" << message.data.get() << "\"";
out << "</body></html>";
return true;
}
}
return true;
}
public:
Timer(): state(START) {}
};
int main()
{
try
{
boost::asio::io_service::work w(io);
boost::thread t(boost::bind(&boost::asio::io_service::run, &io));
fcgi.handler();
}
catch(std::exception& e)
{
error_log(e.what());
}
}