【轉】CGIC簡明教程

【轉】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 &rarr;">
   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.如有侵權,請告知,立即刪除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章