From c3be3ca7f9d56a193cee640e047123e099dcb05d Mon Sep 17 00:00:00 2001 From: Stormeye Wu Date: Mon, 8 Apr 2019 15:49:14 +0800 Subject: [PATCH] =?UTF-8?q?[httpclient][=E6=96=B0=E5=A2=9E]:=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=87=8D=E8=AF=95=E6=AC=A1=E6=95=B0=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=AE=BF=E9=97=AE=E5=BE=AE=E4=BF=A1=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E9=97=AE=E9=A2=98(org.apache.http.NoHttpResponseExcep?= =?UTF-8?q?tion:=20api.weixin.qq.com:443=20failed=20to=20respond)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iformall/common/MipHttpClientBuilder.java | 338 ++++++++++++++++++ .../wechat/WxOpenInRedisDBConfigStorage.java | 7 + 2 files changed, 345 insertions(+) create mode 100644 mallinkService/src/main/java/com/iformall/common/MipHttpClientBuilder.java diff --git a/mallinkService/src/main/java/com/iformall/common/MipHttpClientBuilder.java b/mallinkService/src/main/java/com/iformall/common/MipHttpClientBuilder.java new file mode 100644 index 0000000..e8083e6 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/common/MipHttpClientBuilder.java @@ -0,0 +1,338 @@ +package com.iformall.common; + +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.NoHttpResponseException; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.config.SocketConfig; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.DnsResolver; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.protocol.HttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * httpclient 连接管理器 自带DNS解析. + *

大部分代码拷贝自:DefaultApacheHttpClientBuilder

