Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

CGI Examples

Hello world
Echo
Sessions
Cookies
File Browser
Stencil

//
// A simple CGI program.
//
// Outputs only "Hello there, universe." and saves a session cookie "test=check" to the browser.
//

#include <iostream>
#include <boost/cgi/cgi.hpp>

namespace cgi = boost::cgi;

int main()
{
try {
  // Construct a request. Parses all GET, POST and environment data,
  // as well as cookies.
  cgi::request req;
  // Using a response is the simplest way to write data back to the client.
  cgi::response resp;
  
  // This is a minimal response. The cgi::cookie(...) may go before or after
  // the response text.
  resp<< "Hello there, universe."
      << cgi::cookie("test", "check")
      << cgi::charset("ascii")
      ;
  //resp.set(cgi::cookie("test", "check"));

  // Leave this function, after sending the response and closing the request.
  // Returns 0 on success.
  return cgi::commit(req, resp);
  
} catch(std::exception& e) {
  using namespace std;
  cout<< "Content-type: text/plain\r\n\r\n"
      << "Error: " << e.what() << endl;
  return 1;
} catch(...) {
  using namespace std;
  cout<< "Content-type: text/plain\r\n\r\n"
      << "Unexpected exception." << endl;
  return 1;
}
}

See the full source listing.

//
// This example simply echoes all variables back to the user. ie.
// the environment and the parsed GET, POST and cookie variables.
// Note that GET and cookie variables come from the environment
// variables QUERY_STRING and HTTP_COOKIE respectively.
//

#include <boost/cgi/cgi.hpp>

namespace cgi = boost::cgi;

// The styling information for the page, just to make things look nicer.
static const char* gCSS_text =
"body { padding: 0; margin: 3%; border-color: #efe; }"
".var_map_title"
    "{ font-weight: bold; font-size: large; }"
".var_map"
    "{ border: 1px dotted; padding: 2px 3px; margin-bottom: 3%; }"
".var_pair"
    "{ border-top: 1px dotted; overflow: auto; padding: 0; margin: 0; }"
".var_name"
    "{ position: relative; float: left; width: 30%; font-weight: bold; }"
".var_value"
    "{ position: relative; float: left; width: 65%; left: 1%;"
     " border-left: 1px solid; padding: 0 5px 0 5px;"
     " overflow: auto; white-space: pre; }"
;

//
// This function writes the title and map contents to the ostream in an
// HTML-encoded format (to make them easier on the eye).
//
// The request data is all held in std::map<>-like containers and can use the same
// member functions. There are some additional functions included for common
// CGI tasks, such as lexical casting.
//
template<typename OStream, typename Request, typename Map>
void format_map(OStream& os, Request& req, Map& m, const std::string& title)
{
  os<< "<div class=\"var_map\">"
         "<div class=\"var_map_title\">"
    <<       title
    <<   "</div>";

  if (m.empty())
    os<< "<div class=\"var_pair\">EMPTY</div>";
  else
  {
    for (typename Map::const_iterator iter(m.begin()), end(m.end());
         iter != m.end();
         ++iter)
    {
      os<< "<div class=\"var_pair\">"
             "<div class=\"var_name\">"
        <<       iter->first
        <<   "</div>"
             "<div class=\"var_value\">"
        <<       iter->second
             << (req.uploads.count(iter->first) ? " (file)" : "")
        <<   "</div>"
           "</div>";
    }
  }
  os<< "</div>";
}

