Skip to content

Commit ae4cbf9

Browse files
huotaihebinarywang
authored andcommitted
添加DNS解析器支持 (binarywang#171)
支持api.weixin.qq.com域名绑定指定的IP 防止域名对应的IP跳跃,导致防火墙策略失效!
1 parent 5234459 commit ae4cbf9

File tree

2 files changed

+378
-0
lines changed

2 files changed

+378
-0
lines changed
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
package me.chanjar.weixin.common.util.http;
2+
3+
import org.apache.commons.lang3.StringUtils;
4+
import org.apache.http.annotation.NotThreadSafe;
5+
import org.apache.http.auth.AuthScope;
6+
import org.apache.http.auth.UsernamePasswordCredentials;
7+
import org.apache.http.client.CredentialsProvider;
8+
import org.apache.http.client.HttpRequestRetryHandler;
9+
import org.apache.http.client.config.RequestConfig;
10+
import org.apache.http.config.Registry;
11+
import org.apache.http.config.RegistryBuilder;
12+
import org.apache.http.config.SocketConfig;
13+
import org.apache.http.conn.DnsResolver;
14+
import org.apache.http.conn.HttpClientConnectionManager;
15+
import org.apache.http.conn.socket.ConnectionSocketFactory;
16+
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
17+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
18+
import org.apache.http.impl.client.BasicCredentialsProvider;
19+
import org.apache.http.impl.client.CloseableHttpClient;
20+
import org.apache.http.impl.client.HttpClientBuilder;
21+
import org.apache.http.impl.client.HttpClients;
22+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
23+
import org.apache.http.protocol.HttpContext;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import java.io.IOException;
28+
import java.util.concurrent.TimeUnit;
29+
import java.util.concurrent.atomic.AtomicBoolean;
30+
31+
/**
32+
* httpclient 连接管理器 自带DNS解析
33+
*
34+
* 大部分代码拷贝自:DefaultApacheHttpClientBuilder
35+
*
36+
* @author Andy.Huo
37+
*/
38+
@NotThreadSafe
39+
public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder {
40+
protected final Logger log = LoggerFactory.getLogger(ApacheHttpDnsClientBuilder.class);
41+
private final AtomicBoolean prepared = new AtomicBoolean(false);
42+
private int connectionRequestTimeout = 3000;
43+
private int connectionTimeout = 5000;
44+
private int soTimeout = 5000;
45+
private int idleConnTimeout = 60000;
46+
private int checkWaitTime = 60000;
47+
private int maxConnPerHost = 10;
48+
private int maxTotalConn = 50;
49+
private String userAgent;
50+
51+
private DnsResolver dnsResover;
52+
53+
private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
54+
@Override
55+
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
56+
return false;
57+
}
58+
};
59+
private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
60+
private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
61+
private String httpProxyHost;
62+
private int httpProxyPort;
63+
private String httpProxyUsername;
64+
private String httpProxyPassword;
65+
66+
/**
67+
* 闲置连接监控线程
68+
*/
69+
private IdleConnectionMonitorThread idleConnectionMonitorThread;
70+
private HttpClientBuilder httpClientBuilder;
71+
72+
private ApacheHttpDnsClientBuilder() {
73+
}
74+
75+
public static ApacheHttpDnsClientBuilder get() {
76+
return new ApacheHttpDnsClientBuilder();
77+
}
78+
79+
@Override
80+
public ApacheHttpClientBuilder httpProxyHost(String httpProxyHost) {
81+
this.httpProxyHost = httpProxyHost;
82+
return this;
83+
}
84+
85+
@Override
86+
public ApacheHttpClientBuilder httpProxyPort(int httpProxyPort) {
87+
this.httpProxyPort = httpProxyPort;
88+
return this;
89+
}
90+
91+
@Override
92+
public ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername) {
93+
this.httpProxyUsername = httpProxyUsername;
94+
return this;
95+
}
96+
97+
@Override
98+
public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) {
99+
this.httpProxyPassword = httpProxyPassword;
100+
return this;
101+
}
102+
103+
@Override
104+
public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) {
105+
this.sslConnectionSocketFactory = sslConnectionSocketFactory;
106+
return this;
107+
}
108+
109+
/**
110+
* 获取链接的超时时间设置,默认3000ms
111+
* <p>
112+
* 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置).
113+
* </p>
114+
*
115+
* @param connectionRequestTimeout
116+
* 获取链接的超时时间设置(单位毫秒),默认3000ms
117+
*/
118+
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
119+
this.connectionRequestTimeout = connectionRequestTimeout;
120+
}
121+
122+
/**
123+
* 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用
124+
* <p>
125+
* 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置).
126+
* </p>
127+
*
128+
* @param connectionTimeout
129+
* 建立链接的超时时间设置(单位毫秒),默认5000ms
130+
*/
131+
public void setConnectionTimeout(int connectionTimeout) {
132+
this.connectionTimeout = connectionTimeout;
133+
}
134+
135+
/**
136+
* 默认NIO的socket超时设置,默认5000ms.
137+
*
138+
* @param soTimeout
139+
* 默认NIO的socket超时设置,默认5000ms.
140+
* @see java.net.SocketOptions#SO_TIMEOUT
141+
*/
142+
public void setSoTimeout(int soTimeout) {
143+
this.soTimeout = soTimeout;
144+
}
145+
146+
/**
147+
* 空闲链接的超时时间,默认60000ms.
148+
* <p>
149+
* 超时的链接将在下一次空闲链接检查是被销毁
150+
* </p>
151+
*
152+
* @param idleConnTimeout
153+
* 空闲链接的超时时间,默认60000ms.
154+
*/
155+
public void setIdleConnTimeout(int idleConnTimeout) {
156+
this.idleConnTimeout = idleConnTimeout;
157+
}
158+
159+
/**
160+
* 检查空间链接的间隔周期,默认60000ms.
161+
*
162+
* @param checkWaitTime
163+
* 检查空间链接的间隔周期,默认60000ms.
164+
*/
165+
public void setCheckWaitTime(int checkWaitTime) {
166+
this.checkWaitTime = checkWaitTime;
167+
}
168+
169+
/**
170+
* 每路的最大链接数,默认10
171+
*
172+
* @param maxConnPerHost
173+
* 每路的最大链接数,默认10
174+
*/
175+
public void setMaxConnPerHost(int maxConnPerHost) {
176+
this.maxConnPerHost = maxConnPerHost;
177+
}
178+
179+
/**
180+
* 最大总连接数,默认50
181+
*
182+
* @param maxTotalConn
183+
* 最大总连接数,默认50
184+
*/
185+
public void setMaxTotalConn(int maxTotalConn) {
186+
this.maxTotalConn = maxTotalConn;
187+
}
188+
189+
/**
190+
* 自定义httpclient的User Agent
191+
*
192+
* @param userAgent
193+
* User Agent
194+
*/
195+
public void setUserAgent(String userAgent) {
196+
this.userAgent = userAgent;
197+
}
198+
199+
public IdleConnectionMonitorThread getIdleConnectionMonitorThread() {
200+
return this.idleConnectionMonitorThread;
201+
}
202+
203+
private synchronized void prepare() {
204+
if (prepared.get()) {
205+
return;
206+
}
207+
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
208+
.register("http", this.plainConnectionSocketFactory).register("https", this.sslConnectionSocketFactory)
209+
.build();
210+
211+
@SuppressWarnings("resource")
212+
PoolingHttpClientConnectionManager connectionManager;
213+
if(dnsResover != null){
214+
if(log.isDebugEnabled()){
215+
log.debug("specified dns resolver.");
216+
}
217+
connectionManager = new PoolingHttpClientConnectionManager(registry, dnsResover);
218+
}else{
219+
if(log.isDebugEnabled()){
220+
log.debug("Not specified dns resolver.");
221+
}
222+
connectionManager = new PoolingHttpClientConnectionManager(registry);
223+
}
224+
225+
connectionManager.setMaxTotal(this.maxTotalConn);
226+
connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost);
227+
connectionManager
228+
.setDefaultSocketConfig(SocketConfig.copy(SocketConfig.DEFAULT).setSoTimeout(this.soTimeout).build());
229+
230+
this.idleConnectionMonitorThread = new IdleConnectionMonitorThread(connectionManager, this.idleConnTimeout,
231+
this.checkWaitTime);
232+
this.idleConnectionMonitorThread.setDaemon(true);
233+
this.idleConnectionMonitorThread.start();
234+
235+
this.httpClientBuilder = HttpClients.custom().setConnectionManager(connectionManager)
236+
.setConnectionManagerShared(true)
237+
.setDefaultRequestConfig(RequestConfig.custom().setSocketTimeout(this.soTimeout)
238+
.setConnectTimeout(this.connectionTimeout)
239+
.setConnectionRequestTimeout(this.connectionRequestTimeout).build())
240+
.setRetryHandler(this.httpRequestRetryHandler);
241+
242+
if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) {
243+
// 使用代理服务器 需要用户认证的代理服务器
244+
CredentialsProvider provider = new BasicCredentialsProvider();
245+
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort),
246+
new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword));
247+
this.httpClientBuilder.setDefaultCredentialsProvider(provider);
248+
}
249+
250+
if (StringUtils.isNotBlank(this.userAgent)) {
251+
this.httpClientBuilder.setUserAgent(this.userAgent);
252+
}
253+
prepared.set(true);
254+
}
255+
256+
@Override
257+
public CloseableHttpClient build() {
258+
if (!prepared.get()) {
259+
prepare();
260+
}
261+
return this.httpClientBuilder.build();
262+
}
263+
264+
public static class IdleConnectionMonitorThread extends Thread {
265+
private final HttpClientConnectionManager connMgr;
266+
private final int idleConnTimeout;
267+
private final int checkWaitTime;
268+
private volatile boolean shutdown;
269+
270+
public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr, int idleConnTimeout,
271+
int checkWaitTime) {
272+
super("IdleConnectionMonitorThread");
273+
this.connMgr = connMgr;
274+
this.idleConnTimeout = idleConnTimeout;
275+
this.checkWaitTime = checkWaitTime;
276+
}
277+
278+
@Override
279+
public void run() {
280+
try {
281+
while (!this.shutdown) {
282+
synchronized (this) {
283+
wait(this.checkWaitTime);
284+
this.connMgr.closeExpiredConnections();
285+
this.connMgr.closeIdleConnections(this.idleConnTimeout, TimeUnit.MILLISECONDS);
286+
}
287+
}
288+
} catch (InterruptedException ignore) {
289+
}
290+
}
291+
292+
public void trigger() {
293+
synchronized (this) {
294+
notifyAll();
295+
}
296+
}
297+
298+
public void shutdown() {
299+
this.shutdown = true;
300+
synchronized (this) {
301+
notifyAll();
302+
}
303+
}
304+
}
305+
306+
public DnsResolver getDnsResover() {
307+
return dnsResover;
308+
}
309+
310+
public void setDnsResover(DnsResolver dnsResover) {
311+
this.dnsResover = dnsResover;
312+
}
313+
314+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package me.chanjar.weixin.common.util.http;
2+
3+
import java.net.InetAddress;
4+
import java.net.UnknownHostException;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import org.apache.http.conn.DnsResolver;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
/**
13+
* 微信DNS域名解析器,将微信域名绑定到指定IP
14+
* --------------------------------------------
15+
* 适用于服务器端调用微信服务器需要开通出口防火墙情况
16+
*
17+
* Created by Andy Huo on 17/03/28.
18+
*/
19+
public class WxDnsResolver implements DnsResolver {
20+
21+
protected final Logger log = LoggerFactory.getLogger(WxDnsResolver.class);
22+
23+
private static Map<String, InetAddress[]> MAPPINGS = new HashMap<String, InetAddress[]>();
24+
25+
private final static String WECHAT_API_URL = "api.weixin.qq.com";
26+
27+
private String wxApiIp;
28+
29+
public WxDnsResolver(String ip){
30+
31+
this.wxApiIp = ip;
32+
this.init();
33+
}
34+
35+
private void init(){
36+
if(log.isDebugEnabled()){
37+
log.debug("init wechat dns config with ip {}", wxApiIp);
38+
}
39+
try {
40+
MAPPINGS.put(WECHAT_API_URL, new InetAddress[]{InetAddress.getByName(wxApiIp)});
41+
} catch (UnknownHostException e) {
42+
//如果初始化DNS配置失败则使用默认配置,不影响服务的启动
43+
log.error("init WxDnsResolver error", e);
44+
MAPPINGS = new HashMap<String, InetAddress[]>();
45+
}
46+
47+
}
48+
49+
@Override
50+
public InetAddress[] resolve(String host) throws UnknownHostException {
51+
52+
53+
return MAPPINGS.containsKey(host) ? MAPPINGS.get(host) : new InetAddress[0];
54+
}
55+
56+
public String getWxApiIp() {
57+
return wxApiIp;
58+
}
59+
60+
public void setWxApiIp(String wxApiIp) {
61+
this.wxApiIp = wxApiIp;
62+
this.init();
63+
}
64+
}

0 commit comments

Comments
 (0)