代碼中通過代理訪問外部資源
目前網路上最流行的協議就是HTTP協議。HTTP協議有許多優點,例如它能夠穿越防火牆。同時HTTP也是很多其他協議的基礎,例如SOAP協議就是建立在HTTP協議之上的。
Java通過兩種API對HTTP提供支援,一種是servlet API,它覆蓋了伺服器端的編程問題;另一種是java.net包,它通過HttpURLConnection類在用戶端提供了對HTTP協議的支援。但是我在使用servlet API的時候曾經遇到過一些問題。本文將介紹我曾遇到過的一些問題和相應的解決辦法。
基礎知識
通常在商業網路中,一個終端通過Proxy 伺服器同互連網串連起來,Proxy 伺服器負責監視網路流量和執行安全規則。在java.net API中,軟體可以通過兩個屬性來支援Proxy 伺服器,它們分別是http.proxyHost和http.proxyPort。它們必須被設定為相應的Proxy 伺服器和連接埠,下面的代碼展示了如何設定這兩個屬性:
String url = "http://www.digitalcq.com/",
proxy = "proxy.digitalcq.com",
port = "8080";
URL server = new URL(url);
Properties systemProperties = System.getProperties();
systemProperties.setProperty("http.proxyHost",proxy);
systemProperties.setProperty("http.proxyPort",port);
HttpURLConnection connection = (
HttpURLConnection)server.openConnection();
connection.connect();
InputStream in = connection.getInputStream();
readResponse(in);
在上面的程式中,你需要根據實際情況設定Proxy 伺服器和連接埠。如果你不知道該如何設定的話,可以詢問你們公司的網路系統管理員。
使用驗證
通常公司會要求員工在串連到互連網之前登入到Proxy 伺服器。通過登入這種機制使公司可以更好的監控員工對互連網的使用,例如監控員工都訪問了哪些網站。HttpURLConnection通過驗證類支援Proxy 伺服器驗證。下面是一個如何利用HttpURLConnection類進行驗證的例子。首先需要實現一個驗證者:
public class SimpleAuthenticator
extends Authenticator
{
private String username,
password;
public SimpleAuthenticator(String username,String password)
{
this.username = username;
this.password = password;
}
protected PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication(
username,password.toCharArray());
}
}
然後,通過Authenticator.setDefault()方法註冊驗證者:
String url = "http://www.digitalcq.com/",
proxy = "proxy.digitalcq.com",
port = "8080",
username = "usr",
password = "pwd";
Authenticator.setDefault(new SimpleAuthenticator(
username,password));
URL server = new URL(url);
Properties systemProperties = System.getProperties();
systemProperties.setProperty("http.proxyHost",proxy);
systemProperties.setProperty("http.proxyPort",port);
HttpURLConnection connection = (
HttpURLConnection)server.openConnection();
connection.connect();
InputStream in = connection.getInputStream();
readResponse(in);
問題
上面介紹的都是HttpURLConnection類能夠正常工作的情況。但是我在實際情況中遇到了一些網路,在這些網路中,HttpURLConnection類無法正常工作。最後我發現關鍵的問題在於使用了不正確的DNS配置。在實際情況中,HttpURLConnection類總是先嘗試利用DNS伺服器來解析位址名稱。通常情況下,這種嘗試會失敗,而Proxy 伺服器會將串連重新定向。但是某些DNS伺服器會返回一些不正確的響應,從而導致程式拋出UnknownHostException異常。作為一個程式員,系統不會為了你的程式更改DNS伺服器的配置,因此你需要找出解決這個問題的方法。我的方案是通過自己實現HTTP協議來解決這個問題。例如一個GET命令可以用下面的代碼來實現:
String url = "http://www.digitalcq.com/",
proxy = "proxy.digitalcq.com",
port = "8080",
authentication = "usr:pwd";
URL server = new URL(url);
Socket socket = new Socket(proxy,port);
Writer writer = new OutputStreamWriter(socket.getOutputStream(),
"US-ASCII");
writer.write("GET " + server.toExternalForm() + " HTTP/1.0/r/n");
writer.write("Host: " + server.getHost() + "/r/n");
writer.write("Proxy-Authorization: Basic "
+ new sun.misc.BASE64Encoder().encode(
authentication.getBytes())
+ "/r/n/r/n");
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(),"US-ASCII"));
String line = reader.readLine();
if(line != null && line.startsWith("HTTP/"))
{
int sp = line.indexOf(' ');
String status = line.substring(sp + 1,sp + 4);
if(status.equals("200"))
{
while(line.length() != 0)
line = reader.readLine();
readResponse(reader);
}
else
throw new FileNotFoundException("Host reports error " +
status);
}
else
throw new IOException("Bad protocol");
reader.close();
writer.close();
socket.close();
在上面的代碼中,大家可以注意到Proxy 伺服器的使用者名稱和密碼的格式是:
username:password,
並且程式對它們基於Base 64進行了編碼。如果需要參考HTTP協議,可以到http://www.ietf.org/rfc/rfc2616.txt。