Skip to content

Commit 868abd0

Browse files
authored
新增 apache http builder,支持 apache http client (#315)
* 新增 apacheHttpClientAdapter * 新增apacheHttpClientBuilder * 修改注释 * 格式化 * 格式化 * fix issue * fix issue * fix issue * 修改注释 * readme 新增 apacheHttpClient 相关文档
1 parent c12a7d8 commit 868abd0

File tree

9 files changed

+2009
-1
lines changed

9 files changed

+2009
-1
lines changed

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,54 @@ JsapiService service = new JsapiService.Builder().httpclient(httpClient).build()
495495

496496
我们提供基于 [腾讯 Kona 国密套件](https://github.com/Tencent/TencentKonaSMSuite) 的国密扩展。文档请参考 [shangmi/README.md](shangmi/README.md)
497497

498+
## 使用 ApacheHttpClient 发送 HTTP请求
499+
500+
SDK 支持使用 ApacheHttpClient 发送 HTTP 请求。建议使用 [ApacheHttpClientBuilder](https://github.com/wechatpay-apiv3/wechatpay-java/tree/main/core/src/main/java/com/wechat/pay/java/core/http/ApacheHttpClientBuilder.java) 创建 [ApacheHttpClientAdapter]https://github.com/wechatpay-apiv3/wechatpay-java/tree/main/core/src/main/java/com/wechat/pay/java/core/http/apache/ApacheHttpClientAdapter.java) 来发送 HTTP 请求,会自动生成签名和验证签名。
501+
502+
### 使用示例
503+
504+
发送请求步骤如下:
505+
506+
1. 初始化 `ApacheHttpClientAdapter`,建议使用 `ApacheHttpClientBuilder` 构建
507+
2. 构建请求 `HttpRequest`
508+
3. 调用 `httpClient.execute` 或者 `httpClient.get` 等方法来发送 HTTP 请求。`httpClient.execute` 支持发送 GET、PUT、POST、PATCH、DELETE 请求,也可以调用指定的 HTTP 方法发送请求。
509+
510+
[ApacheHttpClientAdapterTest](https://github.com/wechatpay-apiv3/wechatpay-java/blob/main/core/src/test/java/com/wechat/pay/java/core/http/ApacheHttpClientAdapterTest.java) 中演示了如何构造和发送 HTTP 请求。以下是简单的代码使用示例:
511+
512+
```java
513+
// 初始化商户配置
514+
Config config =
515+
new RSAAutoCertificateConfig.Builder()
516+
.merchantId(merchantId) // 商户号
517+
.privateKeyFromPath(privateKeyPath) // 商户API私钥路径
518+
.merchantSerialNumber(merchantSerialNumber) // 商户证书序列号
519+
.apiV3Key(apiV3Key) // 商户APIV3密钥
520+
.build();
521+
522+
// 方法一:使用默认创建的 ApacheHttpClient
523+
HttpClient httpClient = new ApacheHttpClientBuilder().config(config).build();
524+
// 方法二:使用业务已有的 customApacheHttpClient
525+
HttpClient httpClient = new ApacheHttpClientBuilder().config(config).apacheHttpClient(customApacheHttpClient).build();
526+
527+
// 构造 HttpRequest,以 GET 为例
528+
HttpRequest httpRequest =
529+
new HttpRequest.Builder()
530+
.httpMethod(HttpMethod.GET)
531+
.url(requestPath)
532+
.headers(headers)
533+
.build();
534+
535+
// 发送请求 Response 为自定义的回包的类型
536+
HttpResponse<ExampleResponse> httpResponse = httpClient.execute(httpRequest, ExampleResponse.class);
537+
```
538+
539+
### 如何迁移
540+
541+
如果之前使用的是 [wechatpay-apache-httpclient](https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient),想要迁移到使用 SDK,可以按照以下步骤:
542+
543+
1. 修改初始化方式:可参考示例代码
544+
2. 修改收发包请求:定义回包类 XXXResponse,构造 HttpRequest,可参考示例代码
545+
498546
## 常见问题
499547

500548
### 为什么收到应答中的证书序列号和发起请求的证书序列号不一致?

core/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ jar {
1818
dependencies {
1919
implementation "com.google.code.gson:gson:${gsonVersion}"
2020
implementation "com.squareup.okhttp3:okhttp:${okhttpVersion}"
21+
implementation "org.apache.httpcomponents:httpmime:${apachehttpVersion}"
22+
implementation "org.apache.httpcomponents:httpclient:${apachehttpVersion}"
2123
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
2224

2325
testImplementation "junit:junit:${junitVersion}"
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.wechat.pay.java.core.http;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import com.wechat.pay.java.core.Config;
6+
import com.wechat.pay.java.core.auth.Credential;
7+
import com.wechat.pay.java.core.auth.Validator;
8+
import com.wechat.pay.java.core.http.apache.ApacheHttpClientAdapter;
9+
import org.apache.http.impl.client.CloseableHttpClient;
10+
import org.apache.http.impl.client.HttpClientBuilder;
11+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
12+
13+
/** 默认HttpClient构造器 */
14+
public class ApacheHttpClientBuilder implements AbstractHttpClientBuilder<ApacheHttpClientBuilder> {
15+
16+
private Credential credential;
17+
private Validator validator;
18+
19+
private CloseableHttpClient customizeApacheHttpClient;
20+
21+
static PoolingHttpClientConnectionManager apacheHttpClientConnectionManager =
22+
new PoolingHttpClientConnectionManager();
23+
24+
private CloseableHttpClient initDefaultApacheHttpClient() {
25+
return HttpClientBuilder.create()
26+
.setConnectionManager(apacheHttpClientConnectionManager)
27+
.setConnectionManagerShared(true)
28+
.build();
29+
}
30+
31+
/**
32+
* 复制工厂,复制一个当前对象
33+
*
34+
* @return 对象的副本
35+
*/
36+
@Override
37+
public ApacheHttpClientBuilder newInstance() {
38+
ApacheHttpClientBuilder result = new ApacheHttpClientBuilder();
39+
result.credential = this.credential;
40+
result.validator = this.validator;
41+
result.customizeApacheHttpClient = this.customizeApacheHttpClient;
42+
return result;
43+
}
44+
45+
/**
46+
* 设置凭据生成器
47+
*
48+
* @param credential 凭据生成器
49+
* @return apacheHttpClientBuilder
50+
*/
51+
@Override
52+
public ApacheHttpClientBuilder credential(Credential credential) {
53+
this.credential = credential;
54+
return this;
55+
}
56+
57+
/**
58+
* 设置验证器
59+
*
60+
* @param validator 验证器
61+
* @return apacheHttpClientBuilder
62+
*/
63+
@Override
64+
public ApacheHttpClientBuilder validator(Validator validator) {
65+
this.validator = validator;
66+
return this;
67+
}
68+
69+
/**
70+
* 设置 appacheHttpClient,若没有设置,则使用默认创建的 appacheHttpClient
71+
*
72+
* @param apacheHttpClient 用户自定义的apacheHttpClient
73+
* @return apacheHttpClientBuilder
74+
*/
75+
public ApacheHttpClientBuilder apacheHttpClient(CloseableHttpClient apacheHttpClient) {
76+
this.customizeApacheHttpClient = apacheHttpClient;
77+
return this;
78+
}
79+
80+
public ApacheHttpClientBuilder config(Config config) {
81+
requireNonNull(config);
82+
this.credential = config.createCredential();
83+
this.validator = config.createValidator();
84+
return this;
85+
}
86+
87+
/**
88+
* 构建默认HttpClient
89+
*
90+
* @return httpClient
91+
*/
92+
@Override
93+
public AbstractHttpClient build() {
94+
requireNonNull(credential);
95+
requireNonNull(validator);
96+
97+
CloseableHttpClient httpclient =
98+
customizeApacheHttpClient == null
99+
? initDefaultApacheHttpClient()
100+
: customizeApacheHttpClient;
101+
return new ApacheHttpClientAdapter(credential, validator, httpclient);
102+
}
103+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package com.wechat.pay.java.core.http.apache;
2+
3+
import static java.util.Objects.requireNonNull;
4+
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
5+
6+
import com.wechat.pay.java.core.auth.Credential;
7+
import com.wechat.pay.java.core.auth.Validator;
8+
import com.wechat.pay.java.core.exception.HttpException;
9+
import com.wechat.pay.java.core.exception.MalformedMessageException;
10+
import com.wechat.pay.java.core.exception.ServiceException;
11+
import com.wechat.pay.java.core.http.AbstractHttpClient;
12+
import com.wechat.pay.java.core.http.FileRequestBody;
13+
import com.wechat.pay.java.core.http.HttpRequest;
14+
import com.wechat.pay.java.core.http.JsonRequestBody;
15+
import com.wechat.pay.java.core.http.OriginalResponse;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.util.Map;
19+
import java.util.concurrent.ConcurrentHashMap;
20+
import org.apache.http.Header;
21+
import org.apache.http.HttpEntity;
22+
import org.apache.http.client.methods.CloseableHttpResponse;
23+
import org.apache.http.client.methods.HttpDelete;
24+
import org.apache.http.client.methods.HttpGet;
25+
import org.apache.http.client.methods.HttpPatch;
26+
import org.apache.http.client.methods.HttpPost;
27+
import org.apache.http.client.methods.HttpPut;
28+
import org.apache.http.entity.ContentType;
29+
import org.apache.http.entity.StringEntity;
30+
import org.apache.http.entity.mime.HttpMultipartMode;
31+
import org.apache.http.entity.mime.MultipartEntityBuilder;
32+
import org.apache.http.impl.client.CloseableHttpClient;
33+
import org.apache.http.util.EntityUtils;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
public class ApacheHttpClientAdapter extends AbstractHttpClient {
38+
39+
private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClientAdapter.class);
40+
private static final String META_NAME = "meta";
41+
private static final String FILE_NAME = "file";
42+
43+
private final CloseableHttpClient apacheHttpClient;
44+
45+
public ApacheHttpClientAdapter(
46+
Credential credential, Validator validator, CloseableHttpClient client) {
47+
super(credential, validator);
48+
this.apacheHttpClient = requireNonNull(client);
49+
}
50+
51+
@Override
52+
protected String getHttpClientInfo() {
53+
return "apachehttp/" + apacheHttpClient.getClass().getPackage().getImplementationVersion();
54+
}
55+
56+
@Override
57+
public OriginalResponse innerExecute(HttpRequest wechatPayRequest) {
58+
try {
59+
CloseableHttpResponse apacheHttpResponse =
60+
apacheHttpClient.execute(buildApacheHttpRequest(wechatPayRequest));
61+
return assembleOriginalResponse(wechatPayRequest, apacheHttpResponse);
62+
} catch (IOException e) {
63+
throw new HttpException(wechatPayRequest, e);
64+
}
65+
}
66+
67+
private org.apache.http.client.methods.HttpUriRequest buildApacheHttpRequest(
68+
HttpRequest wechatPayRequest) {
69+
String url = wechatPayRequest.getUrl().toString();
70+
org.apache.http.client.methods.HttpUriRequest apacheHttpRequest;
71+
72+
switch (wechatPayRequest.getHttpMethod().name()) {
73+
case "GET":
74+
apacheHttpRequest = new HttpGet(url);
75+
break;
76+
case "POST":
77+
apacheHttpRequest = new HttpPost(url);
78+
((HttpPost) apacheHttpRequest).setEntity(buildApacheHttpEntity(wechatPayRequest.getBody()));
79+
break;
80+
case "PUT":
81+
apacheHttpRequest = new HttpPut(url);
82+
((HttpPut) apacheHttpRequest).setEntity(buildApacheHttpEntity(wechatPayRequest.getBody()));
83+
break;
84+
case "PATCH":
85+
apacheHttpRequest = new HttpPatch(url);
86+
((HttpPatch) apacheHttpRequest)
87+
.setEntity(buildApacheHttpEntity(wechatPayRequest.getBody()));
88+
break;
89+
case "DELETE":
90+
apacheHttpRequest = new HttpDelete(url);
91+
break;
92+
default:
93+
throw new IllegalArgumentException(
94+
"Unsupported HTTP method: " + wechatPayRequest.getHttpMethod().name());
95+
}
96+
Map<String, String> headers = wechatPayRequest.getHeaders().getHeaders();
97+
headers.forEach(apacheHttpRequest::addHeader);
98+
return apacheHttpRequest;
99+
}
100+
101+
private HttpEntity buildApacheHttpEntity(
102+
com.wechat.pay.java.core.http.RequestBody wechatPayRequestBody) {
103+
// 处理空请求体的情况
104+
if (wechatPayRequestBody == null) {
105+
return new StringEntity("", "");
106+
}
107+
108+
if (wechatPayRequestBody instanceof JsonRequestBody) {
109+
return new StringEntity(
110+
((JsonRequestBody) wechatPayRequestBody).getBody(),
111+
ContentType.create(wechatPayRequestBody.getContentType()));
112+
}
113+
if (wechatPayRequestBody instanceof FileRequestBody) {
114+
FileRequestBody fileRequestBody = (FileRequestBody) wechatPayRequestBody;
115+
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
116+
entityBuilder.setMode(HttpMultipartMode.RFC6532);
117+
entityBuilder.addTextBody(META_NAME, fileRequestBody.getMeta(), APPLICATION_JSON);
118+
entityBuilder.addBinaryBody(
119+
FILE_NAME,
120+
fileRequestBody.getFile(),
121+
ContentType.create(fileRequestBody.getContentType()),
122+
fileRequestBody.getFileName());
123+
return entityBuilder.build();
124+
}
125+
logger.error(
126+
"When an http request is sent and the apache request body is constructed, the requestBody"
127+
+ " parameter"
128+
+ " type cannot be found,requestBody class name[{}]",
129+
wechatPayRequestBody.getClass().getName());
130+
return null;
131+
}
132+
133+
private OriginalResponse assembleOriginalResponse(
134+
HttpRequest wechatPayRequest, CloseableHttpResponse apacheHttpResponse) throws IOException {
135+
Map<String, String> responseHeaders = assembleResponseHeader(apacheHttpResponse);
136+
HttpEntity entity = apacheHttpResponse.getEntity();
137+
try {
138+
String responseBody = entity != null ? EntityUtils.toString(entity) : null;
139+
return new OriginalResponse.Builder()
140+
.request(wechatPayRequest)
141+
.headers(responseHeaders)
142+
.statusCode(apacheHttpResponse.getStatusLine().getStatusCode())
143+
.contentType(entity != null ? entity.getContentType().getValue() : null)
144+
.body(responseBody)
145+
.build();
146+
} catch (IOException e) {
147+
throw new MalformedMessageException(
148+
String.format(
149+
"Assemble OriginalResponse,get responseBody failed.%nHttpRequest[%s]",
150+
wechatPayRequest));
151+
}
152+
}
153+
154+
private Map<String, String> assembleResponseHeader(CloseableHttpResponse apacheHttpResponse) {
155+
Map<String, String> responseHeaders = new ConcurrentHashMap<>();
156+
Header[] headers = apacheHttpResponse.getAllHeaders();
157+
for (Header header : headers) {
158+
responseHeaders.put(header.getName(), header.getValue());
159+
}
160+
return responseHeaders;
161+
}
162+
163+
@Override
164+
protected InputStream innerDownload(HttpRequest httpRequest) {
165+
try {
166+
CloseableHttpResponse apacheHttpResponse =
167+
apacheHttpClient.execute(buildApacheHttpRequest(httpRequest));
168+
if (isInvalidHttpCode(apacheHttpResponse.getStatusLine().getStatusCode())) {
169+
throw new ServiceException(
170+
httpRequest, apacheHttpResponse.getStatusLine().getStatusCode(), "");
171+
}
172+
InputStream responseBodyStream = null;
173+
if (apacheHttpResponse.getEntity() != null) {
174+
responseBodyStream = apacheHttpResponse.getEntity().getContent();
175+
}
176+
return responseBodyStream;
177+
} catch (IOException e) {
178+
throw new HttpException(httpRequest, e);
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)