+ * + * @author Andy.Huo + */ +public class MipHttpClientBuilder implements ApacheHttpClientBuilder { + protected final Logger log = LoggerFactory.getLogger(MipHttpClientBuilder.class); + private final AtomicBoolean prepared = new AtomicBoolean(false); + private int connectionRequestTimeout = 3000; + private int connectionTimeout = 5000; + private int soTimeout = 5000; + private int idleConnTimeout = 60000; + private int checkWaitTime = 60000; + private int maxConnPerHost = 10; + private int maxTotalConn = 50; + private String userAgent; + + private DnsResolver dnsResover; + + private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { + @Override + public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { + if (executionCount > 3) { + log.warn("Maximum tries reached for client http pool "); + return false; + } + if (exception instanceof NoHttpResponseException) { // NoHttpResponseException 重试 + log.warn("NoHttpResponseException on " + executionCount + " call"); + return true; + } + if (exception instanceof ConnectTimeoutException) { // 连接超时重试 + log.warn("ConnectTimeoutException on " + executionCount + " call"); + return true; + } + //if (exception instanceof SocketTimeoutException) { //响应超时不重试,避免造成业务数据不一致 + // log.warn("SocketTimeoutException on " + executionCount + " call"); + //} + return false; + } + }; + private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); + private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); + private String httpProxyHost; + private int httpProxyPort; + private String httpProxyUsername; + private String httpProxyPassword; + + /** + * 闲置连接监控线程. + */ + private IdleConnectionMonitorThread idleConnectionMonitorThread; + private HttpClientBuilder httpClientBuilder; + + private MipHttpClientBuilder() { + } + + public static MipHttpClientBuilder get() { + return new MipHttpClientBuilder(); + } + + @Override + public ApacheHttpClientBuilder httpProxyHost(String httpProxyHost) { + this.httpProxyHost = httpProxyHost; + return this; + } + + @Override + public ApacheHttpClientBuilder httpProxyPort(int httpProxyPort) { + this.httpProxyPort = httpProxyPort; + return this; + } + + @Override + public ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername) { + this.httpProxyUsername = httpProxyUsername; + return this; + } + + @Override + public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) { + this.httpProxyPassword = httpProxyPassword; + return this; + } + + @Override + public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) { + this.sslConnectionSocketFactory = sslConnectionSocketFactory; + return this; + } + + /** + * 获取链接的超时时间设置,默认3000ms + *

+ * 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置). + *

+ * + * @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms + */ + public void setConnectionRequestTimeout(int connectionRequestTimeout) { + this.connectionRequestTimeout = connectionRequestTimeout; + } + + /** + * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 + *

+ * 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). + *

+ * + * @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms + */ + public void setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + /** + * 默认NIO的socket超时设置,默认5000ms. + * + * @param soTimeout 默认NIO的socket超时设置,默认5000ms. + * @see java.net.SocketOptions#SO_TIMEOUT + */ + public void setSoTimeout(int soTimeout) { + this.soTimeout = soTimeout; + } + + /** + * 空闲链接的超时时间,默认60000ms. + *

+ * 超时的链接将在下一次空闲链接检查是被销毁 + *

+ * + * @param idleConnTimeout 空闲链接的超时时间,默认60000ms. + */ + public void setIdleConnTimeout(int idleConnTimeout) { + this.idleConnTimeout = idleConnTimeout; + } + + /** + * 检查空间链接的间隔周期,默认60000ms. + * + * @param checkWaitTime 检查空间链接的间隔周期,默认60000ms. + */ + public void setCheckWaitTime(int checkWaitTime) { + this.checkWaitTime = checkWaitTime; + } + + /** + * 每路的最大链接数,默认10. + * + * @param maxConnPerHost 每路的最大链接数,默认10 + */ + public void setMaxConnPerHost(int maxConnPerHost) { + this.maxConnPerHost = maxConnPerHost; + } + + /** + * 最大总连接数,默认50. + * + * @param maxTotalConn 最大总连接数,默认50 + */ + public void setMaxTotalConn(int maxTotalConn) { + this.maxTotalConn = maxTotalConn; + } + + /** + * 自定义httpclient的User Agent. + * + * @param userAgent User Agent + */ + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } + + public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { + return this.idleConnectionMonitorThread; + } + + private synchronized void prepare() { + if (prepared.get()) { + return; + } + + Registry registry = + RegistryBuilder.create() + .register("http", this.plainConnectionSocketFactory) + .register("https", this.sslConnectionSocketFactory) + .build(); + + @SuppressWarnings("resource") + PoolingHttpClientConnectionManager connectionManager; + if (dnsResover != null) { + if (log.isDebugEnabled()) { + log.debug("specified dns resolver."); + } + connectionManager = new PoolingHttpClientConnectionManager(registry, dnsResover); + } else { + if (log.isDebugEnabled()) { + log.debug("Not specified dns resolver."); + } + connectionManager = new PoolingHttpClientConnectionManager(registry); + } + + connectionManager.setMaxTotal(this.maxTotalConn); + connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost); + connectionManager + .setDefaultSocketConfig(SocketConfig.copy(SocketConfig.DEFAULT).setSoTimeout(this.soTimeout).build()); + + this.idleConnectionMonitorThread = new IdleConnectionMonitorThread( + connectionManager, this.idleConnTimeout, this.checkWaitTime); + this.idleConnectionMonitorThread.setDaemon(true); + this.idleConnectionMonitorThread.start(); + + this.httpClientBuilder = HttpClients.custom().setConnectionManager(connectionManager) + .setConnectionManagerShared(true) + .setDefaultRequestConfig( + RequestConfig.custom() + .setSocketTimeout(this.soTimeout) + .setConnectTimeout(this.connectionTimeout) + .setConnectionRequestTimeout(this.connectionRequestTimeout) + .build()) + .setRetryHandler(this.httpRequestRetryHandler); + + if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) { + // 使用代理服务器 需要用户认证的代理服务器 + CredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort), + new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword)); + this.httpClientBuilder.setDefaultCredentialsProvider(provider); + this.httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort)); + } + + if (StringUtils.isNotBlank(this.userAgent)) { + this.httpClientBuilder.setUserAgent(this.userAgent); + } + prepared.set(true); + } + + @Override + public CloseableHttpClient build() { + if (!prepared.get()) { + prepare(); + } + return this.httpClientBuilder.build(); + } + + public DnsResolver getDnsResover() { + return dnsResover; + } + + public void setDnsResover(DnsResolver dnsResover) { + this.dnsResover = dnsResover; + } + + public static class IdleConnectionMonitorThread extends Thread { + private final HttpClientConnectionManager connMgr; + private final int idleConnTimeout; + private final int checkWaitTime; + private volatile boolean shutdown; + + /** + * 构造方法. + */ + public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr, int idleConnTimeout, int checkWaitTime) { + super("IdleConnectionMonitorThread"); + this.connMgr = connMgr; + this.idleConnTimeout = idleConnTimeout; + this.checkWaitTime = checkWaitTime; + } + + @Override + public void run() { + try { + while (!this.shutdown) { + synchronized (this) { + wait(this.checkWaitTime); + this.connMgr.closeExpiredConnections(); + this.connMgr.closeIdleConnections(this.idleConnTimeout, TimeUnit.MILLISECONDS); + } + } + } catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + } + } + + /** + * 触发. + */ + public void trigger() { + synchronized (this) { + notifyAll(); + } + } + + /** + * 关闭. + */ + public void shutdown() { + this.shutdown = true; + synchronized (this) { + notifyAll(); + } + } + } + +} diff --git a/mlWechatOpen/src/main/java/com/iformall/service/wechat/WxOpenInRedisDBConfigStorage.java b/mlWechatOpen/src/main/java/com/iformall/service/wechat/WxOpenInRedisDBConfigStorage.java index f14fe36..90f340a 100644 --- a/mlWechatOpen/src/main/java/com/iformall/service/wechat/WxOpenInRedisDBConfigStorage.java +++ b/mlWechatOpen/src/main/java/com/iformall/service/wechat/WxOpenInRedisDBConfigStorage.java @@ -1,9 +1,11 @@ package com.iformall.service.wechat; +import com.iformall.common.MipHttpClientBuilder; import com.iformall.domain.po.WxAuthorizerInfo; import com.iformall.domain.po.WxComponentVerifyTicket; import com.iformall.mapper.WxAuthorizerInfoMapper; import com.iformall.mapper.WxComponentVerifyTicketMapper; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; import org.apache.commons.lang3.StringUtils; @@ -245,4 +247,9 @@ public class WxOpenInRedisDBConfigStorage extends WxOpenInMemoryConfigStorage { // TODO update to DB } + + @Override + public ApacheHttpClientBuilder getApacheHttpClientBuilder() { + return MipHttpClientBuilder.get(); + } }