1.CGI 指令碼結構
當指令碼被伺服器引發時,伺服器常常以兩種途徑之一向指令碼傳遞資訊:GET或POST。這兩種方法被稱為要求方法。所使用的要求方法是通過環境變數傳給指令碼,該環境變數叫作REQUEST_METHOD(還定義了另外兩種要求方法一HEAD和PUT,但它們不是特別應用於CGI,並且不鼓勵使用它們)。
1)GET是對資料的一個請求——同樣的方法被用於獲得靜態文檔。GET方法以附加在URL後面的參數發送請求資訊。這些參數將放在環境變數QUERY_STRING中傳給CGI程式。例如,有一個叫作Myprog.exe的指令碼,從如下的連結啟動它:
<a href="cgi-bin/myprog.exe?lname=blow&fname=joe">
REQUEST_METHOD是GET,QUERY_STRING包含lname=b1ow&fname=joe。在“URL一編碼”中將討論QUERY_STRING的格式。
問號從QUERY_STRING的起始處分隔開指令碼名字。在一些伺服器上,問號是強制性的,即使後面沒有跟著QUERY_STRING。另一些伺服器則允許用一個正斜杠代替問號或與之附加在一起。如果使用斜杠,伺服器則用PATH_INFO而不是QUERY_STRING變數將資訊傳給指令碼。(URL解碼)
2)當瀏覽器將資料從一個填寫表單傳給伺服器時,發生POST操作。對於POST,QUERY一STRING可能為空白或不空,這有賴於伺服器。如果有資訊,則其如GET的情況一樣被格式化和傳遞。
來自POST查詢的資料使用STDIN從伺服器傳到指令碼。由於STDIN是一個源,指令碼需要知道有多少有效資料。於是伺服器還提供了另一個變數,CONTENT_LENGTH,以指出到來資料的位元組數。而POST的資料格式為:
variable1=value1&variable2=value2&etc
你的程式必須檢查REQUEST_METHOD環境變數以知道是否要讀取STDIN。CONTENT_LENGTH變數一般只在REOUEST_METHOD為POST時有用。
CGI應用的基本結構既簡單又直接明了:初始化、處理、輸出和終止。由於討論的是概念、資料來源、編程規則,所以在例子中將使用偽碼而不是使用某種特定語言。
理想情況下,一個指令碼具有如下形式(do-initialize,do-process和do-output代表恰當的子常式):
程式開始
調用 do-initialize
調用 do-proces
調用 do一output
程式結束。
實際情況並非這麼簡單。
1.1 初始化
指令碼啟動後必須做的第一件事是確定其輸入、環境和狀態。基本作業系統環境資訊能以通常方式得到:在Windows NT或windows95中從系統註冊區得到,在Unix系統中從標準環境變數得到,在別的Windows版本中從INI檔案得到,等等。
狀態資訊來自於輸入,而不是作業環境或靜態變數。記住:每當CGI指令碼被引發時,它都好象此前從未被引發過。指令碼不在調用之間持續運行,所有的東西都必須從頭初始化,如下:
1.確定指令碼是如何被引發的
典型情況下,這涉及讀取REQUEST_METHOOD環境變數並分析其中的單詞GET或POST。
注意
儘管當前定義應用於coi的操作只有GET和posT,你或許會時不時地遇到PUT或HEAD,假王口你的伺服器支援它並且使用者的剜覽器或一個機器人使用它就可能發生這種情況。 PUl7k作為PosT的另選提供,但從未得多(認可的RFC資格,一般不被使用。HEAD被一些剜覽器fotL器人(自動剜覽器賬用,僅用於提取HTML文在的頭部,不適用於C6路程。此外還有一些古怪的要求方法。你的代碼應該檢查是否為GET和PosT,拒絕任何其他方法,不要假設要求方法如果不是GET便是PosT,或者相反。
2.提取輸入資料
如果方法是GET,必須獲得、分析、解碼QUERY_STRING環境變數。如果方法是POST,必須檢查QUERY_STRING並還要分析STDIN。如果CONTENT_TYPE環境變數是設為application/x-www-form-urlencoded,來自STDIN的源也需要解碼。
1.2處理
指令碼通過讀取和分析其輸入從而對環境初始化之後,便準備進入工作。在此階段發生的事情則遠沒有初始化階段那樣確定。在初始化時,參數是知道的(或是可以被發現),所要做的任務對於各個指令碼都多多少少地相同。然而,處理階段是指令碼的核心,在此時要做的事情幾乎完全依賴於指令碼的目標。
1.處理輸入資料
此時做什麼取決於指令碼。例如,你可以忽略全部輸入而僅僅輸出資料,可能以有條理格式化的HTML將輸入在吐出去,或許會在一個資料庫中獵取資訊在將其顯示出來,或者是從前沒有想到的任何事情。處理資料一般意味著,以某種方式對其進行轉換。在傳統的資料處理術語中,這叫做轉換步驟,因為,在面向批作業的處理中,程式讀取一個記錄並對其施加一些規則(轉換它),然後將其寫回。CGI 程式很少被看作傳統的資料處理,但思想是一樣的。程式的處理資料階段不同的CGI
程式,——在資料處理階段,你拿到輸入,並從其中做出一些新的東西來。
2.輸出結果
在一個簡單的CGI指令碼中,輸出常常只是一個頭部和一些HTML。更複雜些的指令碼可能;輸出圖形、圖形與文本的混和,或者為了用一些附加資訊再次呼叫指令碼而必要的全部資訊。一個常用並且更精巧的技術是使用GET呼叫指令碼一次,這可以用一個標準的<A HREF>標記做到。指令碼可以感知它是用GET調用的,並動態地建立HTML表單一一包括隱藏變數和再次用POST呼叫指令碼所需的代碼。
相容性問題
在UNIX世界中,字元流是一種特殊的檔案。預設地,STDIN和STDOUT是字元流。作業系統很有協助地為你分析流,確保所通過的全是正確的7-bitASCII碼,或者是認可的控制碼。
7-bit?是的。對於HTML,這沒有問題。然而,如果你的指令碼發送圖形資料,使用面向字元的流則意味著立即失敗。解決方案是將流切換到二進位模式。在C語言中,可以使用setmode函數:setmode(fileno(stdout),O_BINARY)。通過setmode(fi1eno(stdout),O_TEXT)在流當中進行切換。一個典型的圖形指令碼以字元模式輸出頭部,而後切換到二進位模式用於圖形資料。
在windows NT世界中,為了相容性,流有著同樣方式的行為。輸出中的一個簡單\n,當寫到STDOUT時,被變換為\r\n。一般的windows NT調用,如write Fi1e(),不發生上述變換,如果同時想要一個斷行符號和一個換行,則必須顯式地指出\r\n。
字元模式和二進位模式的另一種說法是cooked和raw,知道這兩個名詞的人或許會使用它們,而不是更常見的說法。不管使用什麼詞,在什麼平台上,關於流存在著另一問題:預設情況下,它們是有緩衝區的,意思是作業系統掛起資料,直至看見一個行結束符、緩衝區滿或者流被關閉。這意味著,你如果將有緩衝區的prinif()語句同無緩衝區的fwriie()或fprintf()語句混合在一起,事情可能就變得混亂了,儘管它們都會是寫到STDOUT。printf()有緩衝區地將資料寫到流,面向檔案的常式則無緩衝區地輸出資料。結果是亂序的一團糟。
你可能將此歸咎於回溯相容性。除了許多老程式之外,流實在沒理由將預設定為有緩衝區和cooked。這應當是在需要時可以開啟的選項,而不是在不要時關閉。幸運的是,你能夠用setvbuf(stdout,NULL,_IONBF,0)解決這一困難,這個函數關閉UTDOUT流的全部緩衝區。
另一個解決是避免混和不同類型的輸出語句,即使這樣,也不能使cooked輸出變成raw。所以最好是關閉所有緩衝區。許多伺服器和瀏覽器不喜歡接收單調乏味的輸入。
注意
那些常把UNIX掛在嘴邊的人可能會對名詞CRLF(斷行符號與換行)皺眉,而那些在其他平台上編程的人也許不認識\n或\r\n。CRLF等於\r\n。C編程者用\r表示一個斷行符號(CR)符號,用\n表示一個換行(LF)符。(對於Basic編程,LF是Chr$(10,CR是Chr$(13)。)
1.3 終止
終止就是清理和退出。你如果對任何檔案加了鎖,則必須在程式結束前釋放它們。你如果分配了記憶體、訊號量或其他對象,也必須進行釋放。不正確完成這些會導致指令碼“曇花只能一現”。即指令碼在第一次調用時能工作,而在以後的調用中就會崩潰。更有甚者,指令碼由於沒有正確釋放資源和鎖,將會妨礙甚至破壞其他指令碼或伺服器本身。
在一些平台上一Windows NT最顯著, UNIX次之——檔案控制代碼和記憶體對象在進程終止時會被關閉和收回。即使這樣,依賴作業系統為你清理垃圾也非明智之舉。例如,在Windows NT上,如果一個程式對一個檔案全部或部分加鎖,而後不釋放鎖便終止,則檔案系統的行為將是不確定的。
必須確保你的出錯一退出常式——如果有(也應該有)——瞭解指令碼的資源並能象主退出常式一樣徹底地對它們進行清理。
2.計劃指令碼
現在讀者已經看到了一個指令碼的基本結構,下面將要學習如何從頭計劃一個指令碼。按照如下基本步驟進行:
用一些時間定義程式的任