有這樣一個場景,出于安全的考慮,某些操作敏感資料的用戶端必須通過 VPN 通路伺服器端。這種用戶端我們姑且稱之為代理通路。通路路由示意圖:
HTTPS Client <------- Encrypted CONNECT Requests -------> HTTPS Proxy <------- Encrypted CONNECT Requests -------> HTTPS End-Site |
代理位址及端口号作為 Property 參數注入 jvm 程序:https.proxyHost、https.proxyPort。
而大部分用戶端不需要 VPN 通路伺服器端,隻需要通過 HTTPS 直接通路即可。這種用戶端我們姑且稱之為直連通路。通路路由示意圖:
HTTPS Client <------- Encrypted CONNECT Requests -------> HTTPS End-Site |
注意:
- 代理配置是以 global 方式提供,也就是說伺服器是同一個,所有被分發的用戶端啟動的時候都有上述 Property 參數
- 這種 VPN 代理跟 LB 代理不同之處在于 proxy 不需要配置 SSL 證書,也就是說 VPN 隻負責服務的監聽和轉發
Solution 1:使用 JVM 原生态 java.net 和 javax.net.ssl 工具包
示意代碼:
= new URL(urlPath);
URLConnection connection = localURL.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
if (connection instanceof HttpsURLConnection) {
TrustManager[] tm = {ignoreCertificationTrustManger};
try {
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
SSLSocketFactory ssf = sslContext.getSocketFactory();
((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(ssf);
((HttpsURLConnection) httpURLConnection).setHostnameVerifier(ignoreHostnameVerifier);
} catch (NoSuchAlgorithmException e1) {
logger.logError(e1.getMessage(), e1);
} catch (NoSuchProviderException e1) {
logger.logError(e1.getMessage(), e1);
} catch (KeyManagementException e1) {
logger.logError(e1.getMessage(), e1);
}
}
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Content-Type", "application/octet-stream");
httpURLConnection.setRequestProperty("Accept-Encoding", "chunck");
httpURLConnection.setConnectTimeout(3000);
outputStream = httpURLConnection.getOutputStream();
優點:
- 能自動識别https.proxyHost、https.proxyPort等 Property
- 能自動識别直連、代理通路環境,然後決定是否用程序内 Property 挂代理通路
缺點:
- 單次通路性能差于 Apache HttpClient 工具包,性能減一
- 連接配接池化管理差,性能再減一
總之,玩玩或交流學習可以,用于生産環境太兒戲。
Solution 2:使用 Apache HttpClient 工具包的 RequestConfig
示意代碼:
= new HttpHost("defonds.net", 443, "https");
HttpHost proxy = new HttpHost("191.168.1.303", 7443, "https");
RequestConfig config = RequestConfig.custom()
.setProxy(proxy)
.build();
HttpGet request = new HttpGet("/");
request.setConfig(config);
CloseableHttpResponse response = httpclient.execute(target, request);
參考自 Apache 官方示例代碼:https://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientExecuteProxy.java。
直連環境可以,代理環境歇菜。有類似于以下的 SSL 握手問題:
{tls}->https://191.168.1.303:7443->https://defonds.net:443 Connection reset java.net.SocketException: Connection reset at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367) |
Solution 3:使用 Apache HttpClient 工具包的 HttpRoutePlanner
示意代碼:
HttpRoutePlanner routePlanner = new HttpRoutePlanner() {
public HttpRoute determineRoute(
HttpHost target,
HttpRequest request,
HttpContext context) throws HttpException {
return new HttpRoute(target, null, new HttpHost("someproxy", 8080),
"https".equalsIgnoreCase(target.getSchemeName()));
}
};
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();
}
}
參考自 Apache 官方示例代碼:http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/connmgmt.html#d5e485。
上述代碼在代理環境可以,直連環境還需自行适配。
優點:
- 底層做過傳輸優化,單次性能高于原生态工具包
- 池化管理高效高性能
- 不能自動識别https.proxyHost、https.proxyPort等 Property
- 代理 / 直連需顯式适配
參考資料
- HttpRoutePlanner - How does it work with an HTTPS Proxy
- HttpClient proxy configuration