需求描述
在正常的項目開發需求中,連接遠程服務器的場景一般有二:
1 自家實現的http服務器,api接口都已經約定好;
2 開發平台服務,通常如新浪、百度雲等平台提供的restful接口;
以上的兩種場景通過原生的URLConnection或是apache提供的httpclient工具包都可以方便的實現調用。
然而,第三種場景是需要連接國外的開放服務,如google、twitter、tumblr等開放API接口。
在偉大的gfw關懷下,我們被告知不要隨便和陌生人說話...
好吧,接下來讓我們開始實現基於proxy的穿越吧!
准備工作
1 http代理服務器
建議花點銀子買個穩定的VPN,帶http代理的那種。
2 外網訪問測試
可以用chrome switchyOmega插件測試一把,不行直接設置IE系統代理
准備完畢,可以開始開發了
設計分析
代理連接實現的關鍵步驟:
一、設置代理服務器地址端口
方式一:Java支持以System.setProperty的方式設置http代理及端口,如下:
System.setProperty("http.proxySet", "true");
System.setProperty("http.proxyHost", proxyHost);
System.setProperty("http.proxyPort", "" + proxyPort);
// 針對https也開啟代理
System.setProperty("https.proxyHost", proxyHost);
System.setProperty("https.proxyPort", "" + proxyPort);
方式二:使用Proxy對象,在建立連接時注入到URLConnection即可:
// 初始化proxy對象 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); // 創建連接 URL u = new URL(url); URLConnection conn = u.openConnection(proxy);
關於兩種方式的比較
第一種方式更值得推薦,當你采用基於URLConnection封裝實現的類庫時,采用setProperty的方式則不需要動里面的代碼,綠色輕便。
二、實現用戶密碼校驗
方式一:將校驗信息寫入http頭,將用戶名密碼進行base64編碼之后設置Proxy-Authorization頭:
String headerKey = "Proxy-Authorization"; String encoded = new String(Base64.encodeBase64((new String(proxyUser + ":" + proxyPass).getBytes()))); String headerValue = "Basic " + encoded; conn.setRequestProperty(headerKey, headerValue);
不少資料會推薦這樣的方式,但經過測試,
該方式在https的需求場景下無法正常工作!
方式二:實現Authenticator接口,並注入為全局驗證器:
public static class MyAuthenticator extends Authenticator {
String userName;
String password;
public MyAuthenticator (String userName, String password) {
this.userName = userName;
this.password = password;
}
/**
* 當需要使用密碼校驗時自動觸發
*/
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password.toCharArray());
}
}
在執行連接之前注入校驗實例:
MyAuthenticator auth = new MyAuthenticator(proxyUser, proxyPass); Authenticator.setDefault(auth);
實例代碼
入口類
/**
* 網絡代理測試
*
* <pre>
* 設置代理主機及端口:系統變量(https 需同步設置)
* 設置代理驗證方式:全局代理對象
*
*
* https鏈接錯誤:
* Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required"
* 使用全局代理驗證解決
*
* </pre>
*
* @author tzz
* @createDate 2015年7月23日
*
*/
public class ProxyTest {
private static String proxyHost = "xxx.xxxxx.com";
private static int proxyPort = 8080;
private static String proxyUser = "user";
private static String proxyPass = "pass";
public static void main(String[] args) {
String url = "https://www.google.com/";
String content = doProxy(url);
System.out.println("Result :===================\n " + content);
}
/**
* 通過系統變量方式實現代理
*
* @param url
* @return
*/
public static String doProxy(String url) {
// 設置系統變量
System.setProperty("http.proxySet", "true");
System.setProperty("http.proxyHost", proxyHost);
System.setProperty("http.proxyPort", "" + proxyPort);
// 針對https也開啟代理
System.setProperty("https.proxyHost", proxyHost);
System.setProperty("https.proxyPort", "" + proxyPort);
// 設置默認校驗器
setDefaultAuthentication();
//開始請求
try {
URL u = new URL(url);
URLConnection conn = u.openConnection();
HttpsURLConnection httpsCon = (HttpsURLConnection) conn;
httpsCon.setFollowRedirects(true);
String encoding = conn.getContentEncoding();
if (StringUtils.isEmpty(encoding)) {
encoding = "UTF-8";
}
InputStream is = conn.getInputStream();
String content = IOUtils.toString(is, encoding);
return content;
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
/**
* 設置全局校驗器對象
*/
public static void setDefaultAuthentication() {
BasicAuthenticator auth = new BasicAuthenticator(proxyUser, proxyPass);
Authenticator.setDefault(auth);
}
}
校驗器
/**
* 實現sun.net的代理驗證
*
* @author tzz
* @createDate 2015年7月23日
*
*/
public static class BasicAuthenticator extends Authenticator {
String userName;
String password;
public BasicAuthenticator(String userName, String password) {
this.userName = userName;
this.password = password;
}
/**
* Called when password authorization is needed. Subclasses should override the default implementation, which returns null.
*
* @return The PasswordAuthentication collected from the user, or null if none is provided.
*/
@Override
protected PasswordAuthentication getPasswordAuthentication() {
//System.out.println("DEBUG === use global authentication of password");
return new PasswordAuthentication(userName, password.toCharArray());
}
}
常見問題
連接時異常
Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required"
通常是代理服務器未能讀取到驗證信息所致,請檢查目標url是否為https連接以及全局的Authenticator類是否正確設置。
