為了更好地了解 CGI 的概念,讓我們點擊一個超鏈接,瀏覽一個特定的網(wǎng)頁或 URL,看看會發(fā)生什么。
然而,以這種方式搭建起來的 HTTP 服務器,不管何時請求目錄中的某個文件,HTTP 服務器發(fā)送回來的不是該文件,而是以程序形式執(zhí)行,并把執(zhí)行產(chǎn)生的輸出發(fā)送回瀏覽器顯示出來。
公共網(wǎng)關接口(CGI),是使得應用程序(稱為 CGI 程序或 CGI 腳本)能夠與 Web 服務器以及客戶端進行交互的標準協(xié)議。這些 CGI 程序可以用 Python、PERL、Shell、C 或 C++ 等進行編寫。
下圖演示了 CGI 的架構:
在您進行 CGI 編程之前,請確保您的 Web 服務器支持 CGI,并已配置成可以處理 CGI 程序。所有由 HTTP 服務器執(zhí)行的 CGI 程序,都必須在預配置的目錄中。該目錄稱為 CGI 目錄,按照慣例命名為 /var/www/cgi-bin。雖然 CGI 文件是 C++ 可執(zhí)行文件,但是按照慣例它的擴展名是 .cgi。
默認情況下,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>
在這里,我們假設已經(jīng)配置好 Web 服務器并能成功運行,你可以運行任意的 CGI 程序,比如 Perl 或 Shell 等。
請看下面的 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 - 第一個 CGI 程序</title>\n"; cout << "</head>\n"; cout << "<body>\n"; cout << "<h2>Hello World! 這是我的第一個 CGI 程序</h2>\n"; cout << "</body>\n"; cout << "</html>\n"; return 0; }
編譯上面的代碼,把可執(zhí)行文件命名為 cplusplus.cgi,并把這個文件保存在 /var/www/cgi-bin 目錄中。在運行 CGI 程序之前,請使用 chmod 755 cplusplus.cgi UNIX 命令來修改文件模式,確保文件可執(zhí)行。訪問可執(zhí)行文件,您會看到下面的輸出:
上面的 C++ 程序是一個簡單的程序,把它的輸出寫在 STDOUT 文件上,即顯示在屏幕上。在這里,值得注意一點,第一行輸出 Content-type:text/html\r\n\r\n。這一行發(fā)送回瀏覽器,并指定要顯示在瀏覽器窗口上的內容類型。您必須理解 CGI 的基本概念,這樣才能進一步使用 Python 編寫更多復雜的 CGI 程序。C++ CGI 程序可以與任何其他外部的系統(tǒng)(如 RDBMS)進行交互。
行 Content-type:text/html\r\n\r\n 是 HTTP 頭信息的組成部分,它被發(fā)送到瀏覽器,以便更好地理解頁面內容。HTTP 頭信息的形式如下:
HTTP 字段名稱: 字段內容 例如 Content-type: text/html\r\n\r\n
還有一些其他的重要的 HTTP 頭信息,這些在您的 CGI 編程中都會經(jīng)常被用到。
頭信息 | 描述 |
---|---|
Content-type: | MIME 字符串,定義返回的文件格式。例如 Content-type:text/html。 |
Expires: Date | 信息變成無效的日期。瀏覽器使用它來判斷一個頁面何時需要刷新。一個有效的日期字符串的格式應為 01 Jan 1998 12:00:00 GMT。 |
Location: URL | 這個 URL 是指應該返回的 URL,而不是請求的 URL。你可以使用它來重定向一個請求到任意的文件。 |
Last-modified: Date | 資源的最后修改日期。 |
Content-length: N | 要返回的數(shù)據(jù)的長度,以字節(jié)為單位。瀏覽器使用這個值來表示一個文件的預計下載時間。 |
Set-Cookie: String | 通過 string 設置 cookie。 |
所有的 CGI 程序都可以訪問下列的環(huán)境變量。這些變量在編寫 CGI 程序時扮演了非常重要的角色。
變量名 | 描述 |
---|---|
CONTENT_TYPE | 內容的數(shù)據(jù)類型。當客戶端向服務器發(fā)送附加內容時使用。例如,文件上傳等功能。 |
CONTENT_LENGTH | 查詢的信息長度。只對 POST 請求可用。 |
HTTP_COOKIE | 以鍵 & 值對的形式返回設置的 cookies。 |
HTTP_USER_AGENT | 用戶代理請求標頭字段,遞交用戶發(fā)起請求的有關信息,包含了瀏覽器的名稱、版本和其他平臺性的附加信息。 |
PATH_INFO | CGI 腳本的路徑。 |
QUERY_STRING | 通過 GET 方法發(fā)送請求時的 URL 編碼信息,包含 URL 中問號后面的參數(shù)。 |
REMOTE_ADDR | 發(fā)出請求的遠程主機的 IP 地址。這在日志記錄和認證時是非常有用的。 |
REMOTE_HOST | 發(fā)出請求的主機的完全限定名稱。如果此信息不可用,則可以用 REMOTE_ADDR 來獲取 IP 地址。 |
REQUEST_METHOD | 用于發(fā)出請求的方法。最常見的方法是 GET 和 POST。 |
SCRIPT_FILENAME | CGI 腳本的完整路徑。 |
SCRIPT_NAME | CGI 腳本的名稱。 |
SERVER_NAME | 服務器的主機名或 IP 地址。 |
SERVER_SOFTWARE | 服務器上運行的軟件的名稱和版本。 |
下面的 CGI 程序列出了所有的 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 環(huán)境變量</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>";
// 嘗試檢索環(huán)境變量的值
char *value = getenv( ENV[ i ].c_str() );
if ( value != 0 ){
cout << value;
}else{
cout << "環(huán)境變量不存在。";
}
cout << "</td></tr>\n";
}
cout << "</table><\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
在真實的實例中,您需要通過 CGI 程序執(zhí)行許多操作。這里有一個專為 C++ 程序而編寫的 CGI 庫,我們可以從 ftp://ftp.gnu.org/gnu/cgicc/ 上下載這個 CGI 庫,并按照下面的步驟安裝庫:
$tar xzf cgicc-X.X.X.tar.gz $cd cgicc-X.X.X/ $./configure --prefix=/usr $make $make install
您可以點擊 C++ CGI Lib Documentation,查看相關的庫文檔。
您可能有遇到過這樣的情況,當您需要從瀏覽器傳遞一些信息到 Web 服務器,最后再傳到 CGI 程序。通常瀏覽器會使用兩種方法把這個信息傳到 Web 服務器,分別是 GET 和 POST 方法。
GET 方法發(fā)送已編碼的用戶信息追加到頁面請求中。頁面和已編碼信息通過 ? 字符分隔開,如下所示:
http://www.test.com/cgi-bin/cpp.cgi?key1=value1&key2=value2
GET 方法是默認的從瀏覽器向 Web 服務器傳信息的方法,它會在瀏覽器的地址欄中生成一串很長的字符串。當您向服務器傳密碼或其他一些敏感信息時,不要使用 GET 方法。GET 方法有大小限制,在一個請求字符串中最多可以傳 1024 個字符。
當使用 GET 方法時,是使用 QUERY_STRING http 頭來傳遞信息,在 CGI 程序中可使用 QUERY_STRING 環(huán)境變量來訪問。
您可以通過在 URL 后跟上簡單連接的鍵值對,也可以通過使用 HTML <FORM> 標簽的 GET 方法來傳信息。
下面是一個簡單的 URL,使用 GET 方法傳遞兩個值給 hello_get.py 程序。
/cgi-bin/cpp_get.cgi?first_name=ZARA&last_name=ALI
下面的實例生成 cpp_get.cgi CGI 程序,用于處理 Web 瀏覽器給出的輸入。通過使用 C++ 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>使用 GET 和 POST 方法</title>\n";
cout << "</head>\n";
cout << "<body>\n";
form_iterator fi = formData.getElement("first_name");
if( !fi->isEmpty() && fi != (*formData).end()) {
cout << "名:" << **fi << endl;
}else{
cout << "No text entered for first name" << endl;
}
cout << "<br/>\n";
fi = formData.getElement("last_name");
if( !fi->isEmpty() &&fi != (*formData).end()) {
cout << "姓:" << **fi << endl;
}else{
cout << "No text entered for last name" << endl;
}
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
現(xiàn)在,編譯上面的程序,如下所示:
$g++ -o cpp_get.cgi cpp_get.cpp -lcgicc
生成 cpp_get.cgi,并把它放在 CGI 目錄中,并嘗試使用下面的鏈接進行訪問:
/cgi-bin/cpp_get.cgi?first_name=ZARA&last_name=ALI
這會產(chǎn)生以下結果:
名:ZARA 姓:ALI
下面是一個簡單的實例,使用 HTML 表單和提交按鈕傳遞兩個值。我們將使用相同的 CGI 腳本 cpp_get.cgi 來處理輸入。
<form action="/cgi-bin/cpp_get.cgi" method="get">
名:<input type="text" name="first_name"> <br />
姓:<input type="text" name="last_name" />
<input type="submit" value="提交" />
</form>
下面是上述表單的實際輸出,請輸入名和姓,然后點擊提交按鈕查看結果。
一個更可靠的向 CGI 程序傳遞信息的方法是 POST 方法。這種方法打包信息的方式與 GET 方法相同,不同的是,它不是把信息以文本字符串形式放在 URL 中的 ? 之后進行傳遞,而是把它以單獨的消息形式進行傳遞。該消息是以標準輸入的形式傳給 CGI 腳本的。
我們同樣使用 cpp_get.cgi 程序來處理 POST 方法。讓我們以同樣的例子,通過使用 HTML 表單和提交按鈕來傳遞兩個值,只不過這次我們使用的不是 GET 方法,而是 POST 方法,如下所示:
<form action="/cgi-bin/cpp_get.cgi" method="post"> 名:<input type="text" name="first_name"><br /> 姓:<input type="text" name="last_name" /> <input type="submit" value="提交" /> </form>
當需要選擇多個選項時,我們使用復選框。
下面的 HTML 代碼實例是一個帶有兩個復選框的表單:
<form action="/cgi-bin/cpp_checkbox.cgi" method="POST" target="_blank"> <input type="checkbox" name="maths" value="on" /> 數(shù)學 <input type="checkbox" name="physics" value="on" /> 物理 <input type="submit" value="選擇學科" /> </form>
下面的 C++ 程序會生成 cpp_checkbox.cgi 腳本,用于處理 Web 瀏覽器通過復選框給出的輸入。
#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;
bool maths_flag, physics_flag;
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>向 CGI 程序傳遞復選框數(shù)據(jù)</title>\n";
cout << "</head>\n";
cout << "<body>\n";
maths_flag = formData.queryCheckbox("maths");
if( maths_flag ) {
cout << "Maths Flag: ON " << endl;
}else{
cout << "Maths Flag: OFF " << endl;
}
cout << "<br/>\n";
physics_flag = formData.queryCheckbox("physics");
if( physics_flag ) {
cout << "Physics Flag: ON " << endl;
}else{
cout << "Physics Flag: OFF " << endl;
}
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
當只需要選擇一個選項時,我們使用單選按鈕。
下面的 HTML 代碼實例是一個帶有兩個單選按鈕的表單:
<form action="/cgi-bin/cpp_radiobutton.cgi" method="post" target="_blank"> <input type="radio" name="subject" value="maths" checked="checked"/> 數(shù)學 <input type="radio" name="subject" value="physics" /> 物理 <input type="submit" value="選擇學科" /> </form>
下面的 C++ 程序會生成 cpp_radiobutton.cgi 腳本,用于處理 Web 瀏覽器通過單選按鈕給出的輸入。
#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>向 CGI 程序傳遞單選按鈕數(shù)據(jù)</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 元素。
下面的 HTML 代碼實例是一個帶有 TEXTAREA 框的表單:
<form action="/cgi-bin/cpp_textarea.cgi" method="post" target="_blank"> <textarea name="textcontent" cols="40" rows="4"> 請在這里輸入文本... </textarea> <input type="submit" value="提交" /> </form>
下面的 C++ 程序會生成 cpp_textarea.cgi 腳本,用于處理 Web 瀏覽器通過文本區(qū)域給出的輸入。
#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>向 CGI 程序傳遞文本區(qū)域數(shù)據(jù)</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;
}
當有多個選項可用,但只能選擇一個或兩個選項時,我們使用下拉框。
下面的 HTML 代碼實例是一個帶有下拉框的表單:
<form action="/cgi-bin/cpp_dropdown.cgi" method="post" target="_blank"> <select name="dropdown"> <option value="Maths" selected>數(shù)學</option> <option value="Physics">物理</option> </select> <input type="submit" value="提交"/> </form>
下面的 C++ 程序會生成 cpp_dropdown.cgi 腳本,用于處理 Web 瀏覽器通過下拉框給出的輸入。
#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>向 CGI 程序傳遞下拉框數(shù)據(jù)</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;
}
HTTP 協(xié)議是一種無狀態(tài)的協(xié)議。但對于一個商業(yè)網(wǎng)站,它需要在不同頁面間保持會話信息。例如,一個用戶在完成多個頁面的步驟之后結束注冊。但是,如何在所有網(wǎng)頁中保持用戶的會話信息。
在許多情況下,使用 cookies 是記憶和跟蹤有關用戶喜好、購買、傭金以及其他為追求更好的游客體驗或網(wǎng)站統(tǒng)計所需信息的最有效的方法。
服務器以 cookie 的形式向訪客的瀏覽器發(fā)送一些數(shù)據(jù)。如果瀏覽器接受了 cookie,則 cookie 會以純文本記錄的形式存儲在訪客的硬盤上?,F(xiàn)在,當訪客訪問網(wǎng)站上的另一個頁面時,會檢索 cookie。一旦找到 cookie,服務器就知道存儲了什么。
cookie 是一種純文本的數(shù)據(jù)記錄,帶有 5 個可變長度的字段:
向瀏覽器發(fā)送 cookies 是非常簡單的。這些 cookies 會在 Content-type 字段之前,與 HTTP 頭一起被發(fā)送。假設您想設置 UserID 和 Password 為 cookies,設置 cookies 的步驟如下所示:
#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=m.hgci.cn;\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>CGI 中的 Cookies</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "設置 cookies" << endl;
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
從這個實例中,我們了解了如何設置 cookies。我們使用 Set-Cookie HTTP 頭來設置 cookies。
在這里,有一些設置 cookies 的屬性是可選的,比如 Expires、Domain 和 Path。值得注意的是,cookies 是在發(fā)送行 "Content-type:text/html\r\n\r\n 之前被設置的。
編譯上面的程序,生成 setcookies.cgi,并嘗試使用下面的鏈接設置 cookies。它會在您的計算機上設置四個 cookies:
/cgi-bin/setcookies.cgi
檢索所有設置的 cookies 是非常簡單的。cookies 被存儲在 CGI 環(huán)境變量 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>CGI 中的 Cookies</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "<table border = \"0\" cellspacing = \"2\">";
// 獲取環(huán)境變量
const CgiEnvironment& env = cgi.getEnvironment();
for( cci = env.getCookieList().begin();
cci != env.getCookieList().end();
++cci )
{
cout << "<tr><td>" << cci->getName() << "</td><td>";
cout << cci->getValue();
cout << "</td></tr>\n";
}
cout << "</table><\n";
cout << "<br/>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
現(xiàn)在,編譯上面的程序,生成 getcookies.cgi,并嘗試使用下面的鏈接獲取您的計算機上所有可用的 cookies:
/cgi-bin/getcookies.cgi
這會產(chǎn)生一個列表,顯示了上一節(jié)中設置的四個 cookies 以及您的計算機上所有其他的 cookies:
UserID XYZ Password XYZ123 Domain m.hgci.cn Path /perl
為了上傳一個文件,HTML 表單必須把 enctype 屬性設置為 multipart/form-data。帶有文件類型的 input 標簽會創(chuàng)建一個 "Browse" 按鈕。
<html> <body> <form enctype="multipart/form-data" action="/cgi-bin/cpp_uploadfile.cgi" method="post"> <p>文件:<input type="file" name="userfile" /></p> <p><input type="submit" value="上傳" /></p> </form> </body> </html>
您可以在自己的服務器上嘗試上面的代碼。
下面是用于處理文件上傳的腳本 cpp_uploadfile.cpp:
#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;
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>CGI 中的文件上傳</title>\n";
cout << "</head>\n";
cout << "<body>\n";
// 獲取要被上傳的文件列表
const_file_iterator file = cgi.getFile("userfile");
if(file != cgi.getFiles().end()) {
// 在 cout 中發(fā)送數(shù)據(jù)類型
cout << HTTPContentHeader(file->getDataType());
// 在 cout 中寫入內容
file->writeToStream(cout);
}
cout << "<文件上傳成功>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;
}
上面的實例是在 cout 流中寫入內容,但您可以打開文件流,并把上傳的文件內容保存在目標位置的某個文件中。
更多建議: