cgic: CGI的C函數庫


下載回源碼包以后,就3個文件:
cgic.c      函數庫
capture.c   一個很簡單的CGI例子,僅僅輸出兩行提示文字
cgictest.c  一個演示讀取form表單數據的CGI例子

首先在vc6里創建一個空的win32靜態庫cgic,然后添加cgic.c,編譯后得到cgic.lib庫
創建一個空的console工程cgictest,然后添加cgictest.c,在setting|link添加cgic.lib,編譯得到cgictest.exe
將cgictest.exe拷貝到事先創建好的測試網站的cgi-bin下,在iis里右擊cgictest.exe瀏覽,就可以看到一個form表單,幾乎所有的控件都包括了
最后以capture.c為例說明一下例子的源碼結構:
#include "cgic.h"

int cgiMain() {
 cgiWriteEnvironment("/CHANGE/THIS/PATH/capcgi.dat");
 cgiHeaderContentType("text/html");
 fprintf(cgiOut, "<title>Captured</title>/n");
 fprintf(cgiOut, "<h1>Captured</h1>/n");
 fprintf(cgiOut, "Your form submission was captured for use in/n");
 fprintf(cgiOut, "debugging CGI code./n");
 return 0;
}

可以看到capture.c實在是太簡單了,主函數main被定義在了cgic.c里,在主函數的最后調用了cgiMain(),所有我們要開發一個自己的cgi的話,
只需要實現一個cgiMain()即可,就像capture.c那樣,其他的功能就看你的需求嘍.cgic和gsoap相結合就可以打造一個基於cgi的webservice的服務器端了.


原文及源碼:  http://www.boutell.com/cgic/

網上看到一篇中譯版的,轉過來附在后面

cgic: 為C語言編寫CGI的C函數庫 
由Thomas Boutell開發 
目錄 

CGIC介紹 
怎樣寫CGIC應用程序 
怎樣產生圖片在CGIC中? 
CGI調試特征: 利用捕獲 
cgic函數參考 
cgic變量參考 
cgic結果編碼參考 
cgic快速索引 

一般的UNIX系統都支持ANSIC,增加相應的庫函數(和相應的h文件)就可以實現CGI。在此我向大家推薦一個用於CGI編程的ANSIC庫:cgic。 

cgic是用來生成基於CGI的WWW應用程序的C語言函數庫,它有以下功能: 

*對數據進行語法分析 

*接收以GET和PSOT兩種方式發送的數據 

*把FORM中的不同域連接成連續的串 

*為檢索FORM數據而提供字符串,整數,浮點以及單項和多項選擇功能 

*為數字字段提供邊界檢測 

*把CGI環境變量加載到非空的C串中 

*為調試而捕捉CGI狀態 

*提供相對安全的系統調用功能 

用一般ANSI C或C++編譯器就可以編譯cgic程序,不過與通常C程序不同的是,用cgic寫的源碼其主函數是cgiMain(),而不是通常的main()。cgic的函數庫會自動把cgiMain連接到相應的main()上去。 


-------------------------------------------------------------------------------- 


寫CGIC程序 
Note: 所有的cgic應用程序必須連接cgic.c. 
用cgimain()替代main() 必須包含: #include"cgic.h." 

基本結構cgictest.c: 