int main()
{
  // A basic CGI request auto-parses everything (including POST data).
  cgi::request req;
  cgi::response resp;

  // You can also stream text to a response. 
  // All of this just prints out the form 
  resp<< "<html>"
         "<head>"
           "<title>CGI Echo Example</title>"
           "<style type=\"text/css\">"
      <<       gCSS_text <<
           "</style>"
         "<head>"
         "<body>"
           "Request ID = " << req.id() << "<br />"
           "<form method=post enctype=\"multipart/form-data\">"
             "<input type=text name=name value='"
      <<         req.post["name"] << "' />"
             "<br />"
             "<input type=checkbox name=checks value='one'"
             << (req.post.matches("checks", "one") ? " checked='checked'" : "") << " />One"
             "<br />"
             "<input type=checkbox name=checks value='two'"
             << (req.post.matches("checks", "two") ? " checked='checked'" : "") << " />Two"
             "<br />"
             "<input type=checkbox name=checks value='six'"
             << (req.post.matches("checks", "six") ? " checked='checked'" : "") << " />Six"
             "<br />"
             "<input type=checkbox name=checks value='ten'"
             << (req.post.matches("checks", "ten") ? " checked='checked'" : "") << " />Ten"
             "<br />"
             "<input type=text name=hello value='"
      <<         req.post["hello"] << "' />"
             "<br />"
             "<input type=file name=user_file /><br />"
             "<input type=file name=user_file />"
             "<input type=hidden name=cmd value=multipart_test />"
             "<br />";
  // Access file uploads (which are saved to disk).
  if (req.uploads.count("user_file")) {
    cgi::common::form_part& part = req.uploads["user_file"];
    if (!part.filename.empty())
      resp<< "Saved uploaded file to: " << part.path << "<br />";
  }
  resp<< "<input type=submit value=submit />"
         "<br />";
         "</form><p />";

  format_map(resp, req, req.env, "Environment Variables");
  //format_map(resp, req, req.get, "GET Variables");
  format_map(resp, req, req.form, "Form [" + req.method() + "] Variables");
  format_map(resp, req, req.cookies, "Cookie Variables");
  format_map(resp, req, req.uploads, "Uploaded Files");

  // Note that this (and any other) HTTP header can go either before or after
  // the response contents.
  resp<< cgi::content_type("text/html");

  // Send the response to the client that made the request.
  return cgi::commit(req, resp);
}

See the full source listing.

//
// A simple example, using the default session type of a
// std::map<string,string>. Session support is optional and
// you should define BOOST_CGI_ENABLE_SESSIONS to use it.
//

#include <boost/cgi/cgi.hpp>
#include <iostream>

using namespace std;
namespace cgi = boost::cgi;

int main(int, char**)
{
  try
  {
    cgi::request req;
    cgi::response resp;
    
    // You can use pick() to return a default value when the item
    // is not found in the request data.
    if (req.get.pick("clear", "") == "1") {
      req.stop_session();
      resp<< "Cleared session";
      return cgi::commit(req, resp);
    }

    // Start the session. It's safe to call `start_session()` if a session
    // is already open.
    req.start_session();

    // Output the current session data.
    resp<< cgi::content_type("text/plain")
        << "one = " << req.session["one"]
        << ", two = " << req.session["two"]
        << ", ten = " << req.session["ten"];

    // Modify the request session.
    req.session["one"] = "1";
    req.session["two"] = "2";
    req.session["ten"] = "10";

    // The session is saved by `commit()`.
    return cgi::commit(req, resp);
  
  } catch (std::exception& e) {
    cerr<< "Error: " << e.what() << endl;
  }

  cout<< "Content-type: text/html\r\n\r\nAn error occurred.";
}

See the full source listing.

This file uses Google cTemplate to show the benefits of using an HTML template engine [10] .

Using cTemplate to separate displaying the response with figuring out what to respond with allows for flexible applications. The 'templates' (aka. 'stencils') can be text files or strings built into your application so you can provide multiple formats of the same data by simply providing multiple template files.

Of course, if you are familiar with any other languages or any web programming at all then none of this will be new to you: This is Model-View-Controller programming.

#include <boost/cgi/cgi.hpp>
#include <ctemplate/template.h>
#include <boost/throw_exception.hpp>
#include <boost/system/system_error.hpp>
#include <boost/filesystem.hpp>

namespace cgi = boost::cgi;
namespace fs = boost::filesystem;

// The types we use. Only here because this is an example.

// Uses cTemplate, from Google. It's simple and powerful.
typedef ctemplate::Template stencil_type;
// You will usually load a template and then populate variables in it
// using a TemplateDictionary.
typedef ctemplate::TemplateDictionary dictionary_type;

// This function just makes it easier to change the templating engine. It's
// only here to keep the cTemplate code out of the core of this example...
stencil_type* get_stencil(std::string const& filename)
{
  if (!fs::exists(filename))
    throw std::runtime_error(
      std::string("Template file not found: '")
        + fs::path(filename).string() + "'");
  else
    return ctemplate::Template::GetTemplate(
      filename, ctemplate::STRIP_WHITESPACE);
}

