在許多應用程序中使用GET都沒有問題。但是,GET要求通過一個環境變量將自己的數據傳遞給CGI程序。但假如GET字串過長,有些Web服務器可能用光自己的環境空間(若字串長度超過200字符,就應開始關心這方面的問題)。CGI為此提供了一個解決方案:POST。通過POST,數據可以編碼,並按與GET相同的方法連結起來。但POST利用標准輸入將編碼過後的查詢字串傳遞給CGI程序。我們要做的全部事情就是判斷查詢字串的長度,而這個長度已在環境變量CONTENT_LENGTH中保存好了。一旦知道了長度,就可自由分配存儲空間,並從標准輸入中讀入指定數量的字符。
對一個用來控制POST的CGI程序,由CGITools.h提供的Pair和CGI_vector均可不加絲毫改變地使用。下面這段程序揭示了寫這樣的一個CGI程序有多麼簡單。這個例子將采用“純”C++,所以studio.h庫被iostream(IO數據流)代替。對於iostream,我們可以使用兩個預先定義好的對象:cin,用於同標准輸入連接;以及cout,用於同標准輸出連接。有幾個辦法可從cin中讀入數據以及向cout中寫入。但下面這個程序准備采用標准方法:用“<<”將信息發給cout,並用一個成員函數(此時是read())從cin中讀入數據:
//: POSTtest.cpp // CGI_vector works as easily with POST as it // does with GET. Written in "pure" C++. #include <iostream.h> #include "CGITools.h" void main() { cout << "Content-type: text/plain\n" << endl; // For a CGI "POST," the server puts the length // of the content string in the environment // variable CONTENT_LENGTH: char* clen = getenv("CONTENT_LENGTH"); if(clen == 0) { cout << "Zero CONTENT_LENGTH" << endl; return; } int len = atoi(clen); char* query_str = new char[len + 1]; cin.read(query_str, len); query_str[len] = '\0'; CGI_vector query(query_str); // Test: dump all names and values for(int i = 0; i < query.size(); i++) cout << "query[" << i << "].name() = [" << query[i].name() << "], " << "query[" << i << "].value() = [" << query[i].value() << "]" << endl; delete query_str; // Release storage } ///:~
getenv()函數返回指向一個字串的指針,那個字串指示著內容的長度。若指針為零,表明CONTENT_LENGTH環境變量尚未設置,所以肯定某個地方出了問題。否則就必須用ANSI C庫函數atoi()將字串轉換成一個整數。這個長度將與new一起運用,分配足夠的存儲空間,以便容納查詢字串(另加它的空中止符)。隨後為cin()調用read()。read()函數需要取得指向目標緩沖區的一個指針以及要讀入的字節數。隨後用空字符(null)中止query_str,指出已經抵達字串的末尾,這就叫作“空中止”。
到這個時候,我們得到的查詢字串與GET查詢字串已經沒有什麼區別,所以把它傳遞給用於CGI_vector的構建器。隨後便和前例一樣,我們可以自由vector內不同的字段。
為測試這個程序,必須把它編譯到主機Web服務器的cgi-bin目錄下。然後就可以寫一個簡單的HTML頁進行測試,就象下面這樣:
<HTML> <HEAD> <META CONTENT="text/html"> <TITLE>A test of standard HTML POST</TITLE> </HEAD> Test, uses standard html POST <Form method="POST" ACTION="/cgi-bin/POSTtest"> <P>Field1: <INPUT TYPE = "text" NAME = "Field1" VALUE = "" size = "40"></p> <P>Field2: <INPUT TYPE = "text" NAME = "Field2" VALUE = "" size = "40"></p> <P>Field3: <INPUT TYPE = "text" NAME = "Field3" VALUE = "" size = "40"></p> <P>Field4: <INPUT TYPE = "text" NAME = "Field4" VALUE = "" size = "40"></p> <P>Field5: <INPUT TYPE = "text" NAME = "Field5" VALUE = "" size = "40"></p> <P>Field6: <INPUT TYPE = "text" NAME = "Field6" VALUE = "" size = "40"></p> <p><input type = "submit" name = "submit" > </p> </Form> </HTML>
填好這個表單並提交出去以後,會得到一個簡單的文本頁,其中包含了解析出來的結果。從中可知道CGI程序是否在正常工作。
當然,用一個程序片來提交數據顯得更有趣一些。然而,POST數據的提交屬於一個不同的過程。在用常規方式調用了CGI程序以後,必須另行建立與服務器的一個連接,以便將查詢字串反饋給它。服務器隨後會進行一番處理,再通過標准輸入將查詢字串反饋回CGI程序。
為建立與服務器的一個直接連接,必須取得自己創建的URL,然後調用openConnection()創建一個URLConnection。但是,由於URLConnection一般不允許我們把數據發給它,所以必須很可笑地調用setDoOutput(true)函數,同時調用的還包括setDoInput(true)以及setAllowUserInteraction(false)——注釋⑥。最後,可調用getOutputStream()來創建一個OutputStream(輸出數據流),並把它封裝到一個DataOutputStream裡,以便能按傳統方式同它通信。下面列出的便是一個用於完成上述工作的程序片,必須在從它的各個字段裡收集了數據之後再執行它:
//: POSTtest.java // An applet that sends its data via a CGI POST import java.awt.*; import java.applet.*; import java.net.*; import java.io.*; public class POSTtest extends Applet { final static int SIZE = 10; Button submit = new Button("Submit"); TextField[] t = new TextField[SIZE]; String query = ""; Label l = new Label(); TextArea ta = new TextArea(15, 60); public void init() { Panel p = new Panel(); p.setLayout(new GridLayout(t.length + 2, 2)); for(int i = 0; i < t.length; i++) { p.add(new Label( "Field " + i + " ", Label.RIGHT)); p.add(t[i] = new TextField(30)); } p.add(l); p.add(submit); add("North", p); add("South", ta); } public boolean action (Event evt, Object arg) { if(evt.target.equals(submit)) { query = ""; ta.setText(""); // Encode the query from the field data: for(int i = 0; i < t.length; i++) query += "Field" + i + "=" + URLEncoder.encode( t[i].getText().trim()) + "&"; query += "submit=Submit"; // Send the name using CGI's POST process: try { URL u = new URL( getDocumentBase(), "cgi-bin/POSTtest"); URLConnection urlc = u.openConnection(); urlc.setDoOutput(true); urlc.setDoInput(true); urlc.setAllowUserInteraction(false); DataOutputStream server = new DataOutputStream( urlc.getOutputStream()); // Send the data server.writeBytes(query); server.close(); // Read and display the response. You // cannot use // getAppletContext().showDocument(u); // to display the results as a Web page! DataInputStream in = new DataInputStream( urlc.getInputStream()); String s; while((s = in.readLine()) != null) { ta.appendText(s + "\n"); } in.close(); } catch (Exception e) { l.setText(e.toString()); } } else return super.action(evt, arg); return true; } } ///:~
⑥:我不得不說自己並沒有真正理解這兒都發生了什麼事情,這些概念都是從Elliotte Rusty Harold編著的《Java Network Programming》裡得來的,該書由O'Reilly於1997年出版。他在書中提到了Java連網函數庫中出現的許多令人迷惑的Bug。所以一旦涉足這些領域,事情就不是編寫代碼,然後讓它自己運行那麼簡單。一定要警惕潛在的陷阱!
信息發送到服務器後,我們調用getInputStream(),並把返回值封裝到一個DataInputStream裡,以便自己能讀取結果。要注意的一件事情是結果以文本行的形式顯示在一個TextArea(文本區域)中。為什麼不簡單地使用getAppletContext().showDocument(u)呢?事實上,這正是那些陷阱中的一個。上述代碼可以很好地工作,但假如試圖換用showDocument(),幾乎一切都會停止運行。也就是說,showDocument()確實可以運行,但從POSTtest得到的返回結果是“Zero CONTENT_LENGTH”(內容長度為零)。所以不知道為什麼原因,showDocument()阻止了POST查詢向CGI程序的傳遞。我很難判斷這到底是一個在以後版本裡會修復的Bug,還是由於我的理解不夠(我看過的書對此講得都很模糊)。但無論在哪種情況下,只要能堅持在文本區域裡觀看自CGI程序返回的內容,上述程序片運行時就沒有問題。