掌握 C++ CGI 网络编程:从入门到实战
什么是 CGI?
CGI(Common Gateway Interface,通用网关接口) 是一组标准,定义 Web 服务器与自定义脚本(CGI 程序)之间的信息交换方式。CGI 规范由 NCSA 维护,当前版本为 CGI/1.1,CGI/1.2 正在制定中。
CGI 程序可用 Python、Perl、Shell、C、C++ 等语言编写,作为外部网关程序与 HTTP 服务器交互。
Web 浏览流程与 CGI 角色
- 浏览器向 HTTP 服务器请求 URL(文件名)。
- 服务器解析 URL 查找文件:找到则返回文件,否则返回错误信息。
- 浏览器显示文件或错误信息。
但可配置服务器:当请求特定目录下的文件时,不直接返回文件,而是将其作为程序执行,将程序的输出返回浏览器显示。这就是 CGI 的工作模式。
Web 服务器配置
CGI 程序通常放在预配置的 CGI 目录,惯例为 /var/www/cgi-bin,文件扩展名通常为 .cgi(即使是 C++ 可执行文件)。
Apache 默认配置示例(httpd.conf):
<Directory "/var/www/cgi-bin">
AllowOverride None
Options ExecCGI
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/cgi-bin">
Options All
</Directory>
确保 Web 服务器已启用 CGI 支持并正确配置。
第一个 CGI 程序
#include <iostream>
using namespace std;
int main() {
// 必须先输出 HTTP 头,指定内容类型
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Hello World - First CGI Program</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "<h2>Hello World! This is my first CGI program</h2>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
编译后命名为 cplusplus.cgi,放入 /var/www/cgi-bin,执行 chmod 755 cplusplus.cgi 赋予执行权限。
关键点:第一行 Content-type:text/html\r\n\r\n 是 HTTP 头,告知浏览器内容类型,必须在实际内容之前输出,且以双换行符结束。
HTTP 头详解
HTTP 头格式:字段名: 字段内容,如 Content-type: text/html\r\n\r\n。
| 序号 | 常用 HTTP 头 |
|---|---|
| 1 | Content-type:MIME 类型,如 text/html。 |
| 2 | Expires:过期时间,格式如 01 Jan 1998 12:00:00 GMT。 |
| 3 | Location:重定向 URL。 |
| 4 | Last-modified:资源最后修改时间。 |
| 5 | Content-length:返回数据字节数。 |
| 6 | Set-Cookie:设置 Cookie。 |
CGI 环境变量
CGI 程序可访问以下关键环境变量:
| 变量名 | 说明 |
|---|---|
| CONTENT_TYPE | 客户端发送内容的数据类型(如文件上传)。 |
| CONTENT_LENGTH | POST 请求的查询信息长度。 |
| HTTP_COOKIE | 客户端 Cookie,键值对形式。 |
| HTTP_USER_AGENT | 客户端浏览器标识。 |
| PATH_INFO | CGI 脚本路径。 |
| QUERY_STRING | GET 请求的 URL 编码参数。 |
| REMOTE_ADDR | 客户端 IP 地址。 |
| REMOTE_HOST | 客户端主机名(不可用时用 IP)。 |
| REQUEST_METHOD | 请求方法(GET/POST 等)。 |
| SCRIPT_FILENAME | CGI 脚本完整路径。 |
| SCRIPT_NAME | CGI 脚本名称。 |
| SERVER_NAME | 服务器主机名或 IP。 |
| SERVER_SOFTWARE | 服务器软件名称版本。 |
示例:列出所有环境变量
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;
const string ENV[] = {
"COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "HTTP_ACCEPT",
"HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION",
"HTTP_HOST", "HTTP_USER_AGENT", "PATH", "QUERY_STRING", "REMOTE_ADDR",
"REMOTE_PORT", "REQUEST_METHOD", "REQUEST_URI", "SCRIPT_FILENAME",
"SCRIPT_NAME", "SERVER_ADDR", "SERVER_ADMIN", "SERVER_NAME",
"SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SIGNATURE", "SERVER_SOFTWARE"
};
int main() {
cout << "Content-type:text/html\r\n\r\n";
cout << "<html><head><title>CGI Environment Variables</title></head><body>";
cout << "<table border=\"0\" cellspacing=\"2\">";
for (int i = 0; i < 24; i++) {
cout << "<tr><td>" << ENV[i] << "</td><td>";
char *value = getenv(ENV[i].c_str());
if (value) cout << value;
else cout << "Environment variable does not exist.";
cout << "</td></tr>\n";
}
cout << "</table></body></html>";
return 0;
}
C++ CGI 库
实际开发推荐使用 cgicc 库,简化表单处理、Cookie、文件上传等操作。下载地址:ftp://ftp.gnu.org/gnu/cgicc/
tar xzf cgicc-X.X.X.tar.gz
cd cgicc-X.X.X/
./configure --prefix=/usr
make
make install
GET 与 POST 方法
GET 方法
参数附加在 URL 后,用 ? 分隔:https://example.com/cgi-bin/cpp.cgi?key1=value1&key2=value2
- 默认方法,参数显示在浏览器地址栏。
- 不适合传递密码等敏感信息。
- 长度限制(通常 1024 字符)。
- 参数通过
QUERY_STRING环境变量获取。
POST 方法
参数作为独立消息体发送,通过标准输入(stdin)传递给 CGI 程序,无长度限制,更安全。
使用 cgicc 处理 GET/POST 示例
#include <iostream>
#include <vector>
#include <string>
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
#include <cgicc/HTMLClasses.h>
using namespace std;
using namespace cgicc;
int main() {
Cgicc formData;
cout << HTTPHTMLHeader(); // 自动输出 Content-type:text/html
cout << "<html><head><title>GET/POST Demo</title></head><body>";
form_iterator fi = formData.getElement("first_name");
if (!fi->isEmpty() && fi != (*formData).end())
cout << "First name: " << **fi << "<br/>";
else
cout << "No text entered for first name<br/>";
fi = formData.getElement("last_name");
if (!fi->isEmpty() && fi != (*formData).end())
cout << "Last name: " << **fi << "<br/>";
else
cout << "No text entered for last name<br/>";
cout << "</body></html>";
return 0;
}
编译:g++ -o cpp_get.cgi cpp_get.cpp -lcgicc
表单控件数据处理
复选框
// HTML
<form action="/cgi-bin/cpp_checkbox.cgi" method="POST">
<input type="checkbox" name="maths" value="on" /> Maths
<input type="checkbox" name="physics" value="on" /> Physics
<input type="submit" value="Select Subject" />
</form>
// C++ 处理
bool maths_flag = formData.queryCheckbox("maths");
bool physics_flag = formData.queryCheckbox("physics");
单选按钮
form_iterator fi = formData.getElement("subject");
if (!fi->isEmpty() && fi != (*formData).end())
cout << "Radio box selected: " << **fi;
文本域
form_iterator fi = formData.getElement("textcontent");
if (!fi->isEmpty() && fi != (*formData).end())
cout << "Text Content: " << **fi;
下拉框
form_iterator fi = formData.getElement("dropdown");
if (!fi->isEmpty() && fi != (*formData).end())
cout << "Value Selected: " << **fi;
Cookie 处理
HTTP 无状态,Cookie 用于维护会话信息。Cookie 由 5 个字段组成:Expires、Domain、Path、Secure、Name=Value。
设置 Cookie
cout << "Set-Cookie:UserID=XYZ;\r\n";
cout << "Set-Cookie:Password=XYZ123;\r\n";
cout << "Set-Cookie:Domain=www.example.com;\r\n";
cout << "Set-Cookie:Path=/app;\r\n";
cout << "Content-type:text/html\r\n\r\n"; // 必须在 Content-type 之前设置 Cookie
读取 Cookie
#include <cgicc/CgiDefs.h>
#include <cgicc/Cgicc.h>
#include <cgicc/HTTPHTMLHeader.h>
Cgicc cgi;
const CgiEnvironment& env = cgi.getEnvironment();
for (const_cookie_iterator cci = env.getCookieList().begin();
cci != env.getCookieList().end(); ++cci) {
cout << cci->getName() << " = " << cci->getValue() << "<br/>";
}
文件上传
HTML 表单需设置 enctype="multipart/form-data":
<form enctype="multipart/form-data" action="/cgi-bin/cpp_uploadfile.cgi" method="post">
<p>File: <input type="file" name="userfile" /></p>
<p><input type="submit" value="Upload" /></p>
</form>
C++ 处理上传文件:
const_file_iterator file = cgi.getFile("userfile");
if (file != cgi.getFiles().end()) {
cout << HTTPContentHeader(file->getDataType()); // 输出正确的 Content-type
file->writeToStream(cout); // 将文件内容写入输出流
// 实际应用中应保存到服务器文件系统
}
安全提示
- 严格验证所有用户输入,防止命令注入、XSS 等攻击。
- 文件上传需限制文件类型、大小,存储路径需不可直接通过 Web 访问。
- 生产环境建议使用成熟 Web 框架(如 Crow、Pistache、Wt)替代原始 CGI。
C语言
- 掌握 C# 命名空间:代码组织、作用域与最佳实践
- 掌握 C++ 运算符:六大类型全解析与实战示例
- C++ 注释最佳实践:编写可读、可维护代码的注释艺术
- C++数组传递给函数实战指南
- C 语言编程入门:全面教程指南
- C++编程语言:核心特性全景解析
- C++常量与字面量完全手册:类型、语法与最佳实践
- C++运算符大全:算术/关系/逻辑/位/赋值/杂项与优先级结合性
- C++数值类型实战指南:基本类型、数学函数与随机数生成
- C++引用详解:核心概念、与指针三大差异及函数参数/返回值应用
- C++模板编程指南:泛型编程基石与函数/类模板实战
- C++预处理器指令详解:#define宏、条件编译、#/#运算符与预定义宏