CGI:通用網關接口(Common Gateway Interface)是一個Web服務器主機提供信息服務的標準接口。經過CGI接口,Web服務器就可以獲取客戶端提交的信息,轉交給服務器端的CGI程序進行處理,最後返回結果給客戶端。javascript
組成CGI通訊系統的是兩部分:一部分是html頁面,就是在用戶端瀏覽器上顯示的頁面。另外一部分則是運行在服務器上的Cgi程序。html
它們之間的通信方式以下圖:java
服務器python |
客戶端linux |
CGI程序程序員 |
HTTP通訊數據庫 |
標準輸入輸出編程 (環境變量)瀏覽器 |
服務器和客戶端之間的通訊,是客戶端的瀏覽器和服務器端的http服務器之間的HTTP通訊,咱們只須要知道瀏覽器請求執行服務器上哪一個CGI程序就能夠了,其餘沒必要深究細節,由於這些過程不須要程序員去操做。緩存
服務器和CGI程序之間的通信纔是咱們關注的。通常狀況下,服務器和CGI程序之間是經過標準輸入輸出來進行數據傳遞的,而這個過程須要環境變量的協做方可實現。
1. 服務器將URL指向一個應用程序
2. 服務器爲應用程序執行作準備
3. 應用程序執行,讀取標準輸入和有關環境變量
4. 應用程序進行標準輸出
對於Windows系統而言,還能夠經過profile文件進行數據傳輸(如ini文件),但在
這裏不作研究。
環境變量在CGI中有着重要的地位!每一個CGI程序只能處理一個用戶請求,因此在激
活一個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程序時,最後還要考慮其餘的狀況。
若是採用POST方法,那麼客戶端來的用戶數據將存放在CGI進程的標準輸入中,同時將用戶數據的長度賦予環境變量中的CONTENT_LENGTH。客戶端用POST方式發送數據有一個相應的MIME類型(通用Internet郵件擴充服務:Multi-purpose Internet Mail Extensions)。目前,MIME類型通常是:application/x-wwww-form-urlencoded,該類型表示數據來自HTML表單。該類型記錄在環境變量CONTENT_TYPE中,CGI程序應該檢查該變量的值。
在該方法下,CGI程序沒法直接從服務器的標準輸入中獲取數據,由於服務器把它從標
準輸入接收到得數據編碼到環境變量QUERY_STRING(或PATH_INFO)。
GET與POST的區別:採用GET方法提交HTML表單數據的時候,客戶機將把這些數
據附加到由ACTION標記命名的URL的末尾,用一個包括把通過URL編碼後的信息與CGI程序的名字分開:http://www.mycorp.com/hello.html?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裏。
以 GET方式接收的數據是有長度限制,而用 POST方式接收的數據是沒有長度限制的。而且,以 GET方式發送數據,能夠經過 URL的形式來發送,但 POST方式發送的數據必需要經過 Form纔到發送。
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!
無論是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、mike@hotmail.com和I’ll be there for you,則通過URL編碼後應爲:
filename=hello&e-mail=hello@hotmail.com&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); |
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程序時公用的,而WEB服務器都支持多進程運行,所以可能會發生同時有多個用戶訪問同一個CGI程序的狀況。好比,有2個用戶幾乎同時訪問同一個CGI程序,服務器爲他們建立了2個CGI程序進程,設爲進程A和進程B。假如進程A首先打開了某個文件,而後因爲某種緣由被掛起(通常是因爲操做系統的進程調度);而就在進程A被掛起的這段時間內,進程B完成了對文件的整個操做流程:打開,寫入,關閉;進程A再繼續往下執行,但進程A所操做的文件依舊是原來文件的就版本,此時進程A的操做結果將覆蓋進程B的操做結果。
爲了防止這種狀況發生,須要用到文件鎖或者信號量。
鑰匙文件?
假若有多個不一樣的HTML能夠調用同一個CGI程序,那麼CGI程序如何區分它們呢?一個是經過隱含的INPUT標籤。不過以爲這個比較麻煩,由於CGI必須通過一系列解碼後才能找到這個隱含INPUT的變量和其值。
用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
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 current date is<! --#echo var = 「DATE.LOCAL」-- >,服務器將向瀏覽器輸出時間。
#include <stdio.h>
#include <stdlib.h>
int main(void){
int len;
char *lenstr,poststr[20];
char m[10],n[10];
printf("Content-Type:text/html\n\n");
printf("<HTML>\n");
printf("<HEAD>\n<TITLE >post Method</TITLE>\n</HEAD>\n");
printf("<BODY>/n");
printf("<div style= \"font-size:12px\">\n");
lenstr=getenv("CONTENT_LENGTH");
if(lenstr == NULL)
printf("<DIV STYLE=\"COLOR:RED\">Error parameters should be entered!</DIV>\n");
else{
len=atoi(lenstr);
fgets(poststr,len+1,stdin);
if(sscanf(poststr,"m=%[^&]&n=%s",m,n)!=2){
printf("<DIV STYLE=\"COLOR:RED\">Error: Parameters are not right!</DIV>\n");
}
else{
printf("<DIV STYLE=\"COLOR:GREEN; font-size:15px;font-weight:bold\">m * n = %d</DIV>\n",atoi(m)*atoi(n));
}
}
printf("<HR COLOR=\"blue\" align=\"left\" width=\"100\">");
printf("<input type=\"button\" value=\"Back CGI\" onclick=\"javascript:window.location='../cgi.html'\">");
printf("\n");
printf("</BODY>\n");
printf("</HTML>\n");
fflush(stdout);
return 0;
}
(1) printf("Content-Type:text/html/n/n");
此行經過標準輸出將字符串″Contenttype:text/plain/n/n″傳送給Web服務器。它是一個MIME頭信息,它告訴Web服務器隨 後的輸出是以純ASCII文本的形式。請注意在這個頭信息中有兩個換行符,這是由於Web服務器須要在實際的文本信息開始以前先看見一個空行。
(2) data = getenv("QUERY_STRING");
CGI定義:當GET方法提交的表單被髮送到服務器斷後,表單中的數據被保存在服務器上一個叫作QUERY_STRING的環境變量中。這種表單的處理相對簡單,只要讀取環境變量就能夠了。
(3) sscanf(data,"a=%[^&]&b=%s",a,b)!=2
這個是關於sscanf函數的使用問題,本身能夠上網搜索一下,這裏再也不詳述!
(4)atoi(a)+atoi(b)
atoi函數的功能是將字符型成整型,只有轉換以後才能夠進行加法運算!
(5) lenstr=getenv("CONTENT_LENGTH");
Web服務器在調用使用POST方法的CGI程序時設置此環境變量,它的文本值表示Web服務器傳送給CGI程序的輸入中的字符數目,所以須要使用函數atoi() 將此環境變量的值轉換成整數,並賦給變量len(下面有定義)。
(6) fgets(poststr,len+1,stdin);
這個是關於fgets函數的使用問題,本身能夠上網搜索一下,這裏再也不詳述!
其餘cgi相關的博客:
c語言寫CGI程序:http://blog.csdn.net/wxhlinux/article/details/8541902