1. 程式人生 > >CGI詳解(原理,配置及訪問)

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環境變數在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:它的值一般包括兩種:POSTGET,但我們寫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程式應該檢查該變數的值。

2GET方法

在該方法下,CGI程式無法直接從伺服器的標準輸入中獲取資料,因為伺服器把它從標

準輸入接收到得資料編碼到環境變數QUERY_STRING(或PATH_INFO)。

GETPOST的區別:採用GET方法提交HTML表單資料的時候,客戶機將把這些數

據附加到由ACTION標記命名的URL的末尾,用一個包括把經過URL編碼後的資訊與CGI程式的名字分開:name=hgq$id=1QUERY_STRING的值為name=hgq&id=1

有些程式設計師不願意採用GET方法,因為在他們看來,把動態資訊附加在URL的末尾有

URL的出發點:URL作為一種標準用語,一般是用作網路資源的唯一定位標示。

環境變數是一個儲存使用者資訊的記憶體區。當客戶端的使用者通過瀏覽器發出CGI請求時,伺服器就尋找本地的相應CGI程式並執行它。在執行CGI程式的同時,伺服器把該使用者的資訊儲存到環境變數裡。接下來,CGI程式的執行流程是這樣的:查詢與該CGI程式程序相應的環境變數:第一步是request_method,如果是POST,就從環境變數的len,然後到該程序相應的標準輸入取出len長的資料。如果是GET,則使用者資料就在環境變數的QUERY_STRING裡。

3POSTGET的區別

       以 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_METHODREMOTE_ADDR等)新增進來,也就是說“getenv(“REQUEST_METHOD”)”將返回NULL

2URL編碼

不管是POST還是GET方式,客戶端瀏覽器傳送給伺服器的資料都不是原始的使用者資料,而是經過URL編碼的。此時,CGI的環境變數Content_type將被設定,如Content_type = application/x-www-form-urlencode就表示伺服器收到的是經過URL編碼的包含有HTML表單變數資料。

編碼的基本規則是:

變數之間用“&”分開;

變數與其對應值用“=”連線;

空格用“+”代替;

保留的控制字元則用“%”連線對應的16禁止ASCII碼代替;

某些具有特殊意義的字元也用“%”接對應的16進位制ASCII碼代替;

空格是非法字元;

任意不可列印的ASCII控制字元均為非法字元。

例如,假設3HTML表單變數filenamee-mailcomments,它們的值對應分別為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);

3CGI資料輸出

CGI程式如何將資訊處理結果返回給客戶端?這實際上是CGI格式化輸出。

在CGI程式中的標準輸出stdout是經過重定義了的,它並沒有在伺服器上產生任何的輸出內容,而是被重定向到客戶瀏覽器,這與它是由C,還是Perl或Python實現無關。

所以,我們可以用列印來實現客戶端新的HTML頁面的生成。比如,Cprintf是向該程序的標準輸出傳送資料,PerlPythonprint向該程序的標準輸出傳送資料。

(1)   CGI標題

CGI的格式輸出內容必須組織成標題/內容的形式。CGI標準規定了CGI程式可以使用

的三個HTTP標題。標題必須佔據第一行輸出!而且必須隨後帶有一個空行。

標題

描述

Content_type   (內容型別)

設定隨後輸出資料所用的MIME型別

Location    (地址)

設定輸出為另外一個文件(URL

Status      (狀態)

指定HTTP狀態碼

MIME

向標準輸出傳送網頁內容時要遵守MIME格式規則:

任意輸出前面必須有一個用於定義MIME型別的輸出內容(Content-type)行,而且隨後還必須跟一個空行。如果遺漏了這一條,服務將會返回一個錯誤資訊。(同樣使用於其他標題)

例如PerlPython

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程式,伺服器為他們建立了2CGI程式程序,設為程序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”-- >,伺服器將向瀏覽器輸出時間。