CGI詳解(原理,配置及訪問)
一.基本原理
CGI:通用閘道器介面(Common Gateway Interface)是一個Web伺服器主機提供資訊服務的標準介面。通過CGI介面,Web伺服器就能夠獲取客戶端提交的資訊,轉交給伺服器端的CGI程式進行處理,最後返回結果給客戶端。
組成CGI通訊系統的是兩部分:一部分是html頁面,就是在使用者端瀏覽器上顯示的頁面。另一部分則是執行在伺服器上的Cgi程式。
它們之間的通訊方式如下圖:
伺服器和客戶端之間的通訊,是客戶端的瀏覽器和伺服器端的http伺服器之間的HTTP通訊,我們只需要知道瀏覽器請求執行伺服器上哪個CGI程式就可以了,其他不必深究細節,因為這些過程不需要程式設計師去操作。
伺服器和CGI程式之間的通訊才是我們關注的。一般情況下,伺服器和CGI程式之間是通過標準輸入輸出來進行資料傳遞的,而這個過程需要環境變數的協作方可實現。
1. 伺服器將URL指向一個應用程式
2. 伺服器為應用程式執行做準備
3. 應用程式執行,讀取標準輸入和有關環境變數
4. 應用程式進行標準輸出
對於Windows系統而言,還可以通過profile檔案進行資料傳輸(如ini檔案),但在
這裡不做研究。
環境變數在CGI中有著重要的地位!每個CGI程式只能處理一個使用者請求,所以在激
活一個CGI程式程序時也建立了屬於該程序的環境變數。
二.環境變數
對於CGI程式來說,它繼承了系統的環境變數。
當一個CGI程式不是被HTTP伺服器呼叫時,它的環境變數幾乎是系統環境變數的複製。
當這個CGI程式被HTTP伺服器呼叫時,它的環境變數就會多了以下關於HTTP伺服器、客戶端、CGI傳輸過程等專案。
與請求相關的環境變數 |
REQUEST_METHOD |
伺服器與CGI程式之間的資訊傳輸方式 |
QUERY_STRING |
採用GET時所傳輸的資訊 |
|
CONTENT_LENGTH |
STDIO中的有效資訊長度 |
|
CONTENT_TYPE |
指示所傳來的資訊的MIME型別 |
|
CONTENT_FILE |
使用Windows HTTPd/WinCGI |
|
PATH_INFO |
路徑資訊 |
|
PATH_TRANSLATED |
CGI程式的完整路徑名 |
|
SCRIPT_NAME |
所呼叫的CGI程式的名字 |
|
與伺服器相關的環境變數 |
GATEWAY_INTERFACE |
伺服器所實現的CGI版本 |
SERVER_NAME |
伺服器的IP或名字 |
|
SERVER_PORT |
主機的埠號 |
|
SERVER_SOFTWARE |
呼叫CGI程式的HTTP伺服器的名稱和版本號 |
|
與客戶端相關的環境變數 |
REMOTE_ADDR |
客戶機的主機名 |
REMOTE_HOST |
客戶機的IP地址 |
|
ACCEPT |
例出能被次請求接受的應答方式 |
|
ACCEPT_ENCODING |
列出客戶機支援的編碼方式 |
|
ACCEPT_LANGUAGE |
表明客戶機可接受語言的ISO程式碼 |
|
AUTORIZATION |
表明被證實了的使用者 |
|
FORM |
列出客戶機的EMAIL地址 |
|
IF_MODIFIED_SINGCE |
當用get方式請求並且只有當文件比指定日期更早時才返回資料 |
|
PRAGMA |
設定將來要用到的伺服器代理 |
|
REFFERER |
指出連線到當前文件的文件的URL |
|
USER_AGENT |
客戶端瀏覽器的資訊 |
CONTENT_TYPE:如application/x-www-form-urlencoded,表示資料來自HTML表單,並且經過了URL編碼。
ACCEPT:客戶機所支援的MIME型別清單,內容如:”image/gif,image/jpeg”
REQUEST_METHOD:它的值一般包括兩種:POST和GET,但我們寫CGI程式時,最後還要考慮其他的情況。
1.POST方法
如果採用POST方法,那麼客戶端來的使用者資料將存放在CGI程序的標準輸入中,同時將使用者資料的長度賦予環境變數中的CONTENT_LENGTH。客戶端用POST方式傳送資料有一個相應的MIME型別(通用Internet郵件擴充服務:Multi-purpose Internet Mail Extensions)。目前,MIME型別一般是:application/x-wwww-form-urlencoded,該型別表示資料來自HTML表單。該型別記錄在環境變數CONTENT_TYPE中,CGI程式應該檢查該變數的值。
2.GET方法
在該方法下,CGI程式無法直接從伺服器的標準輸入中獲取資料,因為伺服器把它從標
準輸入接收到得資料編碼到環境變數QUERY_STRING(或PATH_INFO)。
GET與POST的區別:採用GET方法提交HTML表單資料的時候,客戶機將把這些數
據附加到由ACTION標記命名的URL的末尾,用一個包括把經過URL編碼後的資訊與CGI程式的名字分開:?name=hgq$id=1,QUERY_STRING的值為name=hgq&id=1
有些程式設計師不願意採用GET方法,因為在他們看來,把動態資訊附加在URL的末尾有
違URL的出發點:URL作為一種標準用語,一般是用作網路資源的唯一定位標示。
環境變數是一個儲存使用者資訊的記憶體區。當客戶端的使用者通過瀏覽器發出CGI請求時,伺服器就尋找本地的相應CGI程式並執行它。在執行CGI程式的同時,伺服器把該使用者的資訊儲存到環境變數裡。接下來,CGI程式的執行流程是這樣的:查詢與該CGI程式程序相應的環境變數:第一步是request_method,如果是POST,就從環境變數的len,然後到該程序相應的標準輸入取出len長的資料。如果是GET,則使用者資料就在環境變數的QUERY_STRING裡。
3.POST與GET的區別
以 GET 方式接收的資料是有長度限制,而用 POST 方式接收的資料是沒有長度限制的。並且,以 GET 方式傳送資料,可以通過URL 的形式來發送,但 POST方式傳送的資料必須要通過 Form 才到傳送。
三.CGI程式實現步驟
1.從伺服器獲取資料
C語言實現程式碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> int get_inputs() { int length; char *method; char *inputstring; method = getenv(“REQUEST_METHOD”); //將返回結果賦予指標 if(method == NULL) return 1; //找不到環境變數REQUEST_METHOD if(!strcmp(method, ”POST”)) // POST方法 { length = atoi(getenv(“CONTENT_LENGTH”)); //結果是字元,需要轉換 if(length != 0) { inputstring = malloc(sizeof(char)*length + 1) //必須申請快取,因為stdin是不帶快取的。 fread(inputstring, sizeof(char), length, stdin); //從標準輸入讀取一定資料 } } else if(!strcmp(method, “GET”)) { Inputstring = getenv(“QUERY_STRING”); length = strlen(inputstring); } if(length == 0) return 0; } |
Perl實現程式碼:
$method = $ENV{‘REQUEST_METHOD’}; if($method eq ‘POST’) { Read(STDIN, $input, $ENV{‘CONTENT_LENGTH’}); } if($method eq ‘GET’ || $method eq ‘HEAD’) { $input = $ENV{‘QUERY_STRING’}; } if($input eq “”) { &print_form; exit; } |
PYTHON程式碼實現
#!/usr/local/bin/python import cgi def main(): form = cgi.FieldStorage() Python程式碼實現更簡單,cgi.FieldStorage()返回一個字典,字典的每一個key就是變數名,key對應的值就是變數名的值,更本無需使用者再去進行資料解碼! |
獲取環境變數的時候,如果先判斷“REQUEST_METHOD”是否存在,程式會更健壯,否則在某些情況下可能會造成程式崩潰。因為假若CGI程式不是由伺服器呼叫的,那麼環境變數集裡就沒有與CGI相關的環境變數(如REQUEST_METHOD,REMOTE_ADDR等)新增進來,也就是說“getenv(“REQUEST_METHOD”)”將返回NULL!
2.URL編碼
不管是POST還是GET方式,客戶端瀏覽器傳送給伺服器的資料都不是原始的使用者資料,而是經過URL編碼的。此時,CGI的環境變數Content_type將被設定,如Content_type = application/x-www-form-urlencode就表示伺服器收到的是經過URL編碼的包含有HTML表單變數資料。
編碼的基本規則是:
變數之間用“&”分開;
變數與其對應值用“=”連線;
空格用“+”代替;
保留的控制字元則用“%”連線對應的16禁止ASCII碼代替;
某些具有特殊意義的字元也用“%”接對應的16進位制ASCII碼代替;
空格是非法字元;
任意不可列印的ASCII控制字元均為非法字元。
例如,假設3個HTML表單變數filename、e-mail和comments,它們的值對應分別為hello、[email protected]和I’ll bethere for you,則經過URL編碼後應為:
filename=hello&[email protected]&comments=I%27ll+be+there+for+you |
所以,CGI程式從標準輸入或環境變數中獲取客戶端資料後,還需要進行解碼。解碼的過程就是URL編碼的逆變:根據“&”和“=”分離HTML表單變數,以及特殊字元的替換。
在解碼方面,PYTHON程式碼實現是最理想的,cgi.FieldStorage()函式在獲取資料的同時就已自動進行程式碼轉換了,無需程式設計師再進行額外的程式碼編寫。Perl其次,因為在一個現成的Perl庫:cgi-lib.pl中提供了ReadParse函式,用它來進行URL解碼很簡單:
require ‘cgi-lib.pl’; &ReadParse(*input); |
3.CGI資料輸出
CGI程式如何將資訊處理結果返回給客戶端?這實際上是CGI格式化輸出。
在CGI程式中的標準輸出stdout是經過重定義了的,它並沒有在伺服器上產生任何的輸出內容,而是被重定向到客戶瀏覽器,這與它是由C,還是Perl或Python實現無關。
所以,我們可以用列印來實現客戶端新的HTML頁面的生成。比如,C的printf是向該程序的標準輸出傳送資料,Perl和Python用print向該程序的標準輸出傳送資料。
(1) CGI標題
CGI的格式輸出內容必須組織成標題/內容的形式。CGI標準規定了CGI程式可以使用
的三個HTTP標題。標題必須佔據第一行輸出!而且必須隨後帶有一個空行。
標題 |
描述 |
Content_type (內容型別) |
設定隨後輸出資料所用的MIME型別 |
Location (地址) |
設定輸出為另外一個文件(URL) |
Status (狀態) |
指定HTTP狀態碼 |
MIME:
向標準輸出傳送網頁內容時要遵守MIME格式規則:
任意輸出前面必須有一個用於定義MIME型別的輸出內容(Content-type)行,而且隨後還必須跟一個空行。如果遺漏了這一條,服務將會返回一個錯誤資訊。(同樣使用於其他標題)
例如Perl和Python:
print “Content-type:text/html\n\n”; //輸出HTML格式的資料 print “<body>welcome<br>” print “</body>” |
C語言:
printf( “Content-type:text/html\n\n”); printf(“Welcome\n”); |
MIME型別以型別/子型別(type/subtype)的形式表示。
其中type表示一下幾種典型檔案格式的一種:
Text、Audio、Video、Image、Application、Mutipart、Message
Subtype則用來描述具體所用的資料格式。
Application/msword |
微軟的Word檔案 |
Application/octet-stream |
一種通用的二進位制檔案格式 |
Application/zip |
Zip壓縮檔案 |
Application/pdf |
Pdf檔案 |
。。。。。。。。。。。。。。。。。。。。。。。。。。 |
。。。。。。。。。。。。。。。。。。。。。。。。。 |
Location:
使用Location標題,一個CGI可以使當前使用者轉而訪問同一伺服器上的另外一個程式,甚至可以訪問另外一個URL,但伺服器對他們的處理方式不一樣。
使用Location的格式為:Location:Filename/URL,例如:
print “Location:/test.html\n\n”; 這與直接連結到test.html的效果是一樣的。 |
print “Location:http://www.chinaunix.com/\n\n” 由於該URL並不指向當前伺服器,使用者瀏覽器並不會直接連結到指定的URL,而是給使用者輸出提示資訊。 |
HTTP狀態碼:
表示了請求的結果狀態,是CGI程式通過伺服器用來通知使用者其請求是否成功執行的資訊碼,本文不做研究。
四.CGI中的訊號量和檔案鎖
因為CGI程式時公用的,而WEB伺服器都支援多程序執行,因此可能會發生同時有多個使用者訪問同一個CGI程式的情況。比如,有2個使用者幾乎同時訪問同一個CGI程式,伺服器為他們建立了2個CGI程式程序,設為程序A和程序B。假如程序A首先打開了某個檔案,然後由於某種原因被掛起(一般是由於作業系統的程序排程);而就在程序A被掛起的這段時間內,程序B完成了對檔案的整個操作流程:開啟,寫入,關閉;程序A再繼續往下執行,但程序A所操作的檔案依舊是原來檔案的就版本,此時程序A的操作結果將覆蓋程序B的操作結果。
為了防止這種情況發生,需要用到檔案鎖或者訊號量。
鑰匙檔案?
假如有多個不同的HTML可以呼叫同一個CGI程式,那麼CGI程式如何區分它們呢?一個是通過隱含的INPUT標籤。不過覺得這個比較麻煩,因為CGI必須經過一系列解碼後才能找到這個隱含INPUT的變數和其值。
五.設定HTTP伺服器以相容CGI
用Perl編寫的CGI程式字尾為:.pl;Python編寫的CGI程式字尾為:.py;而C編寫的CGI程式字尾為:.cgi,如果在win下編譯出來的是.exe,最好將它重新命名為.cgi。這些都是為了HTTP服務能夠識別並呼叫它們。
當使用appche httpd伺服器時,請編輯它的配置檔案httpd.conf如下:
修改AddHandler cgi-script一句為AddHandler cgi-script .cgi .py.pl
六.關於CGI的C語言庫——cgihtml
Cgihtml是一個應用非常廣泛的C語言編寫的CGI庫。它提供的功能函式如下:
Read_cgi_input():獲取並解析HTML表單輸入,返回一個指向某結構體的指標
Cgi_val():獲取每個表單變數的值
Html_header():輸出HTML標題欄
Html_begin():輸出HTML文件的開始部分
H1():輸出一行字元,字型為H1
Html_end():輸出HTML文件的結尾部分。
#include “cgi-lib.h”
#include “html-lib.h”
#include “string-lib.h”
六.後話
有的人認為可以用JavaScript來代替CGI程式,這其實是一個概念上的錯誤。JavaScript只能夠在客戶瀏覽器中執行,而CGI卻是工作在伺服器上的。他們所做的工作有一些交集,比如表單資料驗證一類的,但是JavaScript是絕對無法取代CGI的。但可以這樣說,如果一項工作即能夠用JavaScript來做,又可以用CGI來做,那麼絕對要使用JavaScript,在執行的速度上,JavaScript比CGI有著先天的優勢。只有那些在客戶端解決不了的問題,比如和某個遠端資料庫互動,這時就應該使用CGI了。
SSI:一種用來動態輸出HTML文字的特殊程式。
網頁裡包含有某個變數,提交給伺服器後,只有該變數改變。此時我們希望伺服器不要把整個頁面內容都發送過來,而只需要告訴客戶端的瀏覽器,哪個變數的值便成什麼樣了,瀏覽器會自動更新。
SSI在伺服器端執行。
SSI不需要外部介面,它不像CGI從標準輸入接收資訊。
你瀏覽你的HTML文件時看不到SSI標記,因為它已經被相應的程式輸出所替代。
所有的SSI命令都是嵌入在普通的HTML註釋行中的。當伺服器無法解釋SSI時,它將不解釋並直接把文件傳給瀏覽器,由於命令在註釋中,故瀏覽器將忽略它們。而當伺服器識別SSI時,它並不將該命令傳給瀏覽器,相反,伺服器將從上到下掃描HTML文件,執行每一個嵌入註釋的命令,並將命令的執行結果代替原註釋。
<! –註釋文字-->。伺服器將根本不檢視註釋,除非已啟動SSI。
與純註釋不同的是,所有的SSI命令都是以#打頭。
<!--#command tagname = “parameter”-- >,command指出伺服器做什麼,tagname指出引數型別,parameter是該命令的使用者定義值。
The currentdate is<! --#echo var = “DATE.LOCAL”-- >,伺服器將向瀏覽器輸出時間。