int cgiMain() { 
#if DEBUG 
/* Load a saved CGI scenario if we're debugging */ 
cgiReadEnvironment("/path/to/capcgi.dat"); 
#endif 
/* Important: we must indicate the type of document */ 
cgiHeaderContentType("text/html"); 
/* Now invoke other functions to handle each part of the form */ 
fprintf(cgiOut, "<HTML><HEAD>/n"); 
fprintf(cgiOut, "<TITLE>cgic test</TITLE></HEAD>/n"): 
fprintf(cgiOut, "<BODY><H1>cgic test</H1>/n"); 
Name(); 
Address(); 
Hungry(); 
Temperature(); 
Frogs(); 
Color(); 
Flavors(); 
NonExButtons(); 
RadioButtons(); 
fprintf(cgiOut, "</BODY></HTML>/n"); 
/* This value will be the exit code of the program; 0 
generally indicates success among Unix and DOS programs */ 
return 0; 


capture 
輸出標頭 
cgiHeaderContentType()在輸出文擋之前簡要說明MIME內型,如 "text/html"。 
cgiHeaderStatus()代替輸出錯誤代碼 cgiHeaderLocation()代替重新引導至其他頁面。在一個獨立的應用程序中只能有一個cgiHeader函數。 

重點:在cgiHeader函數組中, cgiHeaderContentType(), 在任何向瀏覽器輸出之前被調用. 否則將出錯或瀏覽器不能識別。 cgiOut 

接着, cgiMain() 調用不同的函數.當函數結束后,將返回0 


處理輸入文本 
void Name() { 
char name[81]; 
cgiFormStringNoNewlines("name", name, 81); 
fprintf(cgiOut, "Name: %s<BR>/n", name); 


這個函數的功能就是取的並顯示由用戶輸入的name . 
處理輸出 
Important: cgiOut通常相當於stdout 

cgiFormString 確保斷航 

處理單一Checkboxes輸入 
這個Hungry() function確定用戶是否選擇"hungry"這個 checkbox: 
void Hungry() { 
if (cgiFormCheckboxSingle("hungry") == cgiFormSuccess) { 
fprintf(cgiOut, "I'm Hungry!<BR>/n"); 
} else { 
fprintf(cgiOut, "I'm Not Hungry!<BR>/n"); 



這個函數依靠 cgiFormCheckboxSingle() 確定單一的checkbox 被選擇。 cgiFormCheckboxSingle() 接受checkbox名字的屬性值,如果存在就返回 cgiFormSuccess,否則返回cgiFormNotFound 如果是多項checkboxes,就用 cgiFormCheckboxMultiple()和cgiFormStringMultiple() 函數. 
處理數字輸入 
Temperature() 返回浮點書的值確保在特定的返回內。 
void Temperature() { 
double temperature; 
cgiFormDoubleBounded("temperature", &temperature, 80.0, 120.0, 98.6); 
fprintf(cgiOut, "My temperature is %f.<BR>/n", temperature); 


依靠cgiFormDoubleBounded()得到數據.第一個數據是返回數據中輸入域的名字。最后一個值是用戶沒有提交時的默認值。 
這個函數總是找回在特定返回內合適的值; cgiFormDoubleBounded返回的值被檢查確信用戶輸入的資料在規定范圍內, 而不是其他無效的數據。查看 cgiFormDoubleBounded() 更多的資料. 如果限度檢查不理想,可以用 cgiFormDouble() 替代. 

在整數輸入,cgiFormInteger 和 cgiFormIntegerBounded 可以利用. 這些函數的功能類似. 

處理單一選擇輸入 
<SELECT> HTML標簽被用於向用戶提供幾個選擇. Radio buttons 和checkboxes 椰油這樣的用途,大門、能夠選擇的數量很小時. Color() 
char *colors[] = { 
"Red", 
"Green", 
"Blue" 
}; 

void Color() { 
int colorChoice; 
cgiFormSelectSingle("colors", colors, 3, &colorChoice, 0); 
fprintf(cgiOut, "I am: %s<BR>/n", colors[colorChoice]); 


這個函數確定用戶選擇了幾個選項從<SELECT> 在表但的列表. cgiFormSelectSingle() 
cgiFormSelectSingle() 總是顯示合理的選項值. 

radio button也可以用這個函數.另外還有 cgiFormRadio(), 也是一樣的 


處理多項選擇的輸入 
NonExButtons() 
char *votes[] = { 
"A", 
"B", 
"C", 
"D" 
}; 

void NonExButtons() { 
int voteChoices[4]; 
int i; 
int result; 
int invalid; 

char **responses; 

/* Method #1: check for valid votes. This is a good idea, 
since votes for nonexistent candidates should probably 
be discounted... */ 
fprintf(cgiOut, "Votes (method 1):<BR>/n"); 
result = cgiFormCheckboxMultiple("vote", votes, 4, 
voteChoices, &invalid); 
if (result == cgiFormNotFound) { 
fprintf(cgiOut, "I hate them all!<p>/n"); 
} else { 
fprintf(cgiOut, "My preferred candidates are:/n"); 
fprintf(cgiOut, "<ul>/n"); 
for (i=0; (i < 4); i++) { 
if (voteChoices[i]) { 
fprintf(cgiOut, "<li>%s/n", votes[i]); 


fprintf(cgiOut, "</ul>/n"); 


參考cgiFormCheckboxMultiple(), cgiFormSelectMultiple(). 
cgiFormCheckboxMultiple() cgiFormCheckboxMultiple 

NonExButtons() 函數在 cgictest.c: 

/* Method #2: get all the names voted for and trust them. 
This is good if the form will change more often 
than the code and invented responses are not a danger 
or can be checked in some other way. */ 
fprintf(cgiOut, "Votes (method 2):<BR>/n"); 
result = cgiFormStringMultiple("vote", &responses); 
if (result == cgiFormNotFound) { 
fprintf(cgiOut, "I hate them all!<p>/n"); 
} else { 
int i = 0; 
fprintf(cgiOut, "My preferred candidates are:/n"); 
fprintf(cgiOut, "<ul>/n"); 
while (responses[i]) { 
fprintf(cgiOut, "<li>%s/n", responses[i]); 
i++; 

fprintf(cgiOut, "</ul>/n"); 

/* We must be sure to free the string array or a memory 
leak will occur. Simply calling free() would free 
the array but not the individual strings. The 
function cGIStringArrayFree() does the job completely. */ 
cgiStringArrayFree(responses); 


參考cgiFormStringMultiple() 
cgiFormStringMultiple() 

/* An array of strings; each C string is an array of characters */ 
char **responses; 

cgiFormStringMultiple("vote", &responses); 

檢查CGI環境變量 
將用到的變量 這里, 
產生圖象 

#include "cgic.h" 
#include "gd.h" 

char *colors[] = { 
"red", "green", "blue" 
}; 

#define colorsTotal 3 

int cgiMain() { 
int colorChosen; 
gdImagePtr im; 
int r, g, b; 
/* Use gd to create an image */ 
im = gdImageCreate(64, 64); 
r = gdImageColorAllocate(im, 255, 0, 0); 
g = gdImageColorAllocate(im, 0, 255, 0); 
b = gdImageColorAllocate(im, 0, 0, 255); 
/* Now use cgic to find out what color the user requested */ 
cgiFormSelectSingle("color", 3, &colorChosen, 0); 
/* Now fill with the desired color */ 
switch(colorChosen) { 
case 0: 
gdImageFill(im, 32, 32, r); 
break; 
case 1: 
gdImageFill(im, 32, 32, g); 
break; 
case 2: 
gdImageFill(im, 32, 32, b); 
break; 

/* Now output the image. Note the content type! */ 
cgiHeaderContentType("image/gif"); 
/* Send the image to cgiOut */ 
gdImageGif(im, cgiOut); 
/* Free the gd image */ 
gdImageDestroy(im); 
return 0; 


為調試而捕捉CGI狀態 
cgic函數參考 
cgiFormResultType cgiFormString( char *name, char *result, int max) 
用於從輸入域中copy字符串。他將域名max-1字節中的字符copy到緩沖區result。若域不存在,則copy一個空串到result緩沖區。在此函數中所有的新行由換行符代表。 

cgiFormResultType cgiFormStringNoNewlines( char *name, char *result, int max) 
它與cgiFormString函數相似,只是所有的CR和LF都被去掉了。 

cgiFormResultType cgiFormStringSpaceNeeded( char *name, int *length) 
它返回指向name的字符串的長度,並將長度放入length中。 

cgiFormResultType cgiFormStringMultiple( char *name, char ***ptrToStringArray) 
若同一名字有多個輸入域,或域中的字符串可以動態變化,那么你可以使用本函數。它把名為name的所有輸入域的值放在prtToStringArray中。 

void cgiStringArrayFree(char **stringArray) 
它釋放了分配給stringArray的內存。 

cgiFormResultType cgiFormInteger( char *name, int *result, int defaultV) 
從輸入域中取出整數放入result中。 

cgiFormResultType cgiFormIntegerBounded( char *name, int *result, int min, int max, int defaultV) 
若輸入域中的整數在界限內則取出並放入result中。 

cgiFormResultType cgiFormDouble( char *name, double *result, double defaultV) 
從輸入域中取出浮點數放入result中。 

cgiFormResultType cgiFormDoubleBounded( char *name, double *result, double min, double max, double defaultV) 
若輸入域中的浮點數在界限內則取出並放入result中。 

cgiFormResultType cgiFormSelectSingle( char *name, char **choicesText, int choicesTotal, int *result, int defaultV) 
取出復選框(跟在select語句之后的),把選擇的名字copy到choicesText,把選擇的個數copy到choicesTotal,把當前的選擇copy到result。 
cgiFormResultType cgiFormSelectMultiple( char *name, char **choicesText, int choicesTotal, int *result, int *invalid) 
與cgiFormSelectSingle類似,只指向整型數組的result代表了選擇的項。 

cgiFormResultType cgiFormCheckboxSingle( char *name) 
若復選框被選中,則函數返回cgiFormSuccess,否則返回cgiFormNotFound。 

cgiFormResultType cgiFormCheckboxMultiple( char *name, char **valuesText, int valuesTotal, int *result, int *invalid) 
與cgiFormCheckboxSingle類似,但它處理同一名字有多個復選框的情況。name指向復選框的名字;valuesText指向包含有每個復選框中參數的一個數組;valuesTotal指向復選框的總數;result是一個整型數組,每個復選框選中的用1代表,沒選中的用0代表。 

cgiFormResultType cgiFormRadio( char *name, char **valuesText, int valuesTotal, int *result, int defaultV) 
與cgiFormCheckboxMultiple相似,只是這里是單選按鈕而不是復選框。 

void cgiHeaderLocation(char *redirectUrl) 
重定向到redirectUrl指定的URL。 

void cgiHeaderStatus(int status, char *statusMessage) 
輸出狀態代碼status和消息statusMessage。 

void cgiHeaderContentType(char *mimeType) 
用於告知瀏覽器返回的是什么類型的文檔。 

cgiEnvironmentResultType cgiWriteEnvironment(char *filename) 
本函數把當前CGI環境寫入filename文件中以便以后調試時使用 

cgiEnvironmentResultType cgiReadEnvironment(char *filename) 
本函數從filename文件中讀取CGI環境以便用來調試。 

int cgiMain() 
一個程序必須要寫這個函數, 這是主程序開始之處。 
cgic變量參考 
This section provides a reference guide to the various global variables provided by cgic for the programmer to utilize. These variables should always be used in preference to stdin, stdout, and calls to getenv() in order to ensure compatibility with the cgic CGI debugging features. 
大多數的變量相當於各種CGI變量,重要的是VGIC的變量不能為空. 

char *cgiServerSoftware 
服務器軟件名稱,或者一個空的字符串 or to an empty string if unknown. 
char *cgiServerName 
返回服務器名稱或空 
char *cgiGatewayInterface 
網關接口 (通常是 CGI/1.1),或空 
char *cgiServERProtocol 
網絡協議(usually HTTP/1.0),或空 
char *cgiServerPort 
服務器端口(usually 80),或空 
char *cgiRequestMethod 
請求方式 (usually GET or POST),或空 
char *cgiPathInfo 
指出附加虛擬路徑 
char *cgiPathTranslated 
指出附加虛擬路徑並由服務器轉為本地路徑 
char *cgiScriptName 
調用程序的名字 
char *cgiQueryString 
包含 GET-method 請求或者 <ISINDEX> 標簽. 這個信息不需要解吸,除非用<ISINDEX>標簽通常由CGIC函數庫自動解析。 
char *cgiRemoteHost 
從瀏覽器返回客戶主機的名字 
char *cgiRemoteAddr 
從瀏覽器返回客戶的IP地址 
char *cgiAuthType 
返回用戶授權信息 
char *cgiRemoteUser 
鑒別用戶 cgiAuthType. 
char *cgiRemoteIdent 
返回用戶的名字(用戶通過用戶堅定協議)這個消息是不安全的,特別是Windows系統。 
char *cgiContentType 
返回MIME內型 
char *cgiAccept 
參考 cgiHeaderContentType() cgiUserAgent 
char *cgiUserAgent 
取的用戶瀏覽器信息 
char *cgiReferrer 
指向用戶訪問的URL. 
int cgiContentLength 
表單或查詢數據的字節被認為是標准的. 
FILE *cgiOut 
CGI輸出. cgiHeader函數,象cgiHeaderContentType, 首先被用於輸出mime頭; 用於 fprintf() 和fwrite(). cgiOut通常相當於stdout。 
FILE *cgiIn 
CGI輸入. 在決大部分時間你都不會需要這個函數。 
cgic結果編碼參考 
在大量的按列中, cgic函數有計划的產生合理的結果,甚至瀏覽器和用戶不合理時。無論如何, 有時候知道不合理的事情發生,尤其賦予一個值或定義一個范圍是一個不充分的解決方案。下面的這些結果編碼有助更好了解。 

cgiFormSuccess 
提交信息成功 
cgiFormTruncated 
刪除部分字節. 
cgiFormBadType 
錯誤的輸入信息(沒有按要求) 
cgiFormEmpty 
提交信息為空. 
cgiFormNotFound 
提交信息沒有找到. 
cgiFormConstrained 
數字屬於某個特定的范圍,被迫低於或高於適當范圍。 
cgiFormNoSuchChoice 
單一選擇提交的值是不被接受。通常說明表但和程序之間存在矛盾。 
cgiEnvironmentIO 
從CGI環境或獲取的文件讀或寫的企圖失敗,報出I/O的錯誤。 
cgiEnvironmentMemory 
從CGI環境或獲取的文件讀或寫的企圖失敗,報出out-of-memory的錯誤。 
cgiEnvironmentSuccess 
從CGI環境或獲取的文件讀或寫的企圖成功。 
cgic快速索引 
cgiAccept | cgiAuthType | cgiContentLength | cgiContentType | cgiEnvironmentIO | cgiEnvironmentMemory | cgiEnvironmentSuccess | cgiFormBadType | cgiFormCheckboxMultiple() | cgiFormCheckboxSingle() | cgiFormConstrained | cgiFormDouble() | cgiFormDoubleBounded() | cgiFormEmpty | cgiFormInteger() | cgiFormIntegerBounded() | cgiFormNoSuchChoice | cgiFormNotFound | cgiFormRadio() | cgiFormSelectMultiple() | cgiFormSelectSingle() | cgiFormString() | cgiFormStringMultiple() | cgiFormStringNoNewlines() | cgiFormStringSpaceNeeded() | cgiFormSuccess | cgiFormTruncated | cgiGatewayInterface | cgiHeaderContentType() | cgiHeaderLocation() | cgiHeaderStatus() | cgiIn | cgiMain() cgiOut | cgiPathInfo | cgiPathTranslated | cgiQueryString | cgiReadEnvironment() | cgiReferrer() | cgiRemoteAddr | cgiRemoteHost | cgiRemoteIdent | cgiRemoteUser | cgiRequestMethod | cgiScriptName | cgiServerName | cgiServerPort | cgiServerProtocol | cgiServerSoftware | cgiStringArrayFree() | cgiUserAgent | cgiWriteEnvironment() 

=========================================================================

CGIC簡明教程

本系列的目的是演示如何使用C語言的CGI庫“CGIC”完成Web開發的各種要求。

      基礎知識
    * 1: 使用CGIC的基本思路
    * 2: 獲取Get請求字符串
    * 3: 反轉義
    * 4: 獲取請求中的參數值
     進階訓練
    * 用CGIC實現文件上傳


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文件,其內容為:

   1. test.cgi:cgictest.c cgic.h cgic.c
   2. 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”。


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="Continue &rarr;">
   9.     </form>
  10. </body>
  11. </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)在這個函數聲明語句的上方,你會看到一個結構體定義:

   1. typedef enum {
   2.     cgiUnescapeSuccess,
   3.     cgiUnescapeMemory
   4. } cgiUnescapeResultType;

把這幾行語句復制到cgic.h文件中,並在這里把它注釋掉;
同時還要刪除在第一步中找到的函數聲明語句中的“static”關鍵字。

3)我們現在就可以使用反轉義函數cgiUnescapeChars了:
在你自己的代碼(按照慣例,還是test.c)中,加入以下聲明語句即可
extern cgiUnescapeResultType cgiUnescapeChars(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 cgiUnescapeResultType cgiUnescapeChars(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.     cgiUnescapeChars(&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的存儲空間是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,都要有效。

下面給出示例代碼:
下載: 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用於測試輸入值的長度,可以以此為依據,然后按需精確分配緩沖區。


用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&lt;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 /* CGICDEBUG */
   7. //我的代碼
   8. //UpSize:文件長度上限值,以byte為單位,UpSize是一個int變量,因為cgiContentLength的類型為int
   9.     if(cgiContentLength&gt;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 /* CGICDEBUG */
  22.         cgiFreeResources();
  23.         return -1;
  24.     }
  25. #ifdef CGICDEBUG
  26. CGICDEBUGSTART
  27.     fprintf(dout, "PostMultipartInput succeeded/n");
  28. CGICDEBUGEND
  29.     #endif /* CGICDEBUG */
  30. }
  31. }

變量UpSize表示文件大小的上限。在cgic.c的main中找到相關代碼,並修改成上面這樣即可。你可以在cgic.c中定義UpSize,也可以在剛才完成的upload.c中定義,然后在cgic.c中用extern方式引用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM