利用通用網關接口(CGI) Web服務器可以執行一些外部程序 並將這些外部程序所產生的輸出結果和Web服務器所管理的靜態文本 圖像和聲音融合在一起傳給相應的Web浏覽器。當客戶機的浏覽器向Web服務器請求一個HTML文件時 服務器在收到請求後就去尋找這個文件並將找到的文件傳送給客戶機。而當客戶機的請求是一個CGI程序時 Web服務器將激活客戶機所請求的CGI程序並把程序的執行結果傳給客戶機。
標准的CGI程序是通過環境變量和標准輸入輸出來與Web服務器交換信息的。任何一個被系統激活的進程都擁有標准輸入和輸出這兩個文件句柄 CGI程序的進程也不例外。不過 當CGI程序被Web服務器激活以後 它的標准輸入STDIN被連接到Web服務器的標准輸出STDOUT上 而CGI程序的標准輸出STDOUT則被連到服務器的標准輸入STDIN上。因此 CGI程序從標准輸入讀取信息(也就是從Web服務器的標准輸出讀信息) 而它向標准輸出寫信息(也就是向Web服務器的標准輸入寫信息)。
Web服務器一般將客戶機傳送來的信息放在它的標准輸出和相關環境變量中 而CGI程序則從環境變量和它的標准輸入(也就是Web服務器的標准輸出)獲取所需的信息 程序的最終輸出結果則被寫向它的標准輸出STDOUT(也就是Web服務器的標准輸入)。Web服務器將從它的標准輸入STDIN(也就是CGI程序的標准輸出)獲取CGI程序的輸出結果並將它傳送給客戶機。客戶機 Web服務器和CGI程序之間的信息交流如下圖所示。顯然 Web服務器就像是客戶機和CGI程序間的中介。
Web服務器 CGI程序間的這種標准框架在Unix系統下和微軟Windows環境中的字符方式下可以工作得非常好 因為此時系統產生的所有進程都可以存取標准輸入和標准輸出。但對於微軟Windows圖形方式下的程序就不行了 因為它們無法存取標准輸入和標准輸出。為了解決這一問題 微軟在Win32系統中創建了另一類型的標准輸入和標准輸出 程序可以通過調用Win32API函數來存取標准輸入和標准輸出 不過 這就意味著使用這類標准輸入和標准輸出的CGI程序都必須是32位的。
微軟Windows環境下的其它一些Web服務器(例如Website)則使用另外一種特殊的技術(即利用INI文件)來實現Web服務器和CGI程序間的數據交流。采用這種被稱為“Win-CGI”規范編寫的CGI程序通常只能在部份Web服務器上運行。一般地 支持Win-CGI的Web服務器將客戶端的輸入以及有關的狀態信息寫入到一個INI文件中 而CGI程序則從該INI文件中獲取相關信息 這類程序的執行效率沒有標准CGI程序高。
在進行CGI編程時 只要使CGI程序從標准輸入和環境變量中獲取客戶機提供的信息 並將要傳送給客戶機的輸出結果寫入標准輸出 剩下的信息傳遞工作將由Web服務器自動完成。CGI只是規定了一個標准的接口規范 只要遵守這個標准規范 程序開發人員就可以利用各種編程工具(如Perl C FORTRAN VisualBasic等)進行CGI編程了。考慮到VisualBasic的強大的數據庫處理能力 客戶機/服務器模式的編程能力以及字符串處理能力 所以本文主要向大家介紹如何使用VB編寫標准的CGI程序。
一 輸入輸出的處理
一個CGI程序被激活以後 它首先要做的事情就是確定系統平台 Web服務器和客戶端浏覽器的狀態信息以及客戶端用戶的輸入數據。此外 它還必須能夠將相關信息傳送給客戶端 否則它將一事無成。這些操作都是通過存取環境變量和標准輸入輸出來完成的。用VB編寫的CGI程序通過調用函數Environ()來獲取相關環境變量的值。存取標准輸入輸出就要在程序中使用Win32API函數GetStdHandle() ReadFile()和WriteFile() 在使用這些函數時首先必須在程序中聲明它們 寫聲明語句時可以借助於VB提供的API文本查看器。
以下的CGI程序說明 在VB-CGI程序中如何處理環境變量和標准輸入輸出。該CGI程序非常簡單 可將標准輸入中的信息不經任何處理就返回給客戶端 它可被任何表單用POST方法激活
DeclareFunctionGetStdHandleLib"kernel32"(ByValnStdHandleAsLong)AsLong
DeclareFunctionReadFileLib"kernel32"(ByValhFileAsLong,lpBufferAsAny,
ByValnNumberOfBytesToReadAsLong,lpNumberOfBytesReadAsLong,lpOverlappedAsAny)AsLong
DeclareFunctionWriteFileLib"kernel32"(ByValhFileAsLong,ByVallpBufferAsString,ByValnNumberOfBytesToWriteAsLong,lpNumberOfBytesWrittenAsLong,lpOverlappedAsAny)AsLong
PublicConstSTD_INPUT_HANDLE=-10&
PublicConstSTD_OUTPUT_HANDLE=-11&
PublicConstFILE_BEGIN=0&
PublichStdInAsLong'標准輸入文件句柄
PublichStdOutAsLong'標准輸出文件句柄
SubMain()
DimCGI_ContentLengthAsString CGI_QueryStringAsString
DimlContentLengthAsLong'標准輸入中的字符串的長度
DimsBuffAsString'用於存儲標准輸入中的字符串
DimlBytesReadAsLong'實際讀入的字符個數
DimrcAsLong
DimsFormDataAsString
'調用系統函數生成標准輸入輸出文件句柄
hStdIn=GetStdHandle(STD_INPUT_HANDLE)
hStdOut=GetStdHandle(STD_OUTPUT_HANDLE)
'獲取環境變量CONTENT_LENGTH的值 並將它轉換為整型
CGI_ContentLength=Environ("CONTENT_LENGTH")
lContentLength=Val(CGI_ContentLength)
sBuff=String(lContentLength,Chr$(0))
'從標准輸入中讀數據
rc=ReadFile(hStdIn,ByValsBuff,lContentLength,lBytesRead,ByVal0&)
sFormData=Left$(sBuff,lBytesRead)
OutPut"Content-type:text/html"&vbCrLf
OutPut"<HTML><HEAD>"
OutPut"<TITLE>表單傳送數據的方法POST</TITLE></HEAD>"
OutPut"<BODY><H3>表單傳送數據的方法POST</H3>"
OutPut"<P>本CGI程序使用VisualBasic編制 "
OutPut"<P>POST方法傳送的數據:"
OutPut"<P>"&sBuff
OutPut"</BODY></HTML>"
EndSub
SubOutPut(sAsString)'定義一個向標准輸出寫信息的函數
DimlBytesWrittenAsLong
s=s&vbCrLf
WriteFilehStdOut,s,Len(s),lBytesWritten,ByVal0&
EndSub
一般地 用VB編譯生成的CGI程序不能正確處理中文信息。這主要表現在CGI程序向STDOUT輸出的中文在Web頁面上無法正確顯示 可通過在該中文字符串後跟著輸出一些空格來解決這個問題。當使用HTML標識符<P> </P>對Web頁面進行排版時 浏覽器在顯示該Web頁面時會吃掉多馀的空格而只保留一個。在這種情況下 這些空格對Web頁面的外觀基本上沒有什堋影響。如果使用HTML標識符<PRE> </PRE>對Web頁面進行排版 則由於空格不能被浏覽器吃掉 所以Web頁面的外觀將會受到較大的影響。不過 這時可用HTML的表格<table> <P>來代替<PRE>對Web頁面進行排版。
注意 整個CGI程序的主體必須放在MAIN()函數中。
二 URL譯碼與解碼
由於Web服務器和浏覽器不能正確處理一些特殊的字符 Web服務器和浏覽器之間可能會因此而產生某種程度的誤會 所以在數據被傳送之前 浏覽器都要對表單內客戶輸入的數據中的特殊字符進行URL譯碼。
例如 Web系統用“=”分解表單各元素的NAME和VALUE屬性 用“&”分解不同表單元素的輸入數據。如果在表單的輸入數據中包含這些特殊的字符 並且表單的數據在傳送給Web服務器前不作任何處理 則Web服務器將無法知道哪一個“=” “&”是用戶輸入的 哪一個是浏覽器加上的。在由表單屬性ACTION定義的URL中 也可能會出現一些特殊的字符 當在CGI程序的名稱和路徑信息(PathInformation)中出現“=” “&”和“ ”時 都會影響數據的正確傳送。
URL譯碼(URLEncoding)就是將Web服務器所不能正確處理的特殊字符轉換成它的十六進制數的形式 比如將“”轉換成“%” “=”轉換成“=”等等。這些特殊的字符通常被稱作Web系統的保留字符。在Web系統上無論是用GET方法還是用POST方法傳送的數據都要進行URL譯碼。CGI程序要想處理表單傳送來的數據 還必須對浏覽器URL譯碼過的數據進行解碼。因此 理解URL譯碼對於我們進行CGI編程是非常重要的。URL譯碼一般包括以下步驟
1 浏覽器將所傳送的數據根據表單所包含的元素分解成“NAME=VALUE”形式 NAME和VALUE分別是表單元素的屬性。其中 VALUE屬性中存儲客戶機在表單中輸入的數據 如果客戶機沒有輸入數據 則VALUE存儲的是表單定義的缺省值如果缺省值也沒有定義 則VALUE值為空。
2 代表表單中各元素的各個“NAME=VALUE”對被浏覽器用“&”連接起來。
3 VALUE屬性中存放的數據若含有空格 則被轉換成“ ”。
4 URL和輸入數據中所包含的Web系統的保留字符必須被譯碼成其十六進制數形式。
5 被譯碼後的字符被表示成一個“”和它們的十六進制數形式(即HH)。
CGI程序從環境變量“QUERY_STRING”或標准輸入中讀入的數據是經過浏覽器URL譯碼過的 故在使用這些數據以前還必須對它們進行URL解碼。解碼的目的是將數據還原成客戶端用戶在Web頁面上輸入時的形式。本文已經介紹了URL譯碼過程 URL解碼過程與它正好相反 它一般包括以下步驟
1 從浏覽器用GET或POST方法所傳送來的數據中找出代表各個表單元素所儲存數據的“NAME=VALUE”對。
2 VALUE屬性中所存放的數據若含有“ ” 則被轉換成空格。
3 將VALUE屬性中所存放的數據的十六進制數“HH”轉換成相應的字符。
Web系統將漢字當成特殊的字符 對它也要進行URL譯碼。對於一個特殊的單字節字符(比如“/”) 浏覽器通常將它譯碼成十六進制數的形式(比如/) “”表示它後面跟的是兩位十六進制數。當VB程序對其進行處理時調用Chr$函數就可以將其恢復為原貌。而一個漢字則被浏覽器譯碼成四位十六進制數(比如張)。如果CGI程序還像以前那樣分別調用Chr(D5)和Chr(C5) 則由於D5 C5都不是正常的單字節十六進制數碼 故Chr函數返回空 漢字將無法正確還原。正確的做法應該是將有關漢字的四位十六進制數一起傳給函數Chr(如Chr(D5C5)) 此時漢字才能被正確還原。
因此 可以讓CGI程序對四位連續的十六進制數一起進行譯碼 以便使漢字能夠被正確還原。但在這種情況下 當客戶端用戶輸入了兩個連續的Web系統保留字符時 CGI程序又可能把它們當成漢字來處理。這時可以讓CGI程序在 要對四位連續的十六進制數進行譯碼時首先檢查前面兩位是否為Web系統的保留字符 如果是則仍然按照單字節的字符處理。不過如果客戶端用戶在表單內填寫了很多漢字 則CGI程序的負擔將會大大加重。事實上 在大多數情況下 客戶端用戶很少會使用兩個連續的Web系統的保留字符 所以可以只讓CGI程序對最容易出現的情形如“://”(當客戶端用戶在表單中輸入某一URL時會出現這種情況)進行檢查 本文下節提供的函數UrlDecode()可以實現對漢字和Web系統保留字符的URL解碼。->