將數(shù)據(jù)提交到網(wǎng)頁服務(wù)器
在上面的例子中,數(shù)據(jù)是作為 URL 的一部分被送到服務(wù)器的,使用的 GET 方法?,F(xiàn)在來看一個使用 POST 方法發(fā)送數(shù)據(jù)的例子。這個例子中,http://www.javacourses.com/cgi-bin 中的 CGI 腳本 (名為 .cgi) 需要 name 和 email 值。如果用戶提交 Sally McDonald 作為 name 值,smc@yahoo.com 作為 email 值,CGI 腳本會獲取輸入并對消息進行解析、解碼,再將提交的內(nèi)容返回給客戶端。這個 CGI 腳本做事情并不多,但我們要用它來演示如何向服務(wù)器提交數(shù)據(jù)。
還有一點非常重要——請注意使用 POST 方法時,消息內(nèi)容的類型是 application/x-www-form-urlencoded,這種類型會:
- 指定常規(guī)數(shù)據(jù)編碼
- 將空格轉(zhuǎn)換為加號 (+)
- 將非文本內(nèi)容轉(zhuǎn)換成十六進制數(shù)后接百分號 (%) 的形式
- 在每個 name=value 對之間放置 & 符號
根據(jù)這個編碼規(guī)則,消息 (name=Sally McDonald and email=smc@yahoo.com) 在發(fā)送給 CGI 腳本之前必須被編碼成:
name=Sally+McDonald&email=smc@yahoo.com
CGI 腳本收到這個已編碼的消息后會對它進行解碼。不過非常幸運,你不必為手工進行編碼操作。你可以使用 java.net.URLEncoder 類來對消息進行編碼,就像下面的例子中那樣。相應(yīng)地你可以使用 java.net.URLDecoder 來對消息進行解碼。
示例代碼 5 中是這個例子的實現(xiàn) (使用 HttpURLConnection 類),它展示了如何使用 POST 方法向服務(wù)器發(fā)送數(shù)據(jù)。你會看到:
- 為 CGI 腳本打開連接和 I/O 流
- 設(shè)置請求方法為 POST
- 使用 URLEncoder.encode 方法對消息進行編碼 (URLDecoder.decode 方法可以用于解碼)
- 向 CGI 腳本發(fā)送已經(jīng)編碼的消息
- 接收服務(wù)器返回的消息并在控制臺打印出來
示例代碼 5:PostExample.java
import java.io.*;
import java.net.*;
public class PostExample {
public static void main(String[] argv) throws Exception {
URL url = new URL("http://www.javacourses.com/cgi-bin/names.cgi");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
PrintWriter out = new PrintWriter(connection.getOutputStream());
// encode the message
String name = "name="+URLEncoder.encode("Qusay Mahmoud", "UTF-8");
String email = "email="+URLEncoder.encode("qmahmoud@javacourses.com", "UTF-8");
// send the encoded message
out.println(name+"&"+email);
out.close();
BufferedReader in
= new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
in.close();
}
}
代理服務(wù)器和防火墻
如果你使用了防火墻,你就得把代理服務(wù)器以及端口號的詳細信息告訴 Java,這樣才能訪問到防火墻外的主機。你可以通過定義一些 HTTP 或者 FTP 屬性來做到:
http.proxyHost (default: )
http.proxyPort (default: 80 if http.proxyHost specified)
http.nonProxyHosts (default: )
http.proxyHost 和 http.proxyPort 用來指定 HTTP 協(xié)議處理器需要使用的代理服務(wù)器和端口號。http.nonProxyHosts 用來指定哪些主機是直接連接的 (即不通過代理服務(wù)器來連接)。http.nonProxyHosts 屬性的值是一個由 | 分隔開的主機列表,它可以使用正則表達式來表示所匹配的主機,如:*.sfbay.sun.com 將匹配 sfbay 域中的任何主機。
ftp.proxyHost (default: )
ftp.proxyPort (default: 80 if ftp.proxyHost specified)
ftp.nonProxyHosts (default: )
ftp.proxyHost 和 ftp.proxyPort 用來指定 FTP 協(xié)議處理器需要使用的代理服務(wù)器和端口號。ftp.nonProxyHosts 用來指定哪些主機是直接聯(lián)系的,指定的方法與 http.nonProxyHosts 類似。
你可以在應(yīng)用程序啟動的時候設(shè)置這些屬性:
Prompt> java -Dhttp.proxyHost=HostName -Dhttp.proxyPort=PortNumber yourApp
HTTP 驗證
HTTP 協(xié)議提供驗證機制來保護資源。當一個請求要求取得受保護的資源時,網(wǎng)頁服務(wù)器回應(yīng)一個 401 Unauthorized error 錯誤碼。這個回應(yīng)包含一個指定了驗證方法和領(lǐng)域的 WWW-Authenticate 頭信息。把這個領(lǐng)域想像成一個存儲著用戶名和密碼的數(shù)據(jù)庫,它將被用來標識受保護資源的有效的用戶。比如,你試著去訪問某個網(wǎng)站上標識為“Personal Files”的資源,服務(wù)器響應(yīng)可能是:WWW-Authenticate: Basic realm="Personal Files" (假設(shè)驗證方法是 Basic)。
驗證方法
現(xiàn)在有幾種用于網(wǎng)絡(luò)應(yīng)用的驗證方法,其中最廣泛使用的是基本驗證 (Basic Authentication) 和摘要驗證 (Digest Authentication)。
當用戶想要訪問有限的資源時,使用基本驗證方法的網(wǎng)頁服務(wù)器會要求瀏覽器詢顯示一個對話框,并要求用戶輸入用戶名和密碼。如果用戶輸入的用戶名和密碼正確,服務(wù)器就允許他訪問這些資源;否則,在接連三次嘗試失敗之后,會顯示一個錯誤消息頁面。這個方法的缺點是用戶名和密碼都是用 Base64 編碼 (全是可讀文本) 之后傳輸過去的。也就是說,這個驗證方法的安全程度只是和 Telnet 一樣,并不是非常安全。
數(shù)據(jù)驗證方法不會在網(wǎng)絡(luò)中傳輸密碼,而是生成一些數(shù)字 (根據(jù)密碼和其它一些需要的數(shù)據(jù)產(chǎn)生的) 來代替密碼,而這些數(shù)字是經(jīng)過 MD5 (Message Digest Algorithm) 加密的。生成的值在網(wǎng)絡(luò)有隨著服務(wù)器需要用來校難密碼的其它信息一起傳輸。這個方法明顯更為安全。
基于表單的驗證方法和基本驗證方法類似,只是服務(wù)器使用你自定義的登錄頁面來代替了標準的登錄對話框。
最后,客戶證書驗證使用 SLL (Secure Socket Layer,安全套接層) 和客戶證明。
在 Tomcat 下保護資源
你可以在 tomcat-users.xml 文件中寫一個用戶及其角色的列表。這個文件在 TOMCAT_HOME (你安裝 Tomcat 的目錄) 下的 conf 目錄中。這個文件默認包含了三個用戶 (tomcat、role1、both) 的定義。下面一段 XML 代碼是我添加了兩個新用戶 (qusay 和 reader) 之后的 tomcat-users.xml:
<tomcat-users>
<user name="tomcat" password="tomcat" roles="tomcat" />
<user name="role1" password="tomcat" roles="role1" />
<user name="both" password= "tomcat" roles="tomcat,role1" />
<user name="qusay" password="guesswhat" roles="author" />
<user name="reader" password="youguess" roles="reader" />
</tomcat-users>
新添加的兩個用戶 (qusay 和 reader) 的 roles 分別設(shè)置為 author 和 reader。角色屬性非常重要,因為當你創(chuàng)建安全規(guī)則的時候,每個受限制的資源都是與可訪問它的角色相關(guān)聯(lián) (稍后你會看到)。
下面做個實驗 (假設(shè)你已經(jīng)安裝并配置好了 Tomcat)。為你期望的頁應(yīng)用程序建立一個目錄??梢园聪铝胁襟E做好準備:
- 在你安裝了 Tomcat 的目錄下,有一個目錄是 webapps。在這個目錄下建立一個目錄 (如:learn)。
- 在第一步建立的目錄下建立一個子目錄,命名為 chapter。
- 在 chapter 目錄中,建立一個 HTML 文件,內(nèi)容自定,文件名為 index.html。
- 在第一步建立的目錄下建立一個名為 WEB-INF 的子目錄。
- 在 WEB-INF 目錄中創(chuàng)建一個名為 web.xml 的文件,該文件內(nèi)容如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<description>
Learning Web Programming
</description>
<security-constraint>
<web-resource-collection>
<web-resource-name>
Restricted Area
</web-resource-name>
<url-pattern>/chapter/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>tomcat</role-name>
<role-name>author</role-name>
<role-name>reader</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Authenticate yourself</realm-name>
</login-config>
</web-app>
web.xml 配置描述
web.xml 是描述配置的文件,這里集中說明一下安全相關(guān)的配置元素。
- <security-constraint>:這個元素限制對一個或者多個資源的訪問,可以在配置信息中出現(xiàn)多次。上面的配置信息中,它限制了 chapter 目錄 (http://localhost:8080/learn/chapter) 下所有資源的訪問。<security-constraint> 包含了下列元素:
- <web-resource-collection>:這個元素用于標識你想限制訪問的資源。你可以定義 URL 模式 和 HTTP 方法 (用 <http-method> 元素定義 HTTP 方法)。如果沒有定義 HTTP 方法,那么限制將應(yīng)用于所有方法。在上面的應(yīng)用中,我想限制訪問的資源是 http://localhost:8080/learn/chapter/*,也就是 chapter 目錄下的所有文檔。
- <auth-constraint>:這個元素可以訪問上面定義的受限資源的用戶角色。在上面的應(yīng)用中,tomcat、author 和 erader 這三個角色可以訪問這些資源。
<login-config>:這個元素用于指定驗證方法。它包含下列元素:
- <auth-method>:指定驗證方法。它的值可能是下列值集中的一個:BASIC (基本驗證)、DIGEST (摘要驗證)、FORM (基于表單的驗證) 或者 CLIENT-CERT (客戶證書驗證)。
- <realm-name>:如果選用 BASIC 方法進行驗證的時候,標準登錄對話框中的一個描述名稱。
示例
上述配置中使用了 BASIC 驗證方法。下面我們做個實驗:啟動你的 Tomcat 服務(wù)器并向它發(fā)送到 http://localhost:8080/learn/chapter 的請求。這時候,就會有像圖 3 所示那樣的對話框提示你輸入用戶和密碼:
圖 3:HTTP 基本驗證 (Basic Authentication)
輸入一個用戶及其密碼 (你可以看看 tomcat-users.xml 文件),這個用戶的角色應(yīng)該在配置 (web.xml) 中存在。如果你輸入的用戶名和密碼正確,你就能訪問到那些資源;否則,你還可以再試兩次。
使用摘要驗證來實驗:
- 關(guān)閉你的 Tomcat 服務(wù)器。
- 修改你的配置文件 (web.xml),把 BASIC 換成 DIGEST。
- 重新啟動你的 Tomcat 服務(wù)器。
- 打開一個新的瀏覽器窗口。
- 在地址欄中輸入 http://localhost:8080/learn/chapter 并回車。
你會看到類似的對話框。從圖 4 你可以看到,這個登錄對話框是安全的,因為使用了摘要驗證。
圖 4:HTTP 摘要驗證 (Digest Authentication)
服務(wù)器在幕后的回復
當使用基本驗證方法保護資源的時候,服務(wù)器發(fā)回類似于圖 5 所示的響應(yīng)信息:
圖 5:服務(wù)器回復 (基本驗證)
如果是使用的摘要驗證方法來保護的資源,服務(wù)器發(fā)回的響應(yīng)信息就像圖 6 所示的那樣:
圖 6:服務(wù)器回復 (摘要驗證)
Java 支持 HTTP 驗證
J2SE (1.2 或者更高版本) 通過 Authenticator 類為驗證提供了本地支持。你所要做的只是繼承這個類并實現(xiàn)它的 getPasswordAuthentication 方法。這個方法取得用戶名和密碼并用它們生成一個 PasswordAuthentication 對象返回。完成之后,你還得使用 Authenticator.setDefault 方法注冊你的 Authenticator 實例?,F(xiàn)在,只要你想訪問受保護的資源,就會調(diào)用 getPasswordAuthentication。Authenticator 類管理著所有低層的詳細資料。它不受 HTTP 的限制,可以應(yīng)用于所有網(wǎng)絡(luò)連接,這不能不說是一個好消息。
示例代碼 6 中是實現(xiàn) Authenticator 的一個示例。正如你所看到的,在請求驗證的時候,getPasswordAuthentication 方法會彈出一個登錄對話框。
示例代碼 6:AuthImpl.java
import java.net.*;
import java.awt.*;
import javax.swing.*;
public class AuthImpl extends Authenticator {
protected PasswordAuthentication getPasswordAuthentication() {
JTextField username = new JTextField();
JTextField password = new JPasswordField();
JPanel panel = new JPanel(new GridLayout(2,2));
panel.add(new JLabel("User Name"));
panel.add(username);
panel.add(new JLabel("Password") );
panel.add(password);
int option
= JOptionPane.showConfirmDialog(null,
new Object[] {
"Site: "+getRequestingHost(),
"Realm: "+getRequestingPrompt(),
panel},
"Enter Network Password",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
if ( option == JOptionPane.OK_OPTION ) {
String user = username.getText();
char pass[] = password.getText().toCharArray();
return new PasswordAuthentication(user, pass);
} else {
return null;
}
}
}
示例代碼 7 用來做測試的代碼。我做的第一件事情就是用 Authenticator.setDefault 讓我的 Authenticator 實例開始運行。我不必去解析任何服務(wù)器返回的信息以檢查是否需要驗證,因為 Authenticator 類非常聰明,它知道是否需要驗證。
示例代碼 7:BasicReader.java
import java.io.*;
import java.net.*;
public class BasicReader {
public static void main(String argv[]) throws Exception {
Authenticator.setDefault(new AuthImpl());
if (argv.length != 1) {
System.err.println("Usage: java BasicReader <site>");
System.exit(1);
}
URL url = new URL(argv[[#333333]0[/#]]);
URLConnection connection = url.openConnection();
BufferedReader in
= new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuffer sb = new StringBuffer();
while ((line = in.readLine()) != null) {
sb.append(line);
}
in.close();
System.out.println(sb.toString());
System.exit(0);
}
}
運行下面的命令,用 Tomcat 來進行測試:
prompt> java BasicReader http://localhost:8080/learn/chapter
如果你進入的站點需要驗證 (它也這樣做了),那會像圖 7 那樣的對話框就會顯示出來。
圖 7:Java 處理 HTTP 驗證
一旦你輸入了正確的用戶名和密碼,服務(wù)器就會允許你訪問。BasicReader 會讀取被請求頁面的 HTML 內(nèi)容并顯示在你的控制臺窗口。
特別注意:在 Tomcat 4.0 中使用摘要驗證時你可能會遇到問題,這是由 J2SE 1.4.0 和 J2SE 1.4.1 的一個 BUG 引起的。不過這個問題已經(jīng)在 J2SE 1.4.2 中解決了。詳情情看這里。
總結(jié)
這篇文章是一篇教程,它介紹了 java.net 包的高層 API。這些 API 使你可以快速簡捷地建立有用的網(wǎng)絡(luò)應(yīng)用程序,如 StockReader。這里也討論了 HTTP 驗證,并用實例演示了如何使用你自己的驗證方案。URL 和 URLConnection 還有一些優(yōu)勢沒有在文中提到,它們包括:自動重定向、自動管理保持的連接等。不過現(xiàn)在你已經(jīng)從文中獲得基礎(chǔ)知識,可以自己解決這些問題了。