解决Eclipse中HTTPS URL请求出现Connection Reset问题的方法
我在Eclipse中编写Java代码请求一个HTTP URL时,能正常收到301重定向响应,但当代码尝试重定向到对应的HTTPS URL时,触发了Connection Reset错误。原HTTP请求可以正常返回301状态码,问题只出在跳转后的HTTPS连接阶段。以下是我的代码:
try { String url = "http://api.demo.globalgatewaye4.firstdata.com/transaction/search?start_date=2018-01-07&end_date=2018-01-09"; URL obj = new URL(url); HttpURLConnection conn = (HttpURLConnection) obj.openConnection(); conn.setReadTimeout(5000); conn.addRequestProperty("Authorization", "Basic *****************"); System.out.println("Request URL ... " + url); boolean redirect = false; // normally, 3xx is redirect int status = conn.getResponseCode(); if (status != HttpURLConnection.HTTP_OK) { if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) redirect = true; } System.out.println("Response Code ... " + status); if (redirect) { // get redirect url from "location" header field String newUrl = conn.getHeaderField("Location"); // get the cookie if need, for login String cookies = conn.getHeaderField("Set-Cookie"); // open the new connnection again conn = (HttpURLConnection) new URL(newUrl).openConnection(); conn.setRequestProperty("Cookie", cookies); conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8"); conn.addRequestProperty("User-Agent", "Mozilla"); conn.addRequestProperty("Referer", "google.com"); System.out.println("Redirect to URL : " + newUrl); } BufferedReader in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String inputLine; StringBuffer html = new StringBuffer(); while ((inputLine = in.readLine()) != null) { html.append(inputLine); } in.close(); System.out.println("URL Content... \n" + html.toString()); System.out.println("Done"); } catch (Exception e) { e.printStackTrace(); }
请问如何在Eclipse中解决这个问题,或者有没有通用的解决方法?
可能的解决方法
1. 处理HTTPS证书信任问题
Connection Reset很可能是因为Java默认不信任目标HTTPS站点的SSL证书。你可以创建一个信任所有证书的SSL上下文(仅用于测试环境,生产环境请谨慎使用),然后应用到HttpsURLConnection中:
// 先添加这个工具方法 private static void disableSSLCertificateChecking() throws NoSuchAlgorithmException, KeyManagementException { TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HostnameVerifier allHostsValid = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); }
在创建连接之前调用这个方法,比如在URL obj = new URL(url);之前添加disableSSLCertificateChecking();。
2. 完善重定向时的请求头传递
原代码在重定向时没有传递关键的Authorization头,而且添加了可能不必要的Referer(目标站点可能验证Referer合法性)。修改重定向部分的代码:
if (redirect) { String newUrl = conn.getHeaderField("Location"); String cookies = conn.getHeaderField("Set-Cookie"); // 打开新连接 URL newUrlObj = new URL(newUrl); conn = (HttpURLConnection) newUrlObj.openConnection(); // 传递原请求的Authorization头 conn.addRequestProperty("Authorization", "Basic *****************"); if (cookies != null) { conn.setRequestProperty("Cookie", cookies); } conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8"); conn.addRequestProperty("User-Agent", "Mozilla/5.0"); // 使用标准的User-Agent // 移除不必要的Referer,或者设置为原HTTP URL // conn.addRequestProperty("Referer", url); conn.setReadTimeout(5000); conn.setConnectTimeout(5000); // 添加连接超时 System.out.println("Redirect to URL : " + newUrl); }
3. 调整连接参数
原代码只设置了readTimeout,建议添加connectTimeout,同时设置followRedirects为false(因为我们手动处理重定向,避免自动跳转带来的问题):
在第一次创建连接时添加:
conn.setConnectTimeout(5000); conn.setInstanceFollowRedirects(false); // 禁用自动重定向,手动处理
4. 使用Apache HttpClient替代HttpURLConnection
HttpURLConnection对HTTPS和重定向的处理比较繁琐,Apache HttpClient会自动处理很多细节,包括证书(可配置)和重定向。示例代码(HttpClient 4.x):
CloseableHttpClient httpClient = HttpClients.custom() .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) .setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, (chain, authType) -> true).build()) .build(); try { HttpGet request = new HttpGet("http://api.demo.globalgatewaye4.firstdata.com/transaction/search?start_date=2018-01-07&end_date=2018-01-09"); request.addHeader("Authorization", "Basic *****************"); request.addHeader("User-Agent", "Mozilla/5.0"); CloseableHttpResponse response = httpClient.execute(request); try { System.out.println("Response Code: " + response.getStatusLine().getStatusCode()); HttpEntity entity = response.getEntity(); if (entity != null) { String result = EntityUtils.toString(entity); System.out.println("URL Content... \n" + result); } } finally { response.close(); } } catch (Exception e) { e.printStackTrace(); } finally { httpClient.close(); }
注意:生产环境中不要盲目信任所有证书,应该导入目标站点的合法证书到Java的信任库中。
内容的提问来源于stack exchange,提问作者Skadoosh

