- Java核心技術(shù)·卷Ⅱ:高級(jí)特性(原書第10版)
- (美)凱S.霍斯特曼
- 2780字
- 2020-10-30 18:10:58
4.4.3 提交表單數(shù)據(jù)
在上一節(jié)中,我們介紹了如何從Web服務(wù)器讀取數(shù)據(jù)。現(xiàn)在,我們將介紹如何讓程序再將數(shù)據(jù)反饋回Web服務(wù)器和那些被Web服務(wù)器調(diào)用的程序。
為了將信息從Web瀏覽器發(fā)送到Web服務(wù)器,用戶需要填寫一個(gè)類似圖4-8中所示的表單。

圖4-8 HTML表單
當(dāng)用戶點(diǎn)擊提交按鈕時(shí),文本框中的文本以及復(fù)選框和單選按鈕的設(shè)定值都被發(fā)送到了Web服務(wù)器。此時(shí),Web服務(wù)器調(diào)用程序?qū)τ脩舻妮斎脒M(jìn)行處理。
有許多技術(shù)可以讓W(xué)eb服務(wù)器實(shí)現(xiàn)對(duì)程序的調(diào)用。其中最廣人所知的是Java Servlet、JavaServer Face、微軟的ASP(Active Server Pages,動(dòng)態(tài)服務(wù)器主頁)以及CGI(Common Gateway Interface,通用網(wǎng)關(guān)接口)腳本。
服務(wù)器端程序用于處理表單數(shù)據(jù)并生成另一個(gè)HTML頁,該頁會(huì)被Web服務(wù)器發(fā)回給瀏覽器,這個(gè)操作過程我們?cè)趫D4-9中作了說明。返回給瀏覽器的響應(yīng)頁可以包含新的信息(例如,信息檢索程序中的響應(yīng)頁)或者僅僅只是一個(gè)確認(rèn)。之后,Web瀏覽器將顯示響應(yīng)頁。

圖4-9 執(zhí)行服務(wù)器端腳本過程中的數(shù)據(jù)流
我們不會(huì)在本書中介紹應(yīng)該如何實(shí)現(xiàn)服務(wù)器端程序,而是將側(cè)重點(diǎn)放在如何編寫客戶端程序使之與已有的服務(wù)器端程序進(jìn)行交互。
當(dāng)表單數(shù)據(jù)被發(fā)送到Web服務(wù)器時(shí),數(shù)據(jù)到底由誰來解釋并不重要,可能是Servlet或CGI腳本,也可能是其他服務(wù)器端技術(shù)。客戶端以標(biāo)準(zhǔn)格式將數(shù)據(jù)發(fā)送給Web服務(wù)器,而Web服務(wù)器則負(fù)責(zé)將數(shù)據(jù)傳遞給具體的程序以產(chǎn)生響應(yīng)。
在向Web服務(wù)器發(fā)送信息時(shí),通常有兩個(gè)命令會(huì)被用到:GET和POST。
在使用GET命令時(shí),只需將參數(shù)附在URL的結(jié)尾處即可。這種URL的格式如下:

其中,每個(gè)參數(shù)都具有“名字=值”的形式,而這些參數(shù)之間用&字符分隔開。參數(shù)的值將遵循下面的規(guī)則,使用URL編碼模式進(jìn)行編碼:
·保留字符A到Z、a到z、0到9,以及.-~_。
·用+字符替換所有的空格。
·將其他所有字符編碼為UTF-8,并將每個(gè)字節(jié)都編碼為%后面緊跟一個(gè)兩位的十六進(jìn)制數(shù)字。
例如,若要發(fā)送街道名San Francisco,CA,可以使用San+Francisco%2c+CA,因?yàn)槭M(jìn)制數(shù)2c(即十進(jìn)制數(shù)44)是“,”的UTF-8碼值。
這種編碼方式使得在任何中間程序中都不會(huì)混入空格,并且也不需要對(duì)其他特殊字符進(jìn)行轉(zhuǎn)換。
例如,就在寫作本書的時(shí)候,Google Map網(wǎng)站(www.google.com/maps)可以接受帶有兩個(gè)名為q和h1參數(shù)的查詢請(qǐng)求,這兩個(gè)參數(shù)分別表示查詢的位置和響應(yīng)中所使用的人類語言。為了得到1 Market Street,San Franciso,CA的地圖,并且讓響應(yīng)使用德語,只需訪問下面的URL即可:

