Skip to content

C++ Web Programming

Important Note: While this tutorial covers the Common Gateway Interface (CGI) with C++, it’s crucial to understand that CGI is an outdated technology for modern web development, especially with C++. Direct CGI scripting in C++ is rarely used today due to performance limitations (spawning a new process for each request), security concerns, and the availability of far more efficient and robust C++ web frameworks and alternative architectures.

Modern alternatives include dedicated C++ web frameworks (like Crow, Drogon, oat++, Pistache), using C++ for backend APIs (e.g., REST, gRPC) accessed by other web servers, or even WebAssembly for client-side execution. However, to understand the historical context and the basic principle of server-side execution, this tutorial explains CGI.

  • The Common Gateway Interface (CGI) is a standard protocol defining how a web server can execute external programs (CGI scripts) to dynamically generate web content.
  • Instead of just sending back a static file, the web server runs the CGI program, captures its standard output, and sends that output back to the client’s browser.
  • CGI scripts can be written in various languages, including C++, Perl, Python, or shell scripts.
  1. A user’s browser requests a specific URL associated with a CGI script from the web server.

  2. The web server identifies the request targets a CGI script (usually based on location or file extension).

  3. The server executes the CGI program (e.g., your compiled C++ executable).

  4. The CGI program performs its task (e.g., accesses a database, performs calculations).

  5. Crucially, the CGI program prints its output to standard output (stdout). This output must start with HTTP headers, followed by a blank line, and then the actual content (usually HTML).

  6. The web server captures everything printed to stdout by the CGI program.

  7. The server sends the captured output (headers and content) back to the user’s browser.

  8. The browser renders the received content.

To run CGI scripts, the web server (e.g., Apache, Nginx) needs to be configured:

  • Specify a directory (often /cgi-bin/) where CGI scripts are allowed.
  • Enable CGI execution for files within that directory or for files with a specific extension (e.g., .cgi).
  • Ensure the script files have execute permissions.
  • The specific configuration directives vary depending on the web server software.

Let’s create a simple C++ program that outputs a basic HTML page via CGI.

#include <iostream>
int main() {
// 1. Print the mandatory HTTP Content-Type header
// Followed by a blank line (\n\n or \r\n\r\n)
std::cout << "Content-type: text/html\n\n";
// 2. Print the HTML content
std::cout << "<!DOCTYPE html>\n";
std::cout << "<html>\n";
std::cout << "<head>\n";
std::cout << "<title>Hello C++ CGI</title>\n";
std::cout << "</head>\n";
std::cout << "<body>\n";
std::cout << "<h1>Hello World!</h1>\n";
std::cout << "<p>This is my first C++ CGI program.</p>\n";
std::cout << "</body>\n";
std::cout << "</html>\n";
return 0; // Indicates successful execution to the server
}

To make this work:

  • Compile this code (e.g., g++ hello.cpp -o hello.cgi).
  • Place the executable hello.cgi into the web server’s configured CGI directory (e.g., /var/www/cgi-bin/).
  • Ensure it has execute permissions (e.g., chmod +x hello.cgi).
  • Access the script via its URL in a web browser (e.g., http://yourserver.com/cgi-bin/hello.cgi).

The browser should display the “Hello World!” heading and the paragraph.

The most crucial header for CGI is Content-type, which tells the browser what kind of data follows (e.g., text/html, text/plain, image/jpeg). It must be followed by a blank line.

Other useful headers include:

HeaderDescription
Content-type:The MIME type of the content (e.g., text/html). Essential.
Location:Used to redirect the browser to a different URL (e.g., Location: http://another-page.com\n\n).
Set-Cookie:Used to send an HTTP cookie to the browser (see Cookies section).
Status:Sets a specific HTTP status code (e.g., Status: 404 Not Found\n\n). Often handled by the server, but can be set by CGI.

The web server provides information to the CGI script through environment variables. Your C++ program can access these using getenv() from <cstdlib>.

Variable NameDescription
REQUEST_METHODThe HTTP method used (e.g., GET, POST).
QUERY_STRINGThe part of the URL after the ? (used with GET requests).
CONTENT_LENGTHThe length of the data sent via POST (in bytes).
CONTENT_TYPEThe MIME type of the data sent via POST.
SCRIPT_NAMEThe virtual path to the script being executed.
REMOTE_ADDRThe IP address of the client making the request.
REMOTE_HOSTThe hostname of the client (if available).
SERVER_NAMEThe hostname or IP address of the server.
SERVER_PORTThe port number the server is listening on.
HTTP_USER_AGENTThe browser/client identification string.
HTTP_COOKIEContains cookies sent by the client.

Example: Displaying Environment Variables

#include <iostream>
#include <cstdlib> // For getenv()
#include <string>
// List of common CGI environment variables
const char* ENV_VARS[] = {
"REQUEST_METHOD", "QUERY_STRING", "CONTENT_LENGTH", "CONTENT_TYPE",
"SCRIPT_NAME", "REMOTE_ADDR", "REMOTE_HOST", "SERVER_NAME",
"SERVER_PORT", "HTTP_USER_AGENT", "HTTP_COOKIE", nullptr // End marker
};
int main() {
std::cout << "Content-type: text/html\n\n";
std::cout << "<!DOCTYPE html><html><head><title>CGI Environment</title></head><body>\n";
std::cout << "<h1>CGI Environment Variables</h1>\n";
std::cout << "<table border='1'><tr><th>Variable</th><th>Value</th></tr>\n";
for (int i = 0; ENV_VARS[i] != nullptr; ++i) {
const char* varName = ENV_VARS[i];
const char* value = std::getenv(varName);
std::cout << "<tr><td>" << varName << "</td><td>";
if (value) {
std::cout << value;
} else {
std::cout << "<i>Not set</i>";
}
std::cout << "</td></tr>\n";
}
std::cout << "</table></body></html>\n";
return 0;
}

Web forms allow users to send data to the server, typically using the GET or POST methods.

Data is appended to the URL as a query string: script.cgi?name1=value1&name2=value2. It’s visible in the browser’s address bar and logs. Suitable for non-sensitive data and limited in length (browser/server dependent, often around 2KB).

In a C++ CGI script, you access GET data by reading the QUERY_STRING environment variable using getenv("QUERY_STRING"). You then need to parse this string (e.g., split by & and =, and URL-decode the values). Manual parsing can be complex.

Data is sent in the body of the HTTP request, not the URL. Suitable for larger amounts of data and sensitive information. The size limit is usually determined by server configuration.

In a C++ CGI script, you access POST data by:

  • Reading the CONTENT_LENGTH environment variable to know how many bytes to read.
  • Reading exactly that number of bytes from standard input (stdin) (e.g., using std::cin.read()).
  • Parsing the read data. The format depends on the CONTENT_TYPE (often application/x-www-form-urlencoded, similar to the query string format, or multipart/form-data for file uploads).

Handling Form Data (Conceptual Example - Simplified Parsing)

Section titled “Handling Form Data (Conceptual Example - Simplified Parsing)”

Parsing form data reliably (especially URL decoding and handling edge cases) requires significant effort or a library. The cgicc library mentioned in the original tutorial is outdated. Below is a conceptual sketch, not robust production code, showing how to read POST data:

#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <sstream>
// --- Basic, Unsafe URL Decoding (for demonstration only!) ---
std::string urlDecode(const std::string& str) {
std::string decoded = "";
char ch;
int i, ii;
for (i = 0; i < str.length(); i++) {
if (str[i] == '%') {
sscanf(str.substr(i + 1, 2).c_str(), "%x", &ii);
ch = static_cast<char>(ii);
decoded += ch;
i = i + 2;
} else if (str[i] == '+') {
decoded += ' ';
} else {
decoded += str[i];
}
}
return decoded;
}
// --- End Basic URL Decoding ---
int main() {
std::cout << "Content-type: text/html\n\n";
std::cout << "<html><body><h1>Form Data Received</h1>";
std::string method = std::getenv("REQUEST_METHOD") ? std::getenv("REQUEST_METHOD") : "";
std::string formData = "";
if (method == "POST") {
const char* contentLengthStr = std::getenv("CONTENT_LENGTH");
if (contentLengthStr) {
int len = std::atoi(contentLengthStr);
if (len > 0) {
std::vector<char> buffer(len);
std::cin.read(buffer.data(), len);
formData.assign(buffer.data(), len);
}
}
} else if (method == "GET") {
const char* queryString = std::getenv("QUERY_STRING");
if (queryString) {
formData = queryString;
}
}
std::cout << "<h2>Raw Data:</h2><pre>" << formData << "</pre>";
// Rudimentary Parsing (Only splits by '&' and '=', needs proper decoding)
std::cout << "<h2>Parsed Data (Basic):</h2><ul>";
std::stringstream ss(formData);
std::string pair;
while (std::getline(ss, pair, '&')) {
size_t eqPos = pair.find('=');
if (eqPos != std::string::npos) {
std::string key = urlDecode(pair.substr(0, eqPos));
std::string value = urlDecode(pair.substr(eqPos + 1));
std::cout << "<li>**" << key << ":** " << value << "</li>";
} else {
std::cout << "<li>**" << urlDecode(pair) << ":** (no value)</li>";
}
}
std::cout << "</ul>";
std::cout << "</body></html>" << std::endl;
return 0;
}

Warning: The parsing and decoding above are extremely basic and insecure. Real-world applications require robust libraries for this.

Cookies allow servers to store small pieces of data on the client’s browser, which are sent back with subsequent requests to the same domain. CGI scripts can interact with cookies.

To set a cookie, the CGI script must print a Set-Cookie HTTP header before the blank line and the content.

// Example: Setting a cookie
std::cout << "Content-type: text/html\n"; // Normal header
std::cout << "Set-Cookie: UserID=Alice; Max-Age=3600; Path=/\n"; // Set cookie header
std::cout << "Set-Cookie: SessionToken=xyz789; HttpOnly; Secure\n"; // Another cookie
std::cout << "\n"; // Blank line **after** all headers
std::cout << "<html><body>Cookies have been set (check browser developer tools).</body></html>";

Attributes like Max-Age (lifetime in seconds), Expires (expiry date), Path (URL path scope), Domain (domain scope), Secure (only send over HTTPS), and HttpOnly (prevent JavaScript access) control the cookie’s behavior.

Cookies sent by the browser are available in the HTTP_COOKIE environment variable as a semicolon-separated string of key=value pairs.

// Example: Retrieving cookies
const char* cookieStr = std::getenv("HTTP_COOKIE");
std::cout << "Content-type: text/html\n\n";
std::cout << "<html><body><h1>Received Cookies:</h1>";
if (cookieStr) {
std::cout << "<pre>" << cookieStr << "</pre>";
// You would need to parse this string (split by ';', then by '=')
// to get individual cookie values.
} else {
std::cout << "<p>No cookies received.</p>";
}
std::cout << "</body></html>";

Again, parsing the cookie string requires careful implementation.

Handling file uploads via CGI in C++ without a dedicated library is highly complex. It involves parsing the multipart/form-data encoding from standard input, which includes boundaries, headers for each part (including filename and content type), and the raw file data.

The HTML form needs enctype="multipart/form-data" and an <input type="file"> element.

<form enctype="multipart/form-data" action="/cgi-bin/upload.cgi" method="post">
<p>Select file: <input type="file" name="userfile"></p>
<p><input type="submit" value="Upload"></p>
</form>

Due to the complexity, a detailed C++ CGI implementation for file uploads is beyond the scope of this introductory tutorial. Modern web frameworks handle this abstraction transparently.

CGI provides a basic understanding of how web servers can execute programs to generate dynamic content. However, for practical C++ web development, modern frameworks offer significantly better performance, security, and developer productivity.