CGIC简明教程
http://deepfuture.iteye.com/blog/1435339
CGIC簡明教程1:使用CGIC的基本思路
C語言編程是一項復雜且容易出錯的工作,所以在完成復雜任務時,一定要選擇合適的庫。對于用C語言編寫CGI程序則更是如此。CGIC是非常優秀的C語言CGI庫函數。 其下載地址為:www.boutell.com/cgic/#obtain,現在的版本號是2.05。本站從今天開始,將逐步介紹如何使用CGIC完成各種操作,也可以說是一個Tutorial。(注:本系列涉及的編程環境都是Linux,Windows用戶需要對用到的操作系統命令稍作修改)
本文綱要 :CGIC的安裝、測試安裝、使用CGIC的基本思路;
1) CGIC的下載安裝
從上面提供的官方網址下載了CGIC庫之后,解開壓縮包,里面有大約10個文件,有用的是:
cgic.h:頭文件;
cgic.c:CGIC的源代碼文件;
cgictest.c:CGIC庫的作者提供的一個CGI程序例子;
capture.c:用于調試CGI程序的工具;
Makefile:安裝CGIC的腳本文件;
可以看到,整個庫實際上就是cgic.c一個文件,可以說是非常的精煉。我們可以把CGIC安裝為操作系統的一個動態鏈接庫,這樣我們每次編譯的時候,就不需要有cgic.c這個源文件了。但是由于需要(以后將會看到),我們將修改cgic.c代碼,所以我們不把它安裝進系統。每次編譯的時候,只要把cgic.c和cgic.h放到當前文件夾就好了。
2) 測試安裝
在開始編寫你自己的CGI程序之前,一定要先走通他的例子程序,免得后來程序出錯的時候還不知道是配置有問題,還是你的程序代碼有問題。我們用他自帶cgictest.c來實現自己的第一個C語言CGI程序。
你可以新建一個工作目錄,用于存放你的CGI程序源代碼,把cgic.h, cgic.c, cgictest.c三個文件拷貝到這個目錄,然后建立一個Makefile文件,其內容為:
????? test.cgi:cgictest.c cgic.h cgic.c
????????? gcc -wall cgictest.c cgic.c -o test.cgi?
需要提醒的是,第二行開頭一定是一個tab鍵(且僅有一個),不能使用空格。保存好Makefile的內容之后,執行make命令:make
我們看到,當前目錄下應該多了一個test.cgi文件。
在你的網站根目錄下建立一個cgi-bin目錄(當然名字可以任意取,但作為習慣,一般叫做cgi-bin),然后在Apache的配置文件里賦予其執行CGI代碼的權限,權限修改完之后要重啟Apache。完成之后,把剛才生成的test.cgi放到cgi-bin目錄中。此時我們可以在瀏覽器中輸入以下地址進行訪問:
http://127.0.0.1/cgi-bin/test.cgi
如果正常的話,應該看到一個網頁被展示出來。這樣,第一個C語言的CGI程序就運行起來了。如果瀏覽器報錯,那么多半是配置Apache的時候有些操作沒有正確完成。
3) 使用CGIC的基本思路
從cgic.c的代碼可以看出,它定義了main函數,而在cgictest.c中定義了一個cgiMain函數。也就是說,對于使用CGIC編寫的CGI程序,都是從cgic.c中的代碼進入,在庫函數完成了一系列必要的操作(比如解析參數、獲取系統環境變量)之后,它才會調用你的代碼(從你定義的cgiMain進入)。
另外一點就是,cgi程序輸出HTML頁面的方式都是使用printf把頁面一行一行地打印出來,比如cgictest.c中的這一段代碼:
fprintf(cgiOut, "<textarea NAME=\"address\" ROWS=4 COLS=40>\n");
fprintf(cgiOut, "Default contents go here. \n");
fprintf(cgiOut, "</textarea>\n");
上面這段代碼的運行結果就是在頁面上輸出一個textarea。 第一個參數cgiOut實際上就是stdin,所以我們可以直接使用printf,而不必使用fprintf。不過在調試的時候會用到fprintf來重定向輸出。
這種方式與Java Servlet非常類似,Servlet也是通過調用打印語句System.out.println(…)來輸出一個頁面。(不過后來Java推出了JSP來克服這種不便。)但是與Servlet不同的地方在于,使用C語言的我們還要自己輸出HTML頭部(聲明文檔類型):
cgiHeaderContentType("text/html");
這個語句的調用一定要在所有printf語句之前。而這個語句執行的任務實際上就是:
void cgiHeaderContentType(char *mimeType) {
?? fprintf(cgiOut, "Content-type: %s\r\n\r\n", mimeType);
}
這個語句告訴瀏覽器,這次傳來的數據是什么類型,是一個HTML文檔,還是一個bin文件… 如果是個HTML文檔,就通過瀏覽器窗口顯示,如果是一個bin(二進制)文件,則打開下載窗口,讓用戶選擇是否保存文件以及保存文件的路徑。理解了這幾點之后,你就可以編寫自己的CGIC程序了。新建一個文件test.c試試:
#include <stdio.h>?
#include "cgic.h"?
#include <string.h>?
#include <stdlib.h>?
int cgiMain() {?
cgiHeaderContentType("text/html");?
fprintf(cgiOut, "<HTML><HEAD>\n");?
fprintf(cgiOut, "<TITLE>My First CGI</TITLE></HEAD>\n");?
fprintf(cgiOut, "<BODY><H1>Hello CGIC</H1></BODY>\n");?
fprintf(cgiOut, "</HTML>\n");?
return 0;?
}?
把Makefile文件中的cgitest.c全部換稱test.c,保存,再執行make命令即可。此時通過瀏覽器訪問,會在頁面上看到一個大大的“Hello CGIC”。
CGIC簡明教程2:獲取Get請求字符串
Get請求就是我們在瀏覽器地址欄輸入URL時發送請求的方式,或者我們在HTML中定義一個表單(form)時,把action屬性設為“Get”時的工作方式;Get請求字符串就是跟在URL后面以問號“?”開始的字符串,但不包括問號。比如這樣的一個請求:
http://127.0.0.1/cgi-bin/out.cgi?ThisIsTheGetString
在上面這個URL中,“ThisIsTheGetString”就是Get請求字符串。在進入我們自己編寫的cgi代碼之前,CGIC庫已經事先把這個字符串取到了,我們可以在程序中直接獲得,要做的僅僅是在你編寫的cgiMain方法前面加入以下聲明:
extern char *cgiQueryString;
現在給出一個簡單的例子,這個例子跟上一篇的測試程序非常相似,只不過程序的輸出是使用者輸入的Get請求字符串。
#include <stdio.h>
#include "cgic.h"?
#include <string.h>?
#include <stdlib.h>?
extern char *cgiQueryString;?
int cgiMain() {?
cgiHeaderContentType("text/html");?
fprintf(cgiOut, "<HTML><HEAD>\n");?
fprintf(cgiOut, "<TITLE>My CGIC</TITLE></HEAD>\n");?
fprintf(cgiOut, "<BODY>");?
fprintf(cgiOut, "<H1>%s</H1>",cgiQueryString);?
fprintf(cgiOut, "</BODY>\n");?
fprintf(cgiOut, "</HTML>\n");?
return 0;?
}?
假設把這個程序編譯成out.cgi(編譯方法參見上一篇),并部署到Web服務器的cgi-bin目錄下,當用戶在瀏覽器地址欄輸入本文開頭給出的URL字符串時,瀏覽器頁面上會顯示:ThisIsTheGetString
我們也可以編寫一個用于測試的HTML頁面:
<html>?
<head>?
<title>Test</title>?
</head>?
<body>?
<form action="cgi-bin/out.cgi" method="get">?
<input type="text" name="theText">?
<input type="submit" value="Continue →">?
</form>?
</body>?
</html>?
文件的部署結構應該為:
|test.html
|---cgi-bin/out.cgi
大家可以試試,通過瀏覽器訪問http://127.0.0.1/test.html,在文本框內輸入一些字符,并點擊提交按鈕,然后就可以看到cgi程序的執行結果:把在文本框輸入的字符原樣顯示在瀏覽器上。
CGIC簡明教程3:反轉義
瀏覽器在發送Get請求時,會把請求字符串進行轉義操作(英文術語為: escape); 比如,我們在地址欄輸入(注意最后”it’s me”中的空格):http://localhost/~Jack/cgi-bin/out.cgi?it's me
瀏覽器會把它轉義為:http://localhost/~Jack/cgi-bin/out.cgi?it's%20me
在上一篇最后給出的例子中,如果在文本框內輸入 it's me
你會發現,瀏覽器最終發送的請求為?http://localhost/~Jack/cgi-bin/out.cgi?theText=it%27s+me
通過CGIC,我們可以把這些被轉義后的字符還原為我們本來的輸入,這個過程就叫“反轉義” (Unescape)。不過這個過程有點像hack他的代碼。
整個過程分三個步驟:
1)打開cgic.c,找到這一行語句:
static cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);
注意,我們要找的只是這個函數聲明,不是函數定義;
2)在這個函數聲明語句的上方,你會看到一個結構體定義:
typedef enum {?
cgiUnescapeSuccess,?
cgiUnescapeMemory?
} cgiUnescapeResultType;?
把這幾行語句復制到cgic.h文件中,并在這里把它注釋掉;同時還要刪除在第一步中找到的函數聲明語句中的“static”關鍵字。
3)我們現在就可以使用反轉義函數cgiUnescapeChars了:在你自己的代碼(按照慣例,還是test.c)中,加入以下聲明語句即可
extern cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);
接下來我們給出一段完整的test.c代碼?
#include <stdio.h>
#include "cgic.h"?
#include <string.h>?
#include <stdlib.h>?
extern char *cgiQueryString;?
extern cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);?
int cgiMain() {?
char * buffer;?
cgiHeaderContentType("text/html");?
fprintf(cgiOut, "<HTML><HEAD>\n");?
fprintf(cgiOut, "<TITLE>My CGI</TITLE></HEAD>\n");?
fprintf(cgiOut, "<BODY>");?
cgiUnescapeChars(&buffer, cgiQueryString, strlen(cgiQueryString));?
fprintf(cgiOut, "<H1>%s</H1>",buffer);?
fprintf(cgiOut, "</BODY>\n");?
fprintf(cgiOut, "</HTML>\n");?
free(buffer);?
return 0;?
}?
值得注意的是,buffer的存儲空間是cgiUnescapeChars幫你分配的,但最后要由你自己來釋放(free),這一點千萬不可忘記。下面你可以結合上一篇給出的測試用html代碼試試該cgi程序的運行結果,也可以直接在瀏覽器地址欄輸入一些帶有特殊符號的字符串。
最后講一下為什么不得不用這種hacker的方式來完成該任務,而CGIC不顯式提供?CGIC的出發點是,我們平時只需要解析請求中的鍵值對,比如:”?q=nice&client=IE”,當我們在服務端查詢“q”的值時,我們就能得到“nice”。CGIC有一族函數幫助我們完成這個任務,比如cgiFormString(以后會講到)。在解析這種請求格式的時候,如果我們提供的參數值含有被轉義的字符,那么CGIC就會在內部調用cgiUnescapeChars完成反轉義。但是,有時候我們會發送非常復雜的Get請求字符串,但并不是“鍵-值”對的格式。這就需要直接使用cgiUnescapeChars進行反轉義了。例如:假設我們有個服務端cgi程序chat.cgi,這是個網絡聊天機器人(也許你可以開發自己的Web版MSN機器人、QQ機器人)。如果我們發送如下請求:http://127.0.0.1/cgi-bin/chat.cgi?"this?is a cgi user"
那么chat.cgi就會把“this is a cgi user”當做你對它說的話,經過處理,它會回復一段語句。為了方便,我們并沒有寫成“鍵-值”對的形式。這個時候被我們hack的cgiUnescapeChars就能派上用場了。
CGIC簡明教程4:獲取請求中的參數值
在提交一個表單(form)時,怎樣把表單內的值提取出來呢?比如下面這個表單:
<form action="cgi-bin/out.cgi" method="POST">
??? <input type="text" name="name" />
??? <input type="text" name="number" />
??? <input type="submit" value="Submit" />
</form>
當out.cgi收到請求時,需要把輸入框”name”和輸入框”number”內的值提取出來。而且不管form中的action是GET還是POST,都要有效。下面給出示例代碼:
#include <stdio.h>
#include "cgic.h"?
#include <string.h>?
#include <stdlib.h>???
int cgiMain() {?
??? char name[241];?
??? char number[241];?
??? cgiHeaderContentType("text/html");?
??? fprintf(cgiOut, "<HTML><HEAD>\n");?
??? fprintf(cgiOut, "<TITLE>My CGI</TITLE></HEAD>\n");?
??? fprintf(cgiOut, "<BODY>");?
??? cgiFormString("name", name, 241);?
??? cgiFormString("number", number, 241);?
??? fprintf(cgiOut, "<H1>%s</H1>",name);?
??? fprintf(cgiOut, "<H1>%s</H1>",number);?
??? fprintf(cgiOut, "</BODY>\n");?
??? fprintf(cgiOut, "</HTML>\n");?
??? return 0;?
}
從上面的代碼可以看出,第13行和第14行獲取了輸入框的值。獲取輸入參數值在CGIC中其實有一族函數,cgiFormString是其中最常用的一個。cgiFormStringNoNewlines用來去掉換行符(如果用戶是在一個TextArea里輸入字符的話);cgiFormStringSpaceNeeded用于測試輸入值的長度,可以以此為依據,然后按需精確分配緩沖區
總結
- 上一篇: linux 路由 route命令
- 下一篇: cgic: 为C语言编写CGI的C函数库