fastcgi++
A C++ FastCGI/Web API
Echo

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 wide characters internally and have the library code convert it to utf-8 before sending it to the client. It covers the following topics:

You can build it with

make echo.fcgi

Walkthrough

First we'll define our request.

#include <iomanip>
class Echo: public Fastcgipp::Request<wchar_t>
{

We will now use the Fastcgipp::Request::Request argument to set our maximum upload size to 5 kiB so as to avoid this output being too large. The default argument for this constructor is 0 thereby prohibiting POST data for any request class that uses the base class's default constructor. If the client attempts send a POST request larger than 5 kiB they will receive a HTTP error 413. This response can be changed by overriding Fastcgipp::Request::bigPostErrorHandler().

public:
Echo():
Fastcgipp::Request<wchar_t>(5*1024)
{}
private:
bool response()
{

Normally the default constructor for Fastcgipp::Request would suffice

We'll do the following to have direct access to the Fastcgipp::Encoding stream manipulator.

Now let's set up the HTTP header. We will also set a weird cookie. Notice the use of the Fastcgipp::Encoding manipulator. In this context we are telling the stream buffer to URL encode all insertions until a new encoding manipulator is inserted.

out << L"Set-Cookie: echoCookie=" << Encoding::URL << L"<\"русский\">;"
<< Encoding::NONE << L"; path=/\n";
out << L"Content-Type: text/html; charset=utf-8\r\n\r\n";

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

out <<
L"<!DOCTYPE html>\n"
L"<html>"
L"<head>"
L"<meta charset='utf-8' />"
L"<title>fastcgi++: Echo</title>"
L"</head>"
L"<body>"
L"<h1>Echo</h1>";

Now we are ready to start outputting environment data. We'll start with the simple environment() members. This data is defined and initialized in the environment() object which is of type Fastcgipp::Http::Environment.

out <<
L"<h2>Environment Parameters</h2>"
L"<p>"
L"<b>FastCGI Version:</b> "
L"<b>fastcgi++ Version:</b> " << Fastcgipp::version << L"<br />"
L"<b>Hostname:</b> " << Encoding::HTML << environment().host
<< Encoding::NONE << L"<br />"
L"<b>User Agent:</b> " << Encoding::HTML << environment().userAgent
<< Encoding::NONE << L"<br />"
L"<b>Accepted Content Types:</b> " << Encoding::HTML
<< environment().acceptContentTypes << Encoding::NONE
<< L"<br />"
L"<b>Accepted Languages:</b> " << Encoding::HTML;
if(!environment().acceptLanguages.empty())
{
auto language = environment().acceptLanguages.cbegin();
while(true)
{
out << language->c_str();
++language;
if(language == environment().acceptLanguages.cend())
break;
out << ',';
}
}
out << Encoding::NONE << L"<br />"
L"<b>Accepted Characters Sets:</b> " << Encoding::HTML
<< environment().acceptCharsets << Encoding::NONE << L"<br />"
L"<b>Referer:</b> " << Encoding::HTML << environment().referer
<< Encoding::NONE << L"<br />"
L"<b>Content Type:</b> " << Encoding::HTML
<< environment().contentType << Encoding::NONE << L"<br />"
L"<b>Root:</b> " << Encoding::HTML << environment().root
<< Encoding::NONE << L"<br />"
L"<b>Script Name:</b> " << Encoding::HTML
<< environment().scriptName << Encoding::NONE << L"<br />"
L"<b>Request URI:</b> " << Encoding::HTML
<< environment().requestUri << Encoding::NONE << L"<br />"
L"<b>Request Method:</b> " << environment().requestMethod
<< L"<br />"
L"<b>Content Length:</b> " << environment().contentLength
<< L" bytes<br />"
L"<b>Keep Alive Time:</b> " << environment().keepAlive
<< L" seconds<br />"
L"<b>Server Address:</b> " << environment().serverAddress
<< L"<br />"
L"<b>Server Port:</b> " << environment().serverPort << L"<br />"
L"<b>Client Address:</b> " << environment().remoteAddress << L"<br />"
L"<b>Client Port:</b> " << environment().remotePort << L"<br />"
L"<b>Etag:</b> " << environment().etag << L"<br />"
L"<b>If Modified Since:</b> " << Encoding::HTML
<< std::put_time(std::gmtime(&environment().ifModifiedSince),
L"%a, %d %b %Y %H:%M:%S %Z") << Encoding::NONE <<
L"</p>";

Next we will take a look at what path info we were sent. This is available as a vector of strings.

out <<
L"<h2>Path Info</h2>";
if(environment().pathInfo.size())
{
out <<
L"<p>";
std::wstring preTab;
for(const auto& element: environment().pathInfo)
{
out << preTab << Encoding::HTML << element << Encoding::NONE
<< L"<br />";
preTab += L"&nbsp;&nbsp;&nbsp;&nbsp;";
}
out <<
L"</p>";
}
else
out <<
L"<p>No Path Info</p>";

Now let's take a look at the GET data. The GET data is stored in an associative multimap container linking names to values.

out <<
L"<h2>GET Data</h2>";
if(environment().gets.size())
for(const auto& get: environment().gets)
out << L"<b>" << Encoding::HTML << get.first << Encoding::NONE
<< L":</b> " << Encoding::HTML << get.second
<< Encoding::NONE << L"<br />";
else
out <<
L"<p>No GET data</p>";

Now let's take a look at the POST data. The POST data is, unsurprisingly, stored in an associative multimap container linking names to values.

out <<
L"<h2>POST Data</h2>";
if(environment().posts.size())
for(const auto& post: environment().posts)
out << L"<b>" << Encoding::HTML << post.first << Encoding::NONE
<< L":</b> " << Encoding::HTML << post.second
<< Encoding::NONE << L"<br />";
else
out <<
L"<p>No POST data</p>";

Now let's take a look at the cookie data. The cookie data is, as well, stored in an associative multimap container linking names to values.

out <<
L"<h2>Cookies</h2>";
if(environment().cookies.size())
for(const auto& cookie: environment().cookies)
out << L"<b>" << Encoding::HTML << cookie.first
<< Encoding::NONE << L":</b> " << Encoding::HTML
<< cookie.second << Encoding::NONE << L"<br />";
else
out <<
L"<p>No Cookies</p>";

And now we shall take a look at the files that were submitted to us. This again is an associated multimap container but although the key is a string, the value is the Fastcgipp::Http::File data structure.

out <<
L"<h2>Files</h2>";
if(environment().files.size())
{
for(const auto& file: environment().files)
{
out <<
L"<h3>" << Encoding::HTML << file.first << Encoding::NONE << L"</h3>"
L"<p>"
L"<b>Filename:</b> " << Encoding::HTML << file.second.filename
<< Encoding::NONE << L"<br />"
L"<b>Content Type:</b> " << Encoding::HTML
<< file.second.contentType << Encoding::NONE << L"<br />"
L"<b>Size:</b> " << file.second.size << L"<br />"
L"<b>Data:</b>"
L"</p>"
L"<pre>";

When we wish to output some binary data we obviously wish to avoid using the stream buffers code conversion and encoding mechanisms and output direct. To actually display the file we will use the Fastcgipp::Request::dump() function. This dumps the file data directly to the client.

dump(file.second.data.get(), file.second.size);
out <<
L"</pre>";
}
}
else
out <<
L"<p>No files</p>";

Now let's output a simple form for sending data to this application

out <<
L"<h1>Form</h1>"
L"<h3>multipart/form-data</h3>"
L"<form action='?getVar=testing&amp;secondGetVar=tested&amp;"
L"utf8GetVarTest=проверка&amp;enctype=multipart' method='post' "
L"enctype='multipart/form-data' accept-charset='utf-8'>"
L"Name: <input type='text' name='+= aquí está el campo' value='Él "
L"está con un niño' /><br />"
L"File: <input type='file' name='aFile' /> <br />"
L"<input type='submit' name='submit' value='submit' />"
L"</form>"
L"<h3>application/x-www-form-urlencoded</h3>"
L"<form action='?getVar=testing&amp;secondGetVar=tested&amp;"
L"utf8GetVarTest=проверка&amp;enctype=url-encoded' method='post' "
L"enctype='application/x-www-form-urlencoded' "
L"accept-charset='utf-8'>"
L"Name: <input type='text' name='+= aquí está el campo' value='Él "
L"está con un niño' /><br />"
L"File: <input type='file' name='aFile' /><br />"
L"<input type='submit' name='submit' value='submit' />"
L"</form>";

And the rest is all familiar

out <<
L"</body>"
L"</html>";
return true;
}
};
int main()
{
manager.setupSignals();
manager.listen();
manager.start();
manager.join();
return 0;
}

Full Source Code

#include <iomanip>
class Echo: public Fastcgipp::Request<wchar_t>
{
public:
Echo():
Fastcgipp::Request<wchar_t>(5*1024)
{}
private:
bool response()
{
out << L"Set-Cookie: echoCookie=" << Encoding::URL << L"<\"русский\">;"
<< Encoding::NONE << L"; path=/\n";
out << L"Content-Type: text/html; charset=utf-8\r\n\r\n";
out <<
L"<!DOCTYPE html>\n"
L"<html>"
L"<head>"
L"<meta charset='utf-8' />"
L"<title>fastcgi++: Echo</title>"
L"</head>"
L"<body>"
L"<h1>Echo</h1>";
out <<
L"<h2>Environment Parameters</h2>"
L"<p>"
L"<b>FastCGI Version:</b> "
L"<b>fastcgi++ Version:</b> " << Fastcgipp::version << L"<br />"
L"<b>Hostname:</b> " << Encoding::HTML << environment().host
<< Encoding::NONE << L"<br />"
L"<b>User Agent:</b> " << Encoding::HTML << environment().userAgent
<< Encoding::NONE << L"<br />"
L"<b>Accepted Content Types:</b> " << Encoding::HTML
<< environment().acceptContentTypes << Encoding::NONE
<< L"<br />"
L"<b>Accepted Languages:</b> " << Encoding::HTML;
if(!environment().acceptLanguages.empty())
{
auto language = environment().acceptLanguages.cbegin();
while(true)
{
out << language->c_str();
++language;
if(language == environment().acceptLanguages.cend())
break;
out << ',';
}
}
out << Encoding::NONE << L"<br />"
L"<b>Accepted Characters Sets:</b> " << Encoding::HTML
<< environment().acceptCharsets << Encoding::NONE << L"<br />"
L"<b>Referer:</b> " << Encoding::HTML << environment().referer
<< Encoding::NONE << L"<br />"
L"<b>Content Type:</b> " << Encoding::HTML
<< environment().contentType << Encoding::NONE << L"<br />"
L"<b>Root:</b> " << Encoding::HTML << environment().root
<< Encoding::NONE << L"<br />"
L"<b>Script Name:</b> " << Encoding::HTML
<< environment().scriptName << Encoding::NONE << L"<br />"
L"<b>Request URI:</b> " << Encoding::HTML
<< environment().requestUri << Encoding::NONE << L"<br />"
L"<b>Request Method:</b> " << environment().requestMethod
<< L"<br />"
L"<b>Content Length:</b> " << environment().contentLength
<< L" bytes<br />"
L"<b>Keep Alive Time:</b> " << environment().keepAlive
<< L" seconds<br />"
L"<b>Server Address:</b> " << environment().serverAddress
<< L"<br />"
L"<b>Server Port:</b> " << environment().serverPort << L"<br />"
L"<b>Client Address:</b> " << environment().remoteAddress << L"<br />"
L"<b>Client Port:</b> " << environment().remotePort << L"<br />"
L"<b>Etag:</b> " << environment().etag << L"<br />"
L"<b>If Modified Since:</b> " << Encoding::HTML
<< std::put_time(std::gmtime(&environment().ifModifiedSince),
L"%a, %d %b %Y %H:%M:%S %Z") << Encoding::NONE <<
L"</p>";
out <<
L"<h2>Path Info</h2>";
if(environment().pathInfo.size())
{
out <<
L"<p>";
std::wstring preTab;
for(const auto& element: environment().pathInfo)
{
out << preTab << Encoding::HTML << element << Encoding::NONE
<< L"<br />";
preTab += L"&nbsp;&nbsp;&nbsp;&nbsp;";
}
out <<
L"</p>";
}
else
out <<
L"<p>No Path Info</p>";
out <<
L"<h2>GET Data</h2>";
if(environment().gets.size())
for(const auto& get: environment().gets)
out << L"<b>" << Encoding::HTML << get.first << Encoding::NONE
<< L":</b> " << Encoding::HTML << get.second
<< Encoding::NONE << L"<br />";
else
out <<
L"<p>No GET data</p>";
out <<
L"<h2>POST Data</h2>";
if(environment().posts.size())
for(const auto& post: environment().posts)
out << L"<b>" << Encoding::HTML << post.first << Encoding::NONE
<< L":</b> " << Encoding::HTML << post.second
<< Encoding::NONE << L"<br />";
else
out <<
L"<p>No POST data</p>";
out <<
L"<h2>Cookies</h2>";
if(environment().cookies.size())
for(const auto& cookie: environment().cookies)
out << L"<b>" << Encoding::HTML << cookie.first
<< Encoding::NONE << L":</b> " << Encoding::HTML
<< cookie.second << Encoding::NONE << L"<br />";
else
out <<
L"<p>No Cookies</p>";
out <<
L"<h2>Files</h2>";
if(environment().files.size())
{
for(const auto& file: environment().files)
{
out <<
L"<h3>" << Encoding::HTML << file.first << Encoding::NONE << L"</h3>"
L"<p>"
L"<b>Filename:</b> " << Encoding::HTML << file.second.filename
<< Encoding::NONE << L"<br />"
L"<b>Content Type:</b> " << Encoding::HTML
<< file.second.contentType << Encoding::NONE << L"<br />"
L"<b>Size:</b> " << file.second.size << L"<br />"
L"<b>Data:</b>"
L"</p>"
L"<pre>";
dump(file.second.data.get(), file.second.size);
out <<
L"</pre>";
}
}
else
out <<
L"<p>No files</p>";
out <<
L"<h1>Form</h1>"
L"<h3>multipart/form-data</h3>"
L"<form action='?getVar=testing&amp;secondGetVar=tested&amp;"
L"utf8GetVarTest=проверка&amp;enctype=multipart' method='post' "
L"enctype='multipart/form-data' accept-charset='utf-8'>"
L"Name: <input type='text' name='+= aquí está el campo' value='Él "
L"está con un niño' /><br />"
L"File: <input type='file' name='aFile' /> <br />"
L"<input type='submit' name='submit' value='submit' />"
L"</form>"
L"<h3>application/x-www-form-urlencoded</h3>"
L"<form action='?getVar=testing&amp;secondGetVar=tested&amp;"
L"utf8GetVarTest=проверка&amp;enctype=url-encoded' method='post' "
L"enctype='application/x-www-form-urlencoded' "
L"accept-charset='utf-8'>"
L"Name: <input type='text' name='+= aquí está el campo' value='Él "
L"está con un niño' /><br />"
L"File: <input type='file' name='aFile' /><br />"
L"<input type='submit' name='submit' value='submit' />"
L"</form>";
out <<
L"</body>"
L"</html>";
return true;
}
};
int main()
{
manager.setupSignals();
manager.listen();
manager.start();
manager.join();
return 0;
}