C++ Web 編程
什麼是CGI?
- 公共網關接口(CGI),是一套標準,定義了信息是如何在Web 服務器和客戶端腳本之間進行交換的。
- CGI 規範目前是由NCSA 維護的,NCSA 定義CGI 如下:
- 公共網關接口(CGI),是一種用於外部網關程序與信息服務器(如HTTP 服務器)對接的接口標準。
- 目前的版本是CGI/1.1,CGI/1.2 版本正在推進中。
Web 瀏覽
為了更好地了解CGI 的概念,讓我們點擊一個超鏈接,瀏覽一個特定的網頁或URL,看看會發生什麼。
- 您的瀏覽器聯繫上HTTP Web 服務器,並請求URL,即文件名。
- Web 服務器將解析URL,並查找文件名。 如果找到請求的文件,Web 服務器會把文件發送回瀏覽器,否則發送一條錯誤消息,表明您請求了一個錯誤的文件。
- Web 瀏覽器從Web 服務器獲取響應,並根據接收到的響應來顯示文件或錯誤消息。
然而,以這種方式搭建起來的HTTP 服務器,不管何時請求目錄中的某個文件,HTTP 服務器發送回來的不是該文件,而是以程序形式執行,並把執行產生的輸出發送回瀏覽器顯示出來。
公共網關接口(CGI),是使得應用程序(稱為CGI 程序或CGI 腳本)能夠與Web 服務器以及客戶端進行交互的標準協議。 這些CGI 程序可以用Python、PERL、Shell、C 或C++ 等進行編寫。
CGI 架構圖
下圖演示了CGI 的架構:
Web 服務器配置
在您進行CGI 編程之前,請確保您的Web 服務器支持CGI,並已配置成可以處理CGI 程序。 所有由HTTP 服務器執行的CGI 程序,都必須在預配置的目錄中。 該目錄稱為CGI 目錄,按照慣例命名為/var/www/cgi-bin。 雖然CGI文件是C++可執行文件,但是按照慣例它的擴展名是.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>
在這裡,我們假設已經配置好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 - 第一个 CGI 程序</title>\n"; cout << "</head>\n"; cout << "<body>\n"; cout << "<h2>Hello World! 这是我的第一个 CGI 程序</h2>\n"; cout << "</body>\n"; cout << "</html>\n"; return 0; }
編譯上面的代碼,把可執行文件命名為cplusplus.cgi,並把這個文件保存在/var/www/cgi-bin 目錄中。 在運行CGI程序之前,請使用chmod 755 cplusplus.cgi UNIX命令來修改文件模式,確保文件可執行。 訪問可執行文件,您會看到下面的輸出:
Hello World! 這是我的第一個CGI 程序
上面的C++ 程序是一個簡單的程序,把它的輸出寫在STDOUT 文件上,即顯示在屏幕上。 在這裡,值得注意一點,第一行輸出Content-type:text/html\r\n\r\n 。 這一行發送回瀏覽器,並指定要顯示在瀏覽器窗口上的內容類型。 您必須理解CGI 的基本概念,這樣才能進一步使用Python 編寫更多複雜的CGI 程序。 C++ CGI 程序可以與任何其他外部的系統(如RDBMS)進行交互。
HTTP 頭信息
行Content-type:text/html\r\n\r\n是HTTP頭信息的組成部分,它被發送到瀏覽器,以便更好地理解頁面內容。 HTTP 頭信息的形式如下:
HTTP 字段名称: 字段内容 例如 Content-type: text/html\r\n\r\n
還有一些其他的重要的HTTP 頭信息,這些在您的CGI 編程中都會經常被用到。
头信息 | 描述 |
---|---|
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 | 要返回的数据的长度,以字节为单位。浏览器使用这个值来表示一个文件的预计下载时间。 |
Set-Cookie: String | 通过string设置 cookie。 |
CGI 環境變量
所有的CGI 程序都可以訪問下列的環境變量。 這些變量在編寫CGI 程序時扮演了非常重要的角色。
变量名 | 描述 |
---|---|
CONTENT_TYPE | 内容的数据类型。当客户端向服务器发送附加内容时使用。例如,文件上传等功能。 |
CONTENT_LENGTH | 查询的信息长度。只对 POST 请求可用。 |
HTTP_COOKIE | 以键 & 值对的形式返回设置的 cookies。 |
HTTP_USER_AGENT | 用户代理请求标头字段,递交用户发起请求的有关信息,包含了浏览器的名称、版本和其他平台性的附加信息。 |
PATH_INFO | CGI 脚本的路径。 |
QUERY_STRING | 通过 GET 方法发送请求时的 URL 编码信息,包含 URL 中问号后面的参数。 |
REMOTE_ADDR | 发出请求的远程主机的 IP 地址。这在日志记录和认证时是非常有用的。 |
REMOTE_HOST | 发出请求的主机的完全限定名称。如果此信息不可用,则可以用 REMOTE_ADDR 来获取 IP 地址。 |
REQUEST_METHOD | 用于发出请求的方法。最常见的方法是 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 环境变量</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 << "环境变量不存在。"; } cout << "</td></tr>\n"; } cout << "</table><\n"; cout << "</body>\n"; cout << "</html>\n"; return 0; }
C++ CGI 庫
在真實的實例中,您需要通過CGI 程序執行許多操作。 這裡有一個專為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 ,查看相關的庫文檔。
GET 和POST 方法
您可能有遇到過這樣的情況,當您需要從瀏覽器傳遞一些信息到Web 服務器,最後再傳到CGI 程序。 通常瀏覽器會使用兩種方法把這個信息傳到Web 服務器,分別是GET 和POST 方法。
使用GET 方法傳遞信息
GET 方法發送已編碼的用戶信息追加到頁面請求中。 頁面和已編碼信息通過? 字符分隔開,如下所示:
http://www.test.com/cgi-bin/cpp.cgi?key1=value1&key2=value2
GET 方法是默認的從瀏覽器向Web 服務器傳信息的方法,它會在瀏覽器的地址欄中生成一串很長的字符串。 當您向服務器傳密碼或其他一些敏感信息時,不要使用GET 方法。 GET 方法有大小限制,在一個請求字符串中最多可以傳1024 個字符。
當使用GET 方法時,是使用QUERY_STRING http 頭來傳遞信息,在CGI 程序中可使用QUERY_STRING 環境變量來訪問。
您可以通過在URL 後跟上簡單連接的鍵值對,也可以通過使用HTML <FORM> 標籤的GET 方法來傳信息。
簡單的URL 實例: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; }
現在,編譯上面的程序,如下所示:
$g++ -o cpp_get.cgi cpp_get.cpp -lcgicc
生成cpp_get.cgi,並把它放在CGI 目錄中,並嘗試使用下面的鏈接進行訪問:
/cgi-bin/cpp_get.cgi?first_name=ZARA&last_name=ALI
這會產生以下結果:
名:ZARA 姓:ALI
簡單的表單實例:GET 方法
下面是一個簡單的實例,使用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>
下面是上述表單的實際輸出,請輸入名和姓,然後點擊提交按鈕查看結果。
使用POST 方法傳遞信息
一個更可靠的向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>
向CGI 程序傳遞複選框數據
當需要選擇多個選項時,我們使用複選框。
下面的HTML 代碼實例是一個帶有兩個複選框的表單:
<form action="/cgi-bin/cpp_checkbox.cgi" method="POST" target="_blank"> <input type="checkbox" name="maths" value="on" /> 数学 <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 程序传递复选框数据</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; }
向CGI 程序傳遞單選按鈕數據
當只需要選擇一個選項時,我們使用單選按鈕。
下面的HTML 代碼實例是一個帶有兩個單選按鈕的表單:
<form action="/cgi-bin/cpp_radiobutton.cgi" method="post" target="_blank"> <input type="radio" name="subject" value="maths" checked="checked"/> 数学 <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 程序传递单选按钮数据</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 程序傳遞文本區域數據
當需要向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 瀏覽器通過文本區域給出的輸入。
#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 程序传递文本区域数据</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>数学</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 程序传递下拉框数据</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 是記憶和跟踪有關用戶喜好、購買、佣金以及其他為追求更好的遊客體驗或網站統計所需信息的最有效的方法。
它是如何工作的
服務器以cookie 的形式向訪客的瀏覽器發送一些數據。 如果瀏覽器接受了cookie,則cookie 會以純文本記錄的形式存儲在訪客的硬盤上。 現在,當訪客訪問網站上的另一個頁面時,會檢索cookie。 一旦找到cookie,服務器就知道存儲了什麼。
cookie 是一種純文本的數據記錄,帶有5 個可變長度的字段:
- Expires : cookie的過期日期。如果此字段留空,cookie 會在訪客退出瀏覽器時過期。
- Domain :網站的域名。
- Path :設置cookie的目錄或網頁的路徑。如果您想從任意的目錄或網頁檢索cookie,此字段可以留空。
- Secure :如果此字段包含單詞"secure",那麼cookie只能通過安全服務器進行檢索。如果此字段留空,則不存在該限制。
- Name=Value : cookie以鍵值對的形式被設置和獲取。
設置Cookies
向瀏覽器發送cookies 是非常簡單的。 這些cookies 會在Content-type 字段之前,與HTTP 頭一起被發送。 假設您想設置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=www.w3cschool.cc;\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是在發送行"Content-type:text/html\r\n\r\n之前被設置的。
編譯上面的程序,生成setcookies.cgi,並嘗試使用下面的鏈接設置cookies。 它會在您的計算機上設置四個cookies:
/cgi-bin/setcookies.cgi
獲取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>CGI 中的 Cookies</title>\n"; cout << "</head>\n"; cout << "<body>\n"; cout << "<table border = \"0\" cellspacing = \"2\">"; // 获取环境变量 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; }
現在,編譯上面的程序,生成getcookies.cgi,並嘗試使用下面的鏈接獲取您的計算機上所有可用的cookies:
/cgi-bin/getcookies.cgi
這會產生一個列表,顯示了上一節中設置的四個cookies 以及您的計算機上所有其他的cookies:
UserID XYZ Password XYZ123 Domain www.w3cschool.cc Path /perl
文件上傳實例
為了上傳一個文件,HTML表單必須把enctype屬性設置為multipart/form-data 。 帶有文件類型的input 標籤會創建一個"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 中发送数据类型 cout << HTTPContentHeader(file->getDataType()); // 在 cout 中写入内容 file->writeToStream(cout); } cout << "<文件上传成功>\n"; cout << "</body>\n"; cout << "</html>\n"; return 0; }
上面的實例是在cout流中寫入內容,但您可以打開文件流,並把上傳的文件內容保存在目標位置的某個文件中。