目錄
-
問題描述
前些天遇到一個需求,我們需要修改本地的dns解析,去驗證業務的正確性,想到的第一個方案就是修改本地的hosts文件。但是後來考量到這樣需要頻繁的修改本地磁盤文件。於是開始尋覓其他的方案。
-
方案查找
經過一番持續不斷的百度^_^,查找到一個工具包(https://github.com/tanhaichao/javahost)然後查看了一下他的核心代碼
public class DnsImpl extends AbstractDns {
//省略代碼
……
public boolean update(String host, String ip) {
return this.update(host, new String[]{ip});
}
public boolean update(String host, String[] ips) {
Object entry = this.createCacheEntry(host, ips);
this.getAddressCache().put(host, entry);
return true;
}
//省略代碼
……
}
public abstract class AbstractDns implements Dns {
// 省略代碼
……
protected Map<String, Object> getAddressCache() {
try {
Field cacheField = InetAddress.class.getDeclaredField("addressCache");
cacheField.setAccessible(true);
Object addressCache = cacheField.get(InetAddress.class);
Class clazz = addressCache.getClass();
Field cacheMapField = clazz.getDeclaredField("cache");
cacheMapField.setAccessible(true);
return (Map)cacheMapField.get(addressCache);
} catch (Exception var5) {
throw new RuntimeException(var5.getMessage(), var5);
}
}
protected Object createCacheEntry(String host, String[] ips) {
try {
long expiration = System.currentTimeMillis() + 315360000000L;
InetAddress[] addresses = new InetAddress[ips.length];
for(int i = 0; i < addresses.length; ++i) {
addresses[i] = InetAddress.getByAddress(host, InetAddress.getByName(ips[i]).getAddress());
}
String className = "java.net.InetAddress$CacheEntry";
Class<?> clazz = Class.forName(className);
Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
return constructor.newInstance(addresses, expiration);
} catch (Exception var9) {
throw new RuntimeException(var9.getMessage(), var9);
}
}
// 省略代碼
……
}
可以看到,這個工具類實際是通過反射機制,去修改了InetAddress中的cache值,來實現dns解析的修改。
-
解決問題
在解決問題的過程中,當使用CloseableHttpClient發起get請求的時候,能夠正常使用。分析具體原因如下:
public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {
//省略代碼
……
public void connect(ManagedHttpClientConnection conn, HttpHost host, InetSocketAddress localAddress, int connectTimeout, SocketConfig socketConfig, HttpContext context) throws IOException {
Lookup<ConnectionSocketFactory> registry = this.getSocketFactoryRegistry(context);
ConnectionSocketFactory sf = (ConnectionSocketFactory)registry.lookup(host.getSchemeName());
if (sf == null) {
throw new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported");
} else {
InetAddress[] addresses = host.getAddress() != null ? new InetAddress[]{host.getAddress()} : this.dnsResolver.resolve(host.getHostName());
int port = this.schemePortResolver.resolve(host);
for(int i = 0; i < addresses.length; ++i) {
InetAddress address = addresses[i];
boolean last = i == addresses.length - 1;
Socket sock = sf.createSocket(context);
sock.setSoTimeout(socketConfig.getSoTimeout());
sock.setReuseAddress(socketConfig.isSoReuseAddress());
sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
sock.setKeepAlive(socketConfig.isSoKeepAlive());
if (socketConfig.getRcvBufSize() > 0) {
sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
}
if (socketConfig.getSndBufSize() > 0) {
sock.setSendBufferSize(socketConfig.getSndBufSize());
}
int linger = socketConfig.getSoLinger();
if (linger >= 0) {
sock.setSoLinger(true, linger);
}
conn.bind(sock);
InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
if (this.log.isDebugEnabled()) {
this.log.debug("Connecting to " + remoteAddress);
}
try {
sock = sf.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context);
conn.bind(sock);
if (this.log.isDebugEnabled()) {
this.log.debug("Connection established " + conn);
}
return;
} catch (SocketTimeoutException var19) {
if (last) {
throw new ConnectTimeoutException(var19, host, addresses);
}
} catch (ConnectException var20) {
if (last) {
String msg = var20.getMessage();
if ("Connection timed out".equals(msg)) {
throw new ConnectTimeoutException(var20, host, addresses);
}
throw new HttpHostConnectException(var20, host, addresses);
}
} catch (NoRouteToHostException var21) {
if (last) {
throw var21;
}
}
if (this.log.isDebugEnabled()) {
this.log.debug("Connect to " + remoteAddress + " timed out. " + "Connection will be retried using another IP address");
}
}
}
}
}
在上述關鍵代碼中,我們看到CloseableHttpClient方法在做connect的時候,利用下述方法先獲取DNS數據。
InetAddress[] addresses = host.getAddress() != null ? new InetAddress[]{host.getAddress()} : this.dnsResolver.resolve(host.getHostName());
this.dnsResolver.resolve(host.getHostName())跟蹤該方法,我們能夠看到它是根據InetAddress.getAllByName獲取到相應的DNS解析記錄,然後根據dns解析去建立socket鏈接。
public InetAddress[] resolve(String host) throws UnknownHostException {
return InetAddress.getAllByName(host);
}