在瀏覽器中出現(xiàn)很長的查詢字符串很讓人郁悶,而且老式的瀏覽器和代理對(duì)在GET請(qǐng)求中能夠包含的字符數(shù)量做出了限制。正因?yàn)榇耍琍OST請(qǐng)求經(jīng)常用來處理具有大量數(shù)據(jù)的表單。在POST請(qǐng)求中,我們不會(huì)在URL上附著參數(shù),而是從URLConnection中獲得輸出流,并將名/值對(duì)寫入到該輸出流中。我們?nèi)耘f需要對(duì)這些值進(jìn)行URL編碼,并用&字符將它們隔開。
下面,我們將詳細(xì)介紹這個(gè)過程。在提交數(shù)據(jù)給服務(wù)器端程序之前,首先需要?jiǎng)?chuàng)建一個(gè)URLConnection對(duì)象。

然后,調(diào)用setDoOutput方法建立一個(gè)用于輸出的連接。

接著,調(diào)用getOutputStream方法獲得一個(gè)流,可以通過這個(gè)流向服務(wù)器發(fā)送數(shù)據(jù)。如果要向服務(wù)器發(fā)送文本信息,那么可以非常方便地將流包裝在PrintWriter對(duì)象中。

現(xiàn)在,可以向服務(wù)器發(fā)送數(shù)據(jù)了。

之后,關(guān)閉輸出流。

最后,調(diào)用getInputStream方法讀取服務(wù)器的響應(yīng)。
下面我們來實(shí)際操作一個(gè)例子。地址為https://www.usps.com/zip4的網(wǎng)站包含一個(gè)用于查找街道地址的郵政編碼的表單(見圖4-8)。要想在Java程序中使用這個(gè)表單,需要知道POST請(qǐng)求的URL和參數(shù)。
你可以通過查看這個(gè)表單的HTML源碼來獲取這些信息,但是通常用網(wǎng)絡(luò)監(jiān)視器來“窺視”發(fā)出的請(qǐng)求會(huì)更容易一些。作為其開發(fā)工具包的組成部分,大多數(shù)瀏覽器都具有網(wǎng)絡(luò)監(jiān)視器。例如,圖4-10展示了Firefox網(wǎng)絡(luò)監(jiān)視器向我們的示例網(wǎng)站提交數(shù)據(jù)時(shí)的截屏。你可以發(fā)現(xiàn)其中的提交URL以及參數(shù)名和參數(shù)值。

圖4-10 一個(gè)HTML表單
在提交表單數(shù)據(jù)時(shí),HTTP頭包含了內(nèi)容類型和內(nèi)容長度:

你還可以以其他格式提交表單。例如,發(fā)送用JavaScript對(duì)象表示法(JSON)表示的數(shù)據(jù),將內(nèi)容類型設(shè)置為application/json。
POST的頭還必須包括內(nèi)容長度,例如:

程序清單4-7用于將POST數(shù)據(jù)發(fā)送給任何腳本,它將數(shù)據(jù)放在如下的.properties文件:

這個(gè)程序移除了url項(xiàng),并將其他內(nèi)容都發(fā)送到了doPost方法。
在doPost方法中,我們首先打開連接、調(diào)用setDoOutput(true)并打開輸出流。然后,枚舉Map對(duì)象中的所有鍵和值。對(duì)每一個(gè)鍵-值對(duì),我們發(fā)送key、=字符、value和&分隔符:

在從寫出請(qǐng)求切換到讀取響應(yīng)的任何部分時(shí),就會(huì)發(fā)生與服務(wù)器的實(shí)際交互。Content-Length頭被設(shè)置為輸出的尺寸,而Content-Type頭被設(shè)置為application/x-www-form-urlencoded,除非指定了不同的內(nèi)容類型。這些頭信息和數(shù)據(jù)都被發(fā)送給服務(wù)器,然后,響應(yīng)頭和服務(wù)器響應(yīng)會(huì)被讀取,并可以被查詢。在我們的示例程序中,這種切換發(fā)生在對(duì)connection.getContentEncoding()的調(diào)用中。
在讀取響應(yīng)過程中會(huì)碰到一個(gè)問題。如果服務(wù)器端出現(xiàn)錯(cuò)誤,那么調(diào)用connection.getInputStream()時(shí)就會(huì)拋出一個(gè)FileNotFoundException異常。但是,此時(shí)服務(wù)器仍然會(huì)向?yàn)g覽器返回一個(gè)錯(cuò)誤頁面(例如,常見的“錯(cuò)誤404-找不到該頁”)。為了捕捉這個(gè)錯(cuò)誤頁,可以調(diào)用getErrorStream方法:

注意:getErrorStream方法與這個(gè)程序中的許多其他方法一樣,屬于URLConnection類的子類HttpURLConnection。如果要?jiǎng)?chuàng)建以http://或https://開頭的URL,那么可以將所產(chǎn)生的連接對(duì)象強(qiáng)制轉(zhuǎn)型為HttpURLConnection。
在將POST數(shù)據(jù)發(fā)送給服務(wù)器時(shí),服務(wù)器端程序產(chǎn)生的響應(yīng)可能是redirect:,后面跟著一個(gè)完全不同的URL,該URL應(yīng)該被調(diào)用以獲取實(shí)際的信息。服務(wù)器可以這么做,因?yàn)檫@些信息位于他處,或者提供了一個(gè)可以作為書簽標(biāo)記的URL。HttpURLConnection類在大多數(shù)情況下可以處理這種重定向。
注意:如果cookie需要在重定向中從一個(gè)站點(diǎn)發(fā)送給另一個(gè)站點(diǎn),那么你可以像下面這樣配置一個(gè)全局的cookie處理器:

然后,cookie就可以被正確地包含在重定向請(qǐng)求中了。
盡管重定向通常是自動(dòng)處理的,但是有些情況下,你需要自己完成重定向。例如,在HTTP和HTTPS之間的自動(dòng)重定向因?yàn)榘踩蚨槐恢С帧V囟ㄏ蜻€會(huì)因更細(xì)微的原因而失敗。例如,郵政編碼服務(wù)在User-Agent請(qǐng)求參數(shù)包含字符串Java時(shí)無法工作,這可能是因?yàn)猷]政局不想為程序自動(dòng)產(chǎn)生的請(qǐng)求服務(wù)。盡管可以在最初的請(qǐng)求中將用戶代理設(shè)置為其他的字符串,但是這項(xiàng)設(shè)置在自動(dòng)重定向中并沒有用到。自動(dòng)重定向總是會(huì)發(fā)送包含單詞Java的通用用戶代理字符串。
在這些情況下,可以人工實(shí)現(xiàn)重定向。在連接到服務(wù)器之前,將自動(dòng)重定向關(guān)閉:

在發(fā)送請(qǐng)求之后,獲取響應(yīng)碼:

檢查它是否是下列值之一:

如果是這些值之一,那么獲取Location響應(yīng)頭,以獲得重定向的URL。然后,斷開連接,并創(chuàng)建到新的URL的連接:

每當(dāng)需要從某個(gè)現(xiàn)有的Web站點(diǎn)查詢信息時(shí),該程序所展示的處理技術(shù)就會(huì)顯得很有用。只需找出需要發(fā)送的參數(shù),然后從回復(fù)信息中剔除HTML和其他不必要的信息。
注意:正如你所看到的,可以使用Java庫的類來與網(wǎng)頁交互,但是用起來并非特別方便。可以考慮使用其他的庫,例如Apach HttpClient(http://hc.apache.org/httpcomponents-client-ga)。
程序清單4-7 post/PostTest.java




java.net.HttpURLConnection 1.0
·InputStream getErrorStream()
返回一個(gè)流,通過這個(gè)流可以讀取Web服務(wù)器的錯(cuò)誤信息。
java.net.URLEncoder 1.0
·static String encode(String s,String encoding)1.4
采用指定的字符編碼模式(推薦使用“UTF-8”)對(duì)字符串s進(jìn)行編碼,并返回它的URL編碼形式。在URL編碼中,'A'-'Z','a'-'z','0'-'9','-','_','.'和'*'等字符保持不變,空格被編碼成'+',所有其他字符被編碼成"%XY"形式的字節(jié)序列,其中0xXY為該字節(jié)十六進(jìn)制數(shù)。
java.net.URLDecoder 1.2
·static string decode(String s,String encoding)1.4
采用指定編碼模式對(duì)已編碼字符串s進(jìn)行解碼,并返回結(jié)果。
- C# Programming Cookbook
- 簡單高效LATEX
- Learning Data Mining with Python
- 深入淺出DPDK
- 數(shù)據(jù)結(jié)構(gòu)習(xí)題解析與實(shí)驗(yàn)指導(dǎo)
- CoffeeScript Application Development Cookbook
- 用戶體驗(yàn)可視化指南
- Learning Continuous Integration with TeamCity
- Oracle GoldenGate 12c Implementer's Guide
- INSTANT Silverlight 5 Animation
- Mastering ASP.NET Core 2.0
- Learning Ionic(Second Edition)
- Java EE 程序設(shè)計(jì)
- Mastering Responsive Web Design
- Python Geospatial Analysis Cookbook