C++ Web Programming
C++ Web Programming (using CGI)
Section titled “C++ Web Programming (using CGI)”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.
What is CGI?
Section titled “What is 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.
How CGI Works (Simplified)
Section titled “How CGI Works (Simplified)”-
A user’s browser requests a specific URL associated with a CGI script from the web server.
-
The web server identifies the request targets a CGI script (usually based on location or file extension).
-
The server executes the CGI program (e.g., your compiled C++ executable).
-
The CGI program performs its task (e.g., accesses a database, performs calculations).
-
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).
-
The web server captures everything printed to stdout by the CGI program.
-
The server sends the captured output (headers and content) back to the user’s browser.
-
The browser renders the received content.
Web Server Configuration (Conceptual)
Section titled “Web Server Configuration (Conceptual)”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.
First C++ CGI Program
Section titled “First C++ CGI Program”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.cgiinto 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.
HTTP Headers
Section titled “HTTP Headers”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:
| Header | Description |
|---|---|
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. |
CGI Environment Variables
Section titled “CGI Environment Variables”The web server provides information to the CGI script through environment variables. Your C++ program can access these using getenv() from <cstdlib>.
| Variable Name | Description |
|---|---|
REQUEST_METHOD | The HTTP method used (e.g., GET, POST). |
QUERY_STRING | The part of the URL after the ? (used with GET requests). |
CONTENT_LENGTH | The length of the data sent via POST (in bytes). |
CONTENT_TYPE | The MIME type of the data sent via POST. |
SCRIPT_NAME | The virtual path to the script being executed. |
REMOTE_ADDR | The IP address of the client making the request. |
REMOTE_HOST | The hostname of the client (if available). |
SERVER_NAME | The hostname or IP address of the server. |
SERVER_PORT | The port number the server is listening on. |
HTTP_USER_AGENT | The browser/client identification string. |
HTTP_COOKIE | Contains cookies sent by the client. |
Example: Displaying Environment Variables
#include <iostream>#include <cstdlib> // For getenv()#include <string>
// List of common CGI environment variablesconst 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;}GET and POST Methods
Section titled “GET and POST Methods”Web forms allow users to send data to the server, typically using the GET or POST methods.
GET Method
Section titled “GET Method”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.
POST Method
Section titled “POST Method”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_LENGTHenvironment 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(oftenapplication/x-www-form-urlencoded, similar to the query string format, ormultipart/form-datafor 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.
Using Cookies in CGI
Section titled “Using Cookies in CGI”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.
Setting Cookies
Section titled “Setting 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 cookiestd::cout << "Content-type: text/html\n"; // Normal headerstd::cout << "Set-Cookie: UserID=Alice; Max-Age=3600; Path=/\n"; // Set cookie headerstd::cout << "Set-Cookie: SessionToken=xyz789; HttpOnly; Secure\n"; // Another cookiestd::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.
Retrieving Cookies
Section titled “Retrieving Cookies”Cookies sent by the browser are available in the HTTP_COOKIE environment variable as a semicolon-separated string of key=value pairs.
// Example: Retrieving cookiesconst 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.
File Uploads
Section titled “File Uploads”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.
Conclusion
Section titled “Conclusion”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.