一. 什么是 CGI?
通用网关接口(Common Gateway Interface,简称 CGI)是一套定义了 Web 服务器和自定义脚本之间如何交换信息的标准。
CGI 规范由 NCSA(国家超级计算应用中心)维护,NCSA 定义 CGI 如下:
CGI 是一种用于外部网关程序与信息服务器(如 HTTP 服务器)交互的标准。
当前版本为 CGI/1.1,CGI/1.2 正在开发中。
1.1 Web 浏览
为了理解 CGI 的概念,让我们看一下点击超链接以浏览特定网页或 URL 时会发生什么:
-
浏览器联系 HTTP 服务器,并请求指定的 URL 文件名。
-
Web 服务器解析 URL,并查找相应文件。如果找到请求的文件,Web 服务器将文件返回给浏览器,否则返回错误信息,提示请求的文件不存在。
-
浏览器接收服务器的响应,显示接收到的文件或错误信息。
然而,可以将 HTTP 服务器设置为,当请求特定目录中的文件时,不发送该文件,而是将其作为程序执行,并将程序的输出返回给浏览器显示。
CGI 是一种标准协议,用于使应用程序(称为 CGI 程序或 CGI 脚本)与 Web 服务器和客户端进行交互。CGI 程序可以用 Python、PERL、Shell、C、C++ 等编写。
二. CGI 架构
2.1 CGI 架构图
以下简单程序展示了 CGI 的基本架构:
2.2 Web 服务器配置
在开始 CGI 编程之前,请确保您的 Web 服务器支持 CGI 并且配置为处理 CGI 程序。所有由 HTTP 服务器执行的 CGI 程序保存在预先配置的目录中。这个目录称为 CGI 目录,通常命名为 /var/www/cgi-bin
。按照惯例,CGI 文件的扩展名为 .cgi
,尽管它们是 C++ 可执行文件。
默认情况下,Apache Web 服务器配置为在 /var/www/cgi-bin
目录中运行 CGI 程序。如果您希望指定其他目录以运行 CGI 脚本,可以修改 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 程序(例如 Perl 或 Shell)。
三. 第一个 CGI 程序
考虑以下 C++ 程序:
#include <iostream>
using namespace std;
int main () {
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
目录中,并具有上述内容。在运行您的 CGI 程序之前,请确保通过 UNIX 命令 chmod 755 cplusplus.cgi
使文件可执行。
这个简单的 C++ 程序将其输出写到 STDOUT,即屏幕。第一个打印的行 Content-type:text/html\r\n\r\n
是一个重要的功能,它将内容类型发送回浏览器,用于指定要在浏览器上显示的内容类型。
现在,您应该已经理解了 CGI 的基本概念,并且可以使用 Python 编写更复杂的 CGI 程序。一个 C++ CGI 程序可以与其他外部系统(如 RDBMS)进行交互,以交换信息。
3.1 HTTP 头信息
Content-type:text/html\r\n\r\n
是 HTTP 头的一部分,它发送给浏览器以便理解内容。所有的 HTTP 头信息将以以下形式出现:
HTTP Field Name: Field Content
例如:
Content-type: text/html\r\n\r\n
四. 常用 HTTP 头信息
以下是一些您在 CGI 编程中会频繁使用的重要 HTTP 头信息。
Sr.No |
头信息 |
描述 |
1 |
Content-type: |
定义返回文件格式的 MIME 字符串。 |
2 |
Expires: Date |
信息失效的日期。格式:01 Jan 1998 12:00:00 GMT。 |
3 |
Location: URL |
返回的 URL 替代请求的 URL,可用于重定向。 |
4 |
Last-modified: Date |
资源的最后修改日期。 |
5 |
Content-length: N |
返回数据的字节长度。浏览器使用该值来报告文件的估计下载时间。 |
6 |
Set-Cookie: String |
通过字符串设置 cookie。 |
五. CGI 环境变量
所有 CGI 程序都有权访问以下环境变量。这些变量在编写 CGI 程序时非常重要。
Sr.No |
变量名称 |
描述 |
1 |
CONTENT_TYPE |
客户端发送给服务器的内容数据类型。 |
2 |
CONTENT_LENGTH |
可用于 POST 请求的查询信息长度。 |
3 |
HTTP_COOKIE |
返回以键值对形式的设置 cookie。 |
4 |
HTTP_USER_AGENT |
包含发出请求的用户代理的信息。 |
5 |
PATH_INFO |
CGI 脚本的路径。 |
6 |
QUERY_STRING |
通过 GET 方法发送的 URL 编码信息。 |
7 |
REMOTE_ADDR |
发出请求的远程主机的 IP 地址。 |
8 |
REMOTE_HOST |
发出请求的主机的完整名称。 |
9 |
REQUEST_METHOD |
用来发出请求的方法。常见方法是 GET 和 POST。 |
10 |
SCRIPT_FILENAME |
CGI 脚本的完整路径。 |
11 |
SCRIPT_NAME |
CGI 脚本的名称。 |
12 |
SERVER_NAME |
服务器的主机名或 IP 地址。 |
13 |
SERVER_SOFTWARE |
服务器运行的软件名称和版本。 |
5.1 列出所有 CGI 变量的程序
#include <iostream>
#include <stdlib.h>
using namespace std;
const string ENV[ 24 ] = {
"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>\n";
cout << "<head>\n";
cout << "<title>CGI Environment Variables</title>\n";
cout << "</head>\n";
cout << "<body>\n";
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 != 0 ) {
cout << value;
} else {
cout << "Environment variable does not exist.";
}
cout << "</td></tr>\n";
}
cout << "</table><\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
六. C++ CGI 库
对于真实的例子,您可能需要通过 CGI 程序执行许多操作。这里有一个 C++ 编写的 CGI 库,您可以从 GNU FTP 服务器 下载并安装。
$ tar xzf cgicc-X.X.X.tar.gz
$ cd cgicc-X.X.X/
$ ./configure --prefix=/usr
$ make
$ make install
七. GET 和 POST 方法
您可能遇到过许多需要从浏览器向 Web 服务器传递信息并最终到达 CGI 程序的情况。浏览器最常用的两种方法是 GET 方法和 POST 方法。
7.1 使用 GET 方法传递信息
GET 方法将编码的用户信息附加到页面请求中。页面和编码信息用
一个 ?
作为分隔符。注意,查询字符串的长度受限于大约 255 个字符。
<form method="GET" action="/cgi-bin/cplusplus.cgi">
First Name: <input type="text" name="first_name"> <br />
Last Name: <input type="text" name="last_name" />
<input type="submit" value="提交" />
</form>
7.2 使用 POST 方法传递信息
<form method="POST" action="/cgi-bin/cplusplus.cgi">
First Name: <input type="text" name="first_name"> <br />
Last Name: <input type="text" name="last_name" />
<input type="submit" value="提交" />
</form>
八. 通过 CGI 程序传递单选按钮数据
单选按钮用于当只需要选择一个选项的情况。
以下是带有两个单选按钮的表单的示例 HTML 代码:
<form action="/cgi-bin/cpp_radiobutton.cgi" method="post" target="_blank">
<input type="radio" name="subject" value="maths" checked="checked" /> Maths
<input type="radio" name="subject" value="physics" /> Physics
<input type="submit" value="Select Subject" />
</form>
该代码生成的表单如下:
Maths Physics
以下是 C++ 程序,它将生成 cpp_radiobutton.cgi
脚本,用于处理通过单选按钮从浏览器发送的输入数据:
#include <iostream>
#include <vector>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#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 << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Radio Button Data to CGI</title>\n";
cout << "</head>\n";
cout << "<body>\n";
form_iterator fi = formData.getElement("subject");
if (!fi->isEmpty() && fi != (*formData).end()) {
cout << "Radio box selected: " << **fi << endl;
}
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
九. 通过 CGI 程序传递文本区域数据
TEXTAREA
元素用于将多行文本传递给 CGI 程序。
以下是带有一个 TEXTAREA
框的表单的示例 HTML 代码:
<form action="/cgi-bin/cpp_textarea.cgi" method="post" target="_blank">
<textarea name="textcontent" cols="40" rows="4">
Type your text here...
</textarea>
<input type="submit" value="Submit" />
</form>
该代码生成的表单如下:
Type your text here...
以下是 C++ 程序,它将生成 cpp_textarea.cgi
脚本,用于处理通过文本区域从浏览器发送的输入数据:
#include <iostream>
#include <vector>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#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 << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Text Area Data to CGI</title>\n";
cout << "</head>\n";
cout << "<body>\n";
form_iterator fi = formData.getElement("textcontent");
if (!fi->isEmpty() && fi != (*formData).end()) {
cout << "Text Content: " << **fi << endl;
} else {
cout << "No text entered" << endl;
}
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
十. 通过 CGI 程序传递下拉框数据
下拉框用于当有多个选项但只需要选择一个或两个的情况。
以下是带有一个下拉框的表单的示例 HTML 代码:
<form action="/cgi-bin/cpp_dropdown.cgi" method="post" target="_blank">
<select name="dropdown">
<option value="Maths" selected>Maths</option>
<option value="Physics">Physics</option>
</select>
<input type="submit" value="Submit"/>
</form>
该代码生成的表单如下:
Maths
以下是 C++ 程序,它将生成 cpp_dropdown.cgi
脚本,用于处理通过下拉框从浏览器发送的输入数据:
#include <iostream>
#include <vector>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#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 << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Drop Down Box Data to CGI</title>\n";
cout << "</head>\n";
cout << "<body>\n";
form_iterator fi = formData.getElement("dropdown");
if (!fi->isEmpty() && fi != (*formData).end()) {
cout << "Value Selected: " << **fi << endl;
}
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
十一. 在 CGI 中使用 Cookies
HTTP 协议是一个无状态协议。但对于一个商业网站,需要在不同的页面之间维护会话信息。例如,用户注册可能需要通过多个页面才能完成。但如何在所有网页之间维护用户的会话信息呢?
在许多情况下,使用 Cookies 是记住和跟踪用户偏好、购买信息、佣金等的最有效方法,这些信息有助于提供更好的访问体验或站点统计数据。
11.1 工作原理
服务器将一些数据以 Cookie 的形式发送到访问者的浏览器。浏览器可能会接受这个 Cookie。如果接受了,它将作为一个纯文本记录存储在访问者的硬盘上。现在,当访问者访问站点上的其他页面时,可以检索该 Cookie。检索后,服务器会知道/记住之前存储的内容。
Cookies 是由五个可变长度字段组成的纯文本数据记录:
-
Expires:显示 Cookie 的过期日期。如果为空,Cookie 将在访问者退出浏览器时过期。
-
-
Path:显示设置 Cookie 的目录或网页的路径。如果为空,则可以从任何目录或页面检索 Cookie。
-
Secure:如果该字段包含单词 "secure",则只能通过安全服务器检索该 Cookie。如果该字段为空,则不存在此类限制。
-
Name=Value:Cookies 以键值对的形式设置和检索。
11.2 设置 Cookies
通过 HTTP 头信息设置 Cookies 非常简单。以下是一个设置 UserID
和 Password
作为 Cookie 的示例:
#include <iostream>
using namespace std;
int main () {
cout << "Set-Cookie:UserID=XYZ;\r\n";
cout << "Set-Cookie:Password=XYZ123;\r\n";
cout << "Set-Cookie:Domain=www.tutorialspoint.com;\r\n";
cout << "Set-Cookie:Path=/perl;\n";
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Cookies in CGI</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "Setting cookies" << endl;
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
通过上述代码,我们可以设置 Cookies。需要注意的是,Cookies 必须在发送 Content-type:text/html\r\n\r\n
之前设置。
11.3 检索 Cookies
检索已经设置的 Cookies 非常简单。Cookies 存储在 CGI 环境变量 HTTP_COOKIE
中,格式如下:
key1=value1; key2=value2; key3=value3...
以下是一个如何检索 Cookies 的示例:
#include <iostream>
#include <vector>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#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 cgi;
const_cookie_iterator cci;
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Cookies in CGI</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "<table border=\"0\" cellspacing=\"2\">";
const CgiEnvironment& env = cgi.getEnvironment();
for (cci = env.getCookieList().begin(); c
ci != env.getCookieList().end(); ++cci) {
cout << "<tr><td>" << cci->getName() << "</td>" << endl;
cout << "<td>" << cci->getValue() << "</td></tr>" << endl;
}
cout << "</table>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
此程序使用了 cgicc
库,检索所有 Cookies 并将其显示为 HTML 表格。
11.4 删除 Cookies
为了删除一个 Cookie,只需设置其到期日期为过去的日期即可。
以下是删除 Cookie 的示例代码:
#include <iostream>
using namespace std;
int main () {
cout << "Set-Cookie:UserID=XYZ; Expires=Wed, 31-Dec-97 23:59:59 GMT;\r\n";
cout << "Set-Cookie:Password=XYZ123; Expires=Wed, 31-Dec-97 23:59:59 GMT;\r\n";
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Delete Cookies in CGI</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "Deleted cookies" << endl;
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}