diff --git a/pom.xml b/pom.xml index d7924adb9d..4147bdcb86 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ com.squareup.okhttp3 okhttp - 3.12.13 + 4.12.0 com.google.code.gson @@ -27,13 +27,18 @@ com.squareup.okhttp3 logging-interceptor - 3.12.13 + 4.12.0 org.apache.commons commons-configuration2 2.12.0 + + javax.xml.bind + jaxb-api + 2.3.0 + junit junit @@ -63,8 +68,8 @@ maven-compiler-plugin 2.3.2 - 1.7 - 1.7 + 1.8 + 1.8 UTF-8 diff --git a/src/main/java/com/tencentcloudapi/common/AbstractClient.java b/src/main/java/com/tencentcloudapi/common/AbstractClient.java index 6efabaceb0..6531e70ee9 100644 --- a/src/main/java/com/tencentcloudapi/common/AbstractClient.java +++ b/src/main/java/com/tencentcloudapi/common/AbstractClient.java @@ -32,6 +32,8 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -43,6 +45,7 @@ import java.net.Proxy; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.security.KeyStore; import java.security.SecureRandom; import java.sql.Date; import java.text.SimpleDateFormat; @@ -420,11 +423,25 @@ private void trySetSSLSocketFactory(HttpConnection conn) { SSLSocketFactory sslSocketFactory = this.profile.getHttpProfile().getSslSocketFactory(); X509TrustManager trustManager = this.profile.getHttpProfile().getX509TrustManager(); if (sslSocketFactory != null) { - if (trustManager != null) { - this.httpConnection.setSSLSocketFactory(sslSocketFactory, trustManager); - } else { - this.httpConnection.setSSLSocketFactory(sslSocketFactory); + if (trustManager == null) { + // okhttp 4.x requires an explicit X509TrustManager alongside SSLSocketFactory. + // Fall back to the JVM default TrustManager so existing callers that only set + // SSLSocketFactory continue to work without requiring API changes. + try { + TrustManagerFactory tmf = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + trustManager = (X509TrustManager) tm; + break; + } + } + } catch (Exception e) { + throw new RuntimeException("Failed to obtain default X509TrustManager for okhttp", e); + } } + this.httpConnection.setSSLSocketFactory(sslSocketFactory, trustManager); } } diff --git a/src/main/java/com/tencentcloudapi/common/http/HttpConnection.java b/src/main/java/com/tencentcloudapi/common/http/HttpConnection.java index f837274e79..81bb6a68b4 100644 --- a/src/main/java/com/tencentcloudapi/common/http/HttpConnection.java +++ b/src/main/java/com/tencentcloudapi/common/http/HttpConnection.java @@ -56,11 +56,6 @@ public void setProxyAuthenticator(Authenticator authenticator) { this.client = this.client.newBuilder().proxyAuthenticator(authenticator).build(); } - @Deprecated - public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { - this.client = this.client.newBuilder().sslSocketFactory(sslSocketFactory).build(); - } - public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) { this.client = this.client.newBuilder().sslSocketFactory(sslSocketFactory, trustManager).build(); } @@ -99,7 +94,7 @@ public Response postRequest(String url, String body) throws TencentCloudSDKExcep MediaType contentType = MediaType.parse("application/x-www-form-urlencoded"); Request request = null; try { - request = new Request.Builder().url(url).post(RequestBody.create(contentType, body)).build(); + request = new Request.Builder().url(url).post(RequestBody.create(body, contentType)).build(); } catch (IllegalArgumentException e) { throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage()); } @@ -115,7 +110,7 @@ public Response postRequest(String url, String body, Headers headers) request = new Request.Builder() .url(url) - .post(RequestBody.create(contentType, body)) + .post(RequestBody.create(body, contentType)) .headers(headers) .build(); } catch (IllegalArgumentException e) { @@ -133,7 +128,7 @@ public Response postRequest(String url, byte[] body, Headers headers) request = new Request.Builder() .url(url) - .post(RequestBody.create(contentType, body)) + .post(RequestBody.create(body, contentType)) .headers(headers) .build(); } catch (IllegalArgumentException e) { diff --git a/src/test/java/com/tencentcloudapi/integration/commonclient/OkhttpUpgradeTest.java b/src/test/java/com/tencentcloudapi/integration/commonclient/OkhttpUpgradeTest.java new file mode 100644 index 0000000000..c310a8850e --- /dev/null +++ b/src/test/java/com/tencentcloudapi/integration/commonclient/OkhttpUpgradeTest.java @@ -0,0 +1,161 @@ +package com.tencentcloudapi.integration.commonclient; + +import com.tencentcloudapi.common.CommonClient; +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import okhttp3.OkHttp; +import org.junit.Assert; +import org.junit.Test; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; + +/** + * 验证 okhttp 升级到 4.x 后核心功能正常 + * 运行前请设置环境变量: + * TENCENTCLOUD_SECRET_ID + * TENCENTCLOUD_SECRET_KEY + */ +public class OkhttpUpgradeTest { + + private Credential getCredential() { + return new Credential( + System.getenv("TENCENTCLOUD_SECRET_ID"), + System.getenv("TENCENTCLOUD_SECRET_KEY")); + } + + /** + * 验证 okhttp 版本已升级到 4.x + */ + @Test + public void testOkhttpVersion() { + String version = OkHttp.VERSION; + System.out.println("OkHttp version: " + version); + Assert.assertTrue("期望 okhttp 版本为 4.x,实际为: " + version, + version.startsWith("4.")); + } + + /** + * 基础 API 调用:DescribeInstances(验证 RequestBody.create 参数顺序修改后正常) + */ + @Test + public void testBasicApiCall() { + Credential cred = getCredential(); + CommonClient client = new CommonClient("cvm", "2017-03-12", cred, "ap-guangzhou"); + try { + String resp = client.call("DescribeInstances", + "{\"Filters\":[{\"Name\":\"zone\",\"Values\":[\"ap-guangzhou-1\"]}]}"); + System.out.println("DescribeInstances 响应: " + resp.substring(0, Math.min(200, resp.length()))); + Assert.assertTrue("响应中应包含 RequestId", resp.contains("RequestId")); + } catch (TencentCloudSDKException e) { + Assert.fail("API 调用失败: " + e.getMessage()); + } + } + + /** + * 验证 HTTPS 正常(默认 SSLSocketFactory,okhttp 4.x 默认连接) + */ + @Test + public void testHttpsCall() { + Credential cred = getCredential(); + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setProtocol("https://"); + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + + CommonClient client = new CommonClient("cvm", "2017-03-12", cred, "ap-guangzhou", clientProfile); + try { + String resp = client.call("DescribeInstances", "{}"); + System.out.println("HTTPS 调用响应: " + resp.substring(0, Math.min(200, resp.length()))); + Assert.assertTrue("HTTPS 调用应包含 RequestId", resp.contains("RequestId")); + } catch (TencentCloudSDKException e) { + Assert.fail("HTTPS 调用失败: " + e.getMessage()); + } + } + + /** + * 验证仅设置 SSLSocketFactory(不传 TrustManager)时 okhttp 4.x 兼容性 + * AbstractClient 改动:自动获取默认 X509TrustManager,避免报错 + */ + @Test + public void testSSLSocketFactoryOnlyCompat() throws NoSuchAlgorithmException, KeyManagementException { + Credential cred = getCredential(); + + // 只创建 SSLSocketFactory,不显式传 TrustManager(模拟旧调用方式) + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, null); // 使用 JVM 默认 TrustManager + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setSslSocketFactory(sslSocketFactory); + // 注意:不调用 setX509TrustManager,测试 AbstractClient 的自动兜底逻辑 + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + + CommonClient client = new CommonClient("cvm", "2017-03-12", cred, "ap-guangzhou", clientProfile); + try { + String resp = client.call("DescribeInstances", "{}"); + System.out.println("SSLSocketFactory-only 调用响应: " + resp.substring(0, Math.min(200, resp.length()))); + Assert.assertTrue("应包含 RequestId", resp.contains("RequestId")); + } catch (TencentCloudSDKException e) { + Assert.fail("SSLSocketFactory only 调用失败(兼容性回归): " + e.getMessage()); + } + } + + /** + * 验证自定义 SSLSocketFactory + TrustManager(信任所有证书)调用正常 + */ + @Test + public void testCustomSSLSocketFactory() throws Exception { + Credential cred = getCredential(); + + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String authType) {} + public void checkServerTrusted(X509Certificate[] chain, String authType) {} + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + } + }; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setSslSocketFactory(sslSocketFactory); + httpProfile.setX509TrustManager((X509TrustManager) trustAllCerts[0]); + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + + CommonClient client = new CommonClient("cvm", "2017-03-12", cred, "ap-guangzhou", clientProfile); + try { + String resp = client.call("DescribeInstances", "{}"); + System.out.println("自定义 SSL 调用响应: " + resp.substring(0, Math.min(200, resp.length()))); + Assert.assertTrue("应包含 RequestId", resp.contains("RequestId")); + } catch (TencentCloudSDKException e) { + Assert.fail("自定义 SSL 调用失败: " + e.getMessage()); + } + } + + /** + * 验证 POST JSON 请求(覆盖 RequestBody.create(body, contentType) 调用路径) + */ + @Test + public void testPostJsonRequest() { + Credential cred = getCredential(); + CommonClient client = new CommonClient("sts", "2018-08-13", cred, "ap-guangzhou"); + try { + String resp = client.call("GetCallerIdentity", "{}"); + System.out.println("GetCallerIdentity 响应: " + resp.substring(0, Math.min(200, resp.length()))); + Assert.assertTrue("应包含 RequestId", resp.contains("RequestId")); + } catch (TencentCloudSDKException e) { + Assert.fail("POST JSON 请求失败: " + e.getMessage()); + } + } +}