简介
HttpClientBuilder是阿帕奇的一个http客户端构建类,通过继承构建类可以添加验签,实现统一发送请求的时候携带验签。
自定义的MyHttpClientBuilder往责任链中添加新的一环
实践
HttpClientBuilder
HttpClientBuilder有很多public方法,但是都是final定义,子类无法重写。
关键改造在于decorateProtocolExec
@NoArgsConstructor
public class MyHttpClientBuilder extends HttpClientBuilder {
private String name;
private String password;
public static MyHttpClientBuilder create() {
return new MyHttpClientBuilder();
}
public MyHttpClientBuilder setName(String name) {
this.name = name;
return this;
}
public MyHttpClientBuilder setPassword(String password) {
this.password = password;
return this;
}
/**
* 创建主链
*/
@Override
protected ClientExecChain createMainExec(HttpRequestExecutor requestExec, HttpClientConnectionManager connManager, ConnectionReuseStrategy reuseStrategy, ConnectionKeepAliveStrategy keepAliveStrategy, HttpProcessor proxyHttpProcessor, AuthenticationStrategy targetAuthStrategy, AuthenticationStrategy proxyAuthStrategy, UserTokenHandler userTokenHandler) {
return super.createMainExec(requestExec, connManager, reuseStrategy, keepAliveStrategy, proxyHttpProcessor, targetAuthStrategy, proxyAuthStrategy, userTokenHandler);
}
/**
* 装饰主链
*/
@Override
protected ClientExecChain decorateMainExec(ClientExecChain mainExec) {
return super.decorateMainExec(mainExec);
}
/**
* 装饰的
*/
@Override
protected ClientExecChain decorateProtocolExec(ClientExecChain protocolExec) {
// return super.decorateProtocolExec(protocolExec);
return new MyClientExecChain(name, password, protocolExec);
}
/**
* 针对多客户端管理添加http客户端
*/
@Override
protected void addCloseable(Closeable closeable) {
super.addCloseable(closeable);
}
/**
* 初始化InternalHttpClient
*/
@Override
public CloseableHttpClient build() {
return super.build();
}
}
自定义责任链
定义责任链添加请求头token
public class MyClientExecChain implements ClientExecChain {
private final String name;
private final String password;
private final ClientExecChain mainExec;
public MyClientExecChain(String name,String password,ClientExecChain mainExec){
this.name = name;
this.password = password;
this.mainExec = mainExec;
}
/**
* 责任链需要规定自己生效的条件
* */
@Override
public CloseableHttpResponse execute(HttpRoute route,
HttpRequestWrapper request,
HttpClientContext clientContext,
HttpExecutionAware execAware) throws IOException, HttpException {
// 控制访问的域名
if ( Objects.nonNull(request.getURI().getHost()) && request.getURI().getHost().endsWith("127.0.0.1")) {
return executeMy(route, request, clientContext, execAware);
} else {
return mainExec.execute(route, request, clientContext, execAware);
}
}
/**
* 添加内容
* */
private CloseableHttpResponse executeMy(HttpRoute route, HttpRequestWrapper request, HttpClientContext clientContext,
HttpExecutionAware execAware) throws IOException, HttpException{
// 添加认证信息
request.addHeader("token", name +"-"+ password);
// 执行mainExec
CloseableHttpResponse response = mainExec.execute(route, request, clientContext, execAware);
// 对成功应答验签
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() >= 200 && statusLine.getStatusCode() < 300) {
// 转换为可重复响应的entity
convertToRepeatableResponseEntity(response);
if (!(request.getOriginal() instanceof WxPayV3DownloadHttpGet)) {
if (!validate(response)) {
throw new HttpException("签名验证失败");
}
}
}
return response;
}
/**
* 转换为可重复响应的entity
* */
protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
if (entity != null && !entity.isRepeatable()) {
response.setEntity(newRepeatableEntity(entity));
}
}
/**
* 新entity
* */
protected HttpEntity newRepeatableEntity(HttpEntity entity) throws IOException {
byte[] content = EntityUtils.toByteArray(entity);
ByteArrayEntity newEntity = new ByteArrayEntity(content);
newEntity.setContentEncoding(entity.getContentEncoding());
newEntity.setContentType(entity.getContentType());
return newEntity;
}
/**
* 校验
* */
private boolean validate(CloseableHttpResponse response) throws IOException {
// 如果不是json格式,直接返回
if (!ContentType.APPLICATION_JSON.getMimeType()
.equals(ContentType.parse(
String.valueOf(response.getFirstHeader("Content-Type").getValue())
).getMimeType())) {
return true;
}
// 获取头部信息
Header name = response.getFirstHeader("name");
// 不存在数据
if (name == null) {
return false;
}
return !Objects.equals(name,this.name);
}
}
测试
这里请求的时候127.0.0.1,责任链拦截的域名也是这个
@Test
void contextLoads() {
String url = "http://127.0.0.1:9090/http/test";
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
// 交易码
request.setOutTradeNo("5566");
// 通知地址(回调地址)
request.setNotifyUrl("https://api.qq.com/");
request.setDescription("这是一个描述");
WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
// 商户openid
payer.setOpenid("openid");
request.setPayer(payer);
//构建金额信息
WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
//设置币种信息
amount.setCurrency("CNY");
//设置金额,这里用分计算
amount.setTotal(BigDecimal.ONE.multiply(BigDecimal.valueOf(100)).intValue());
request.setAmount(amount);
String res = post(url,JSON.toJSONString(request));
log.info("http请求结束:",res);
}
private String post(String url,String entity){
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(RequestConfig.custom()
// 从连接池获取连接的时间
.setConnectionRequestTimeout(1000)
// 建立连接的时间
.setConnectTimeout(1000)
// 读取数据的时间
.setSocketTimeout(3000)
.build());
httpPost.setEntity(new StringEntity(entity, ContentType.create("application/json", "utf-8")));
CloseableHttpClient httpClient = MyHttpClientBuilder
.create()
.setName("zhang")
.setPassword("1234").build();
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
//post方法有可能会没有返回值的情况
String responseString = null;
if (response.getEntity() != null) {
responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
}
//状态码判断200 204 成功
if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, entity, responseString);
return responseString;
}
//有错误提示信息返回
throw new MyException(responseString);
} catch (Exception e) {
log.error("异常:",e);
} finally {
// 关闭连接
httpPost.releaseConnection();
}
return null;
}