// Show the data in the passed map, updating the passed dictionary.
template<typename MapT, typename Dict>
void print_formatted_data(MapT& data, Dict& dict)
{
  Dict* subd = dict.AddSectionDictionary("DATA_MAP");
  if (data.empty())
    subd->ShowSection("EMPTY");
  else
    for(typename MapT::const_iterator iter=data.begin(), end = data.end(); iter != end; ++iter)
    {
      Dict* row_dict = subd->AddSectionDictionary("ROW");
      row_dict->SetValue("NAME", iter->first.c_str());
      row_dict->SetValue("VALUE", iter->second.c_str());
      row_dict->ShowSection("ROW");
    }
}


int main()
{
  try {
    cgi::request req;

    cgi::response resp;

    // Check if we are resetting the user.
    if (req.form.count("reset") && req.form["reset"] == "true")
    {
      resp<< cgi::cookie("name") // delete the 'name' cookie.
          << cgi::redirect(req, req.script_name()); // redirect them.
      return cgi::commit(req, resp);
    }

    if (req.form.count("name"))
    {
      // If requested by the user, delete the cookie.
      if (req.form.count("del"))
        resp<< cgi::cookie(req.form["name"]);
      else // Set the cookie.
        resp<< cgi::cookie(req.form["name"], req.form["value"]);
      resp<< cgi::redirect(req, req.script_name());
      // Exit here.
      return cgi::commit(req, resp);
    }

    dictionary_type dict("cookie-game dict");

    // First, see if they have a cookie set
    if (req.cookies.count("name"))
      dict.SetValueAndShowSection("USER_NAME", req.cookies["name"].c_str(),
        "HAS_NAME_IN_COOKIE_true");
    else
      dict.ShowSection("HAS_NAME_IN_COOKIE_false");

    print_formatted_data(req.cookies, dict);

    dict.SetValue("SCRIPT_NAME", req.script_name());
    // pick() looks up the key in the map, returns a default value
    // (ie. anonymous) if the key isn't found.
    dict.SetValue("COOKIE_NAME", req.form["name"]);
    dict.SetValue("COOKIE_VALUE", req.form["value"]);

    // Load the HTML stencil now from the index.html file.
    stencil_type* stencil = get_stencil("../stencils/index.html");

    // Expand the stencil with the the given dictionary into `output`.
    std::string output;
    stencil->Expand(&output, &dict);

    // Add the template to the response.
    resp<< cgi::content_type("text/html")
        << output;

    // Send the response to the requestor and return control.
    return cgi::commit(req, resp);

  }catch(boost::system::system_error& err){
    std::cerr<< "System Error: [" << err.code() << "] - " << err.what() << std::endl;
  }catch(std::exception const& e){
    std::cerr<< "Exception: [" << typeid(e).name() << "] - " << e.what() << std::endl;
  }catch(...){
    std::cerr<< "boom<blink>!</blink>";
  }
}

See the full source listing.

//
// This example is a simple browser-based file browser.
//
///////////////////////////////////////////////////////////
#include <istream>
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/shared_array.hpp>
#include <boost/algorithm/string/find.hpp>
#include <boost/algorithm/string/case_conv.hpp>
///////////////////////////////////////////////////////////
#include "boost/cgi/cgi.hpp"

using std::cerr;
using std::endl;
using std::ifstream;
using namespace boost::cgi;
namespace fs = boost::filesystem;
namespace algo = boost::algorithm;


/// Get the MIME type of the file. 
/**
 * @returns The MIME type as a string. Returns an empty string if the
 *          file type isn't recognised / supported.
 */
std::string get_mime_type(fs::path const& file)
{
  std::string filetype (file.filename());
  // Note: we want the string after the '.'
  std::size_t pos (filetype.rfind(".")+1);
  if (pos == std::string::npos)
    return "";
  
  filetype = filetype.substr(pos);
  algo::to_lower(filetype);
  
  /// Ordinary text files.
  if (filetype == "ini" || filetype == "txt" || filetype == "conf")
    return "text/plain";  
  else
  if (filetype == "js")
    return "application/javascript";
  else
  if (filetype == "json")
    return "application/json";
  else
  if (filetype == "css")
    return "text/css";
  else
  /// Structured text files.
  if (filetype == "html" || filetype == "htm")
    return "text/html";
  else
  if (filetype == "xml")
    return "text/xml";
  else
  if (filetype == "csv")
    return "text/csv";
  else
  if (filetype == "rtf")
    return "text/rtf";
  else
  /// Image files.
  if (filetype == "jpg" || filetype == "jpeg")
    return "image/jpeg";
  else
  if (filetype == "gif")
    return "image/gif";
  else
  if (filetype == "bmp")
    return "image/x-ms-bmp";
  else
  if (filetype == "png")
    return "image/png";
  else  
  if (filetype == "tiff")
    return "image/tiff";
  else  
  /// Audio files.
  if (filetype == "ogg")
    return "audio/ogg";
  else
  if (filetype == "mp3")
    return "audio/mpeg";
  else
  /// Video files.
  if (filetype == "avi")
    return "video/x-msvideo";
  else
  /// Rich media files.
  if (filetype == "pdf")
    return "application/pdf";
  else
  if (filetype == "doc")
    return "application/msword";
  else
  if (filetype == "swf")
    return "application/x-shockwave-flash";
  else
  if (filetype == "xls")
    return "application/vnd.ms-excel";
  /// Compressed files.
  else
  if (filetype == "zip")
    return "application/zip";
  else
  if (filetype == "tar")
    return "application/x-tar";
  /// Other files.
  else
  if (filetype == "pl")
    return "application/x-perl";
  else
  if (filetype == "py")
    return "application/x-python";
  else
  if (filetype == "exe" || filetype == "dll" || filetype == "sys" ||
      filetype == "chm" || filetype == "lib" || filetype == "pdb" ||
      filetype == "obj" || filetype == "dep" || filetype == "idb" ||
      filetype == "pyd" || filetype == "sqm" || filetype == "idb" ||
      filetype == "asm" || filetype == "suo" || filetype == "sbr")
    return ""; // Not allowed to download these file types.

  return "text/plain";
}

/// Show the file to the user.
/**
 * This will send the file to the user using an appropriate content-type
 * header. Some browsers (eg. Firefox) will be able to handle many file types
 * directly, while others (eg. Internet Explorer) will prompt the user to
 * save / open the file externally.
 *
 * This function actually buffers the entire file before sending it to the user,
 * so this isn't very scalable. You could choose to stream the file directly
 * instead, but make sure your HTTP server won't buffer the response.
 *
 * Files over 200MB won't be displayed.
 */
template<typename Response, typename Client>
void show_file(Response& resp, Client& client, fs::path const& file)
{
  if (!fs::exists(file))
    resp<< "File not found.";
  else
  {
    boost::uintmax_t size (fs::file_size(file));
    if (size > 200000000L) // Files must be < 200MB
      resp<< "File too large: " << file.string() << " [" << size << ']';
    else
    {
      /// Check the file type is allowed.
      std::string mime_type (get_mime_type(file));
      if (!mime_type.empty())
      {
        std::string ctype (content_type(mime_type).content + "\r\n\r\n");
        /// Open the file and read it as binary data.
        ifstream ifs (file.string().c_str(), std::ios::binary);
        if (ifs.is_open())
        {
          resp<< content_type(mime_type);
          boost::uintmax_t bufsize = 500;
          boost::uintmax_t read_bytes;
          char buf[500];
          ifs.seekg(0, std::ios::beg);
          while (!ifs.eof() && size > 0)
          {
            ifs.read(buf, size < bufsize ? size : bufsize);
            read_bytes = ifs.gcount();
            size -= read_bytes;
            resp.write(buf, read_bytes);
          }
        }
      }
      else
        resp<< "File type not allowed.";
    }
  }
}
    
template<typename Response>
void show_paths(Response& resp, fs::path const& parent, bool recursive = true)
{
  if (!fs::exists(parent))
  {
    resp<< "File does not exist\n";
    return;
  }
  
  resp<< "<ul>";
  if (fs::is_directory(parent))
  {
    resp<< parent << "\n";
    resp<< "<li class=\"directory\"><a href=\"?dir="
        << parent.string() << "\">.</a></li>\n";
    if (fs::is_directory(parent.parent_path()))
      resp<< "<li class=\"directory\"><a href=\"?dir="
          << parent.parent_path().string() << "\">..</a></li>\n";
    for (fs::directory_iterator iter(parent), end; iter != end; ++iter)
    {
      if (fs::is_directory(*iter))
      {
        resp<< "<li class=\"directory\"><a href=\"?dir="
            << iter->string() << "\">" << iter->path() << "</a></li>\n";
        if (recursive)
          show_paths(resp, iter->path(), recursive);
      }
      else
      {
        // display filename only.
        resp<< "<li class=\"file\"><a href=\"?file="
            << iter->string() << "\">" << iter->path().filename()
            << "</a>";
        //if (fs::is_regular_file(iter->status()))
        //  resp<< " [" << fs::file_size(iter->path()) << " bytes]";
        resp<< "</li>\n";
      }
    }
  }
  else
  {
    resp<< "<li class=\"file\">" << "<a href=\"?file="
            << parent.string() << "\">" << parent << "</li>\n";
  }
  resp<< "</ul>";
}

/// This function accepts and handles a single request.
template<typename Request>
int handle_request(Request& req)
{
  boost::system::error_code ec;
  
  //
  // Load in the request data so we can access it easily.
  //
  req.load(parse_all); // Read and parse STDIN (ie. POST) data.

  //
  // Construct a `response` object (makes writing/sending responses easier).
  //
  response resp;

  if (req.get.count("file"))
  {
    show_file(resp, req.client(), req.get["file"]);
    //return req.close(http::ok, 0);
  }
  else
  if (req.get.count("dir"))
  {
    //
    // Responses in CGI programs require at least a 'Content-type' header.
    // The library provides helpers for several common headers:
    //
    resp<< content_type("text/html");
    
    // You can also stream text to a response. 
    // All of this just prints out the form 
    resp<< "<html>"
           "<head><title>CGI File Browser Example</title><head>"
           "<body>";

    show_paths(resp, req.get["dir"], req.get["recurse"] == "1");

    resp<< "</body></html>";
  }
  else
    resp<< content_type("text/html")
        << "No path specified. Search for a path using, eg. "
        << " <a href=\"?dir=/\">" << req.script_name() << "?dir=/some/path</a>\n";

  resp<< header("CGI-client", "fcgi_file_browser");
  return commit(req, resp);
}

int main()
{
try {

  request req;
  return handle_request(req);

}catch(boost::system::system_error const& se){
  // This is the type of error thrown by the library.
  cerr<< "[fcgi] System error: " << se.what() << endl;
  return -1;
}catch(std::exception const& e){
  // Catch any other exceptions
  cerr<< "[fcgi] Exception: " << e.what() << endl;
  return -1;
}catch(...){
  cerr<< "[fcgi] Uncaught exception!" << endl;
  return -1;
}
}

See the full source listing.

#include <iostream>
#include <boost/cgi/cgi.hpp>
#include <boost/cgi/utility/stencil.hpp>

namespace cgi = boost::cgi;

int handle_request(cgi::request& req)
{
  req.load(cgi::parse_all);
  
  // Construct a response that uses Google cTemplate. Also sets the root
  // directory where the stencils are found.
  cgi::stencil resp("../stencils/");

  //// Test 1.

  // This is a minimal response. The content_type(...) may go before or after
  // the response text.
  // The content of the response - which is everything streamed to it - is
  // added to a {{content}} field in the stencil.
  resp<< cgi::content_type("text/html")
      << "Hello there, universe!";
      
  //// Test 2.

  // Set some fields.
  resp.set("script_name", req.script_name()); // Populates {{script_name}}.
  resp.set("some_string", req.get["string"]); // Populates {{some_string}}.
  // set() supports any type that Boost.Lexical_cast does.
  resp.set("short_bits", 8 * sizeof(short)); // Populates {{short_bits}}.
  if (req.get.count("short"))
  {
    // Almost any type is supported by pick<>
    // (ie. any type supported by Boost.Lexical_cast.).
    short some_short = req.get.pick<short>("short", -1);
    resp.set("some_short", some_short);
  }
  
  //// Test 3.
  
  // Show a section, conditionally.
  
  // Use the "show" GET variable, or default to the string "show" if not set.
  cgi::request::string_type show = req.get.pick("show", "show");
  resp.set("show", show == "show" ? "hide" : "show");
  if (show == "show")
    resp.show("some_section"); // Shows {{#some_section}}...{{/some_section}}.
  
  //// Test 4.
  
  int num = req.get.pick("count", 0);
  if (num < 0) num = 0;
  resp.set("show_less", num ? num - 1 : 0);
  resp.set("show_more", num + 1);
  cgi::section sec("section_with_variable");
  for (int i(0); i < num; ++i)
  {
    // We can show a section and set one field in it in one go.
    resp.set("some_section_variable", i + 1, sec);
  }
  
  //// Test 5.

  cgi::dictionary test5 = resp.add("test5");
  test5.add("input")
    .set("type", "text")
    .set("name", "text")
    .set("value", req.form["text"])
    .show("label", "Text");

  test5.add("input")
    .set("type", "checkbox")
    .set("name", "check")
    .set("value", "one")
    .show("label", "One")
    .show(req.post.matches("check", "one") ? "checked" : "");
  test5.add("input")
    .set("type", "checkbox")
    .set("name", "check")
    .set("value", "two")
    .show("label", "Two")
    .show(req.post.matches("check", "two") ? "checked" : "");
  test5.add("input")
    .set("type", "checkbox")
    .set("name", "check")
    .set("value", "six")
    .show("label", "Six")
    .show(req.post.matches("check", "six") ? "checked" : "");
  test5.add("input")
    .set("type", "checkbox")
    .set("name", "check")
    .set("value", "ten")
    .show("label", "Ten")
    .show(req.post.matches("check", "ten") ? "checked" : "");

  test5.add("input")
    .set("type", "radio")
    .set("name", "radio")
    .set("value", "yes")
    .show("label", "Yes")
    .show(req.post.matches("radio", "yes") ? "checked" : "");
  test5.add("input")
    .set("type", "radio")
    .set("name", "radio")
    .set("value", "no")
    .show("label", "No")
    .show(req.post.matches("radio", "no") ? "checked  " : "");

  test5.add("input")
    .set("type", "submit")
    .set("value", "Submit");

  //// Test 6.

  resp.add(cgi::section("embedded")).set("test", "passed");
  
  cgi::dictionary dict = resp.add("embedded");
  dict.add("subsection") // returns a new sub-dictionary.
      .set("test", "passed again")
      .set("other", "(another field)");
  dict.set("test", "passed yet again", cgi::section("subsection"));

  //// Test 7.

  // Include another stencil into this one at marker {{>include}}.
  resp.include(
      cgi::section(
          "include",
          "cgi_stencil.include.html"
        )
    );
  
  // Short-cut for stencil includes.
  resp.include("include", "cgi_stencil.include.html");
  
  // Set a session cookie, which expires when the user closes their browser.
  resp<< cgi::cookie("name", "value");

  /// End Tests.
  
  // Expand the response using the specified template.
  // cTemplate has a cache internally, which we can choose to
  // ignore.
  resp.expand("cgi_stencil.html");

  // Send the response and close the request.
  return cgi::commit(req, resp);
}
  
int main()
{
try {
  
  cgi::request req;
  if (handle_request(req))
    std::cerr<< "Error returned from handling request." << std::endl;
  else
    std::cerr<< "Successfully handled request." << std::endl;
  
  return 0;
  
} catch(std::exception& e) {
  using namespace std;
  cout<< "Content-type: text/plain\r\n\r\n"
      << "Error: " << e.what() << endl;
} catch(...) {
  using namespace std;
  cout<< "Content-type: text/plain\r\n\r\n"
      << "Unexpected exception." << endl;
}
  return 1;
}

See the full source listing.



[10] cTemplate isn't just an HTML templating engine. This library is in no way affiliated with cTemplate, etc, etc. :)


PrevUpHomeNext