【轉】CGIC簡明教程
本系列的目的是演示如何使用C語言的CGI庫“CGIC”完成Web開發的各種要求。
*********************************
基礎知識
1.使用CGIC的基本思路
2.獲取Get請求字符串
3.反轉義
4.獲取請求中的參數值
進階訓練
5.用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試試:
下載: test.c
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5. int cgiMain() {
6. cgiHeaderContentType("text/html");
7. fprintf(cgiOut, "<HTML><HEAD>\n");
8. fprintf(cgiOut, "<TITLE>My First CGI</TITLE></HEAD>\n");
9. fprintf(cgiOut, "<BODY><H1>Hello CGIC</H1></BODY>\n");
10. fprintf(cgiOut, "</HTML>\n");
11. return 0;
12. }
```
把Makefile文件中的cgitest.c全部換稱test.c,保存,再執行make命令即可。
此時通過瀏覽器訪問,會在頁面上看到一個大大的“Hello 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請求
字符串。
下載: test.c
```
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5.
6. extern char *cgiQueryString;
7. int cgiMain() {
8. cgiHeaderContentType("text/html");
9. fprintf(cgiOut, "<HTML><HEAD>\n");
10. fprintf(cgiOut, "<TITLE>My CGIC</TITLE></HEAD>\n");
11. fprintf(cgiOut, "<BODY>");
12. fprintf(cgiOut, "<H1>%s</H1>",cgiQueryString);
13. fprintf(cgiOut, "</BODY>\n");
14. fprintf(cgiOut, "</HTML>\n");
15. return 0;
16. }
假設把這個程序編譯成out.cgi(編譯方法參見上一篇),並部署到Web服務器的cgi-bin目錄下,當用戶在瀏覽
器地址欄輸入本文開頭給出的URL字符串時,瀏覽器頁面上會顯示:
ThisIsTheGetString
我們也可以編寫一個用於測試的HTML頁面:
下載: test.html
1. <html>
2. <head>
3. <title>Test</title>
4. </head>
5. <body>
6. <form action="cgi-bin/out.cgi" method="get">
7. <input type="text" name="theText">
8. <input type="submit"
value="http://www.cnblogs.com/NewJourney/archive/2011/12/28/Continue →">
9. </form>
10. </body>
11. </html>
文件的部署結構應該爲:
|test.html
|—-cgi-bin/out.cgi
大家可以試試,通過瀏覽器訪問http://127.0.0.1/test.html,在文本框內輸入一些字符,並點擊提交按鈕,
然後就可以看到cgi程序的執行結果:把在文本框輸入的字符原樣顯示在瀏覽器上。
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 me
在上一篇最後給出的例子中,如果在文本框內輸入
it’s me
你會發現,瀏覽器最終發送的請求爲
http://localhost/~Jack/cgi-bin/out.cgi?theText=it’s+me
通過CGIC,我們可以把這些被轉義後的字符還原爲我們本來的輸入,這個過程就叫“反轉義” ()。
不過這個過程有點像hack他的代碼。
整個過程分三個步驟:
1)打開cgic.c,找到這一行語句:
static cgiResultType cgiChars(char **sp, char *cp, int len);
注意,我們要找的只是這個函數聲明,不是函數定義;
2)在這個函數聲明語句的上方,你會看到一個結構體定義:
1. typedef enum {
2. cgiSuccess,
3. cgiMemory
4. } cgiResultType;
把這幾行語句複製到cgic.h文件中,並在這裏把它註釋掉;
同時還要刪除在第一步中找到的函數聲明語句中的“static”關鍵字。
3)我們現在就可以使用反轉義函數cgiChars了:
在你自己的代碼(按照慣例,還是test.c)中,加入以下聲明語句即可
extern cgiResultType cgiChars(char **sp, char *cp, int len);
接下來我們給出一段完整的test.c代碼
下載: test.c
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5.
6. extern char *cgiQueryString;
7. extern cgiResultType cgiChars(char **sp, char *cp, int len);
8. int cgiMain() {
9. char * buffer;
10. cgiHeaderContentType("text/html");
11. fprintf(cgiOut, "<HTML><HEAD>\n");
12. fprintf(cgiOut, "<TITLE>My CGI</TITLE></HEAD>\n");
13. fprintf(cgiOut, "<BODY>");
14. cgiChars(&buffer, cgiQueryString, strlen(cgiQueryString));
15. fprintf(cgiOut, "<H1>%s</H1>",buffer);
16. fprintf(cgiOut, "</BODY>\n");
17. fprintf(cgiOut, "</HTML>\n");
18. free(buffer);
19. return 0;
20. }
值得注意的是,buffer的存儲空間是cgiChars幫你分配的,但最後要由你自己來釋放(free),這一
點千萬不可忘記。
下面你可以結合上一篇給出的測試用html代碼試試該cgi程序的運行結果,也可以直接在瀏覽器地址欄輸入一些
帶有特殊符號的字符串。
最後講一下爲什麼不得不用這種hacker的方式來完成該任務,而CGIC不顯式提供?
CGIC的出發點是,我們平時只需要解析請求中的鍵值對,比如:”?q=nice&client=IE”,當我們在服務端查詢
“q”的值時,我們就能得到“nice”。CGIC有一族函數幫助我們完成這個任務,比如cgiFormString(以後會
講到)。在解析這種請求格式的時候,如果我們提供的參數值含有被轉義的字符,那麼CGIC就會在內部調用
cgiChars完成反轉義。
但是,有時候我們會發送非常複雜的Get請求字符串,但並不是“鍵-值”對的格式。這就需要直接使用
cgiChars進行反轉義了。
例如:假設我們有個服務端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的cgiChars就能派上用場了。
4:獲取請求中的參數值
我們在提交一個表單(form)時,怎樣把表單內的值提取出來呢?
比如下面這個表單:
<form action="cgi-bin/out.cgi" method="POST">
<input type="text" name="name" />
<input type="text" name="number" />
<input type="submit" value="http://www.cnblogs.com/NewJourney/archive/2011/12/28/Submit" />
</form>
當out.cgi收到請求時,需要把輸入框”name”和輸入框”number”內的值提取出來。而且不管form中的action
是GET還是POST,都要有效。
下面給出示例代碼:
下載: test.c
1. #include <stdio.h>
2. #include "cgic.h"
3. #include <string.h>
4. #include <stdlib.h>
5.
6. int cgiMain() {
7. char name[241];
8. char number[241];
9. cgiHeaderContentType("text/html");
10. fprintf(cgiOut, "<HTML><HEAD>\n");
11. fprintf(cgiOut, "<TITLE>My CGI</TITLE></HEAD>\n");
12. fprintf(cgiOut, "<BODY>");
13. cgiFormString("name", name, 241);
14. cgiFormString("number", number, 241);
15. fprintf(cgiOut, "<H1>%s</H1>",name);
16. fprintf(cgiOut, "<H1>%s</H1>",number);
17. fprintf(cgiOut, "</BODY>\n");
18. fprintf(cgiOut, "</HTML>\n");
19. return 0;
20. }
從上面的代碼可以看出,第13行和第14行獲取了輸入框的值。
獲取輸入參數值在CGIC中其實有一族函數,cgiFormString是其中最常用的一個。
cgiFormStringNoNewlines用來去掉換行符(如果用戶是在一個TextArea裏輸入字符的話);
cgiFormStringSpaceNeeded 用於測試輸入值的長度,可以以此爲依據,然後按需精確分配緩衝區。
5.用C語言庫(CGIC)編寫CGI,實現文件上傳
用C語言編寫cgi程序的話,多半會用到CGIC。這是個非常流行的庫,遇到文件上傳之類的應用更是離不開它。
官方頁面及下載地址爲:www.boutell.com/cgic/#obtain
不少網站都有文件上傳的功能,本文展示如何用CGIC庫編寫文件上傳的服務端程序,最後給出一段簡單的HTML
代碼,供大家測試使用。
下載: upload.c
1. #include<stdio.h>
2. #include<string.h>
3. #include<unistd.h>
4. #include<fcntl.h>
5. #include<sys/stat.h>
6. #include"cgic.h"
7. #define BufferLen 1024
8. int cgiMain(void){
9. cgiFilePtr file;
10. int targetFile;
11. mode_t mode;
12. char name[128];
13. char fileNameOnServer[64];
14. char contentType[1024];
15. char buffer[BufferLen];
16. char *tmpStr=NULL;
17. int size;
18. int got,t;
19. cgiHeaderContentType("text/html");
20. //取得html頁面中file元素的值,應該是文件在客戶機上的路徑名
21. if (cgiFormFileName("file", name, sizeof(name)) !=cgiFormSuccess) {
22. fprintf(stderr,"could not retrieve filename\n");
23. goto FAIL;
24. }
25. cgiFormFileSize("file", &size);
26. //取得文件類型,不過本例中並未使用
27. cgiFormFileContentType("file", contentType, sizeof(contentType));
28. //目前文件存在於系統臨時文件夾中,通常爲/tmp,通過該命令打開臨時文件。臨時文件的名字與
用戶文件的名字不同,所以不能通過路徑/tmp/userfilename的方式獲得文件
29. if (cgiFormFileOpen("file", &file) != cgiFormSuccess) {
30. fprintf(stderr,"could not open the file\n");
31. goto FAIL;
32. }
33. t=-1;
34. //從路徑名解析出用戶文件名
35. while(1){
36. tmpStr=strstr(name+t+1,"\");
37. if(NULL==tmpStr)
38. tmpStr=strstr(name+t+1,"/");//if "\" is not path separator, try "/"
39. if(NULL!=tmpStr)
40. t=(int)(tmpStr-name);
41. else
42. break;
43. }
44. strcpy(fileNameOnServer,name+t+1);
45. mode=S_IRWXU|S_IRGRP|S_IROTH;
46. //在當前目錄下建立新的文件,第一個參數實際上是路徑名,此處的含義是在cgi程序所在的目錄(
當前目錄))建立新文件
47. targetFile=open(fileNameOnServer,O_RDWR|O_CREAT|O_TRUNC|O_APPEND,mode);
48. if(targetFile<0){
49. fprintf(stderr,"could not create the new file,%s\n",fileNameOnServer);
50. goto FAIL;
51. }
52. //從系統臨時文件中讀出文件內容,並放到剛創建的目標文件中
53. while (cgiFormFileRead(file, buffer, BufferLen, &got) ==cgiFormSuccess){
54. if(got>0)
55. write(targetFile,buffer,got);
56. }
57. cgiFormFileClose(file);
58. close(targetFile);
59. goto END;
60. FAIL:
61. fprintf(stderr,"Failed to upload");
62. return 1;
63. END:
64. printf("File "%s" has been uploaded",fileNameOnServer);
65. return 0;
66. }
假設該文件存儲爲upload.c,則使用如下命令編輯:
gcc -Wall upload.c cgic.c -o upload.cgi
編譯完成後把upload.cgi複製到你部署cgi程序的目錄(通常命名爲cgi-bin)。
正式部署時,請務必修改用open創建新文件那一行代碼。把open的第一個參數設置爲目標文件在服務器上存儲
的絕對路徑,或者相對於cgi程序的相對路徑。本例中,出於簡單考慮,在cgi程序所在目錄下創建新文件。
測試用HTML代碼:
下載: upload.html
1. <form target="_blank" method="post" action="cgi-bin/upload.cgi">
2. <input name="file" type="file" /> <input name="submit" type="submit" />
3. </form>
最後的文件目錄結構爲
/MyWebRoot
|—/upload.html
|—/cgi-bin
|——/upload.cgi
當然,你必須配置能夠cgi-bin,並且程序要有權限在cgi-bin目錄下創建文件(因爲此例把文件上傳到cgi-bin
目錄下)。
那麼如何控制上傳文件的大小呢?因爲你有時會不允許用戶上傳太大的文件。
通過分析cgic.c的源代碼,我們發現它定義了一個變量cgiContentLength,表示請求的長度。但我們需要首先
判斷這是一個上傳文件的請求,然後才能根據cgiContentLength來檢查用戶是否要上傳一個太大的文件。
cgic.c的main函數中進行了一系列if-else判斷來檢查請求的類型,首先確定這是一個post請求,然後確定數據
的編碼方式爲 “multipart/form-data”,這個判斷通過之後,就要開始準備接收數據了。所以我們要在接收
數據開始之前使用 cgiContentLength判斷大小,如果超過標準,就立即返回,不允許繼續操作。
下面貼出修改後代碼片段(直接修改cgic.c的源代碼即可):
1. else if (cgiStrEqNc(cgiContentType, "multipart/form-data")) {
2. #ifdef CGICDEBUG
3. CGICDEBUGSTART
4. fprintf(dout, "Calling PostMultipartInput\n");
5. CGICDEBUGEND
6. #endif
7. //我的代碼
8. //UpSize:文件長度上限值,以byte爲單位,UpSize是一個int變量,因爲cgiContentLength的類型爲int
9. if(cgiContentLength>UpSize){
10. cgiHeaderContentType("text/html");
11. printf("File too large!\n");
12. cgiFreeResources();
13. return -1;
14. }
15. //我的代碼結束
16. if (cgiParsePostMultipartInput() != cgiParseSuccess) {
17. #ifdef CGICDEBUG
18. CGICDEBUGSTART
19. fprintf(dout, "PostMultipartInput failed\n");
20. CGICDEBUGEND
21. #endif
22. cgiFreeResources();
23. return -1;
24. }
25. #ifdef CGICDEBUG
26. CGICDEBUGSTART
27. fprintf(dout, "PostMultipartInput succeeded\n");
28. CGICDEBUGEND
29. #endif
30. }
31. }
變量UpSize表示文件大小的上限。在cgic.c的main中找到相關代碼,並修改成上面這樣即可。你可以在cgic.c
中定義UpSize,也可以在剛纔完成的upload.c中定義,然後在cgic.c中用extern方式引用。
特別聲明:
1.資料來源於互聯網,版權歸屬原作者;
2.資料內容屬於網絡意見,與本賬號立場無關;
3.如有侵權,請告知,立即刪除。