背景说明
在我们的工具开发过程中,需要向预发环境请求一些数据。在本地请求预发环境的数据,我们是通过本地绑定host地址来访问预发链接的。我们应用是部署在Docker容器中的,发送HTTP请求用的是HttpClient工具包。那么在工具中要如何实现我们的这个需求呢,本文就基于我们实际的操作过程做一个简单记录。
hosts文件的作用
在开始之前,我们需要明确一下本地绑定host的作用原理。
DNS(Domain Name System)服务是和 HTTP 协议一样位于应用层的协议,它提供域名到 IP 地址之间的解析服务。用户通常使用主机名或域名来访问对方的计算机,而不是直接通过 IP 地址访问。因为与 IP 地址的一组纯数字相比,用字母配合数字的表示形式来指定计算机名更符合人类的记忆习惯。
但要让计算机去理解名称,相对而言就变得困难了。因为计算机更擅长处理一长串数字。为了解决上述的问题,DNS 服务应运而生。DNS 协议提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。(——from 《图解HTTP》)
而Hosts文件是一个没有扩展名的操作系统文件,以表的形式存储了主机名和IP地址的映射关系。Hosts又称host table,译为“主机表”。现代系统中,虽然DNS取代了主机表,但主机表的应用依旧很广。和DNS不同的是,用户可以直接对Hosts文件进行控制。Hosts文件是大多数系统都存在的一个小型主机表。Hosts文件中包含了本地网络重要的主机名和地址信息,查询Hosts文件得到的结果比通过查询DNS得到的结果优先级更高。(——from 维基百科)
如下图所示,正常的DNS域名解析过程如下,而如果要解析的域名配置了hosts文件,那么就不需要再去DNS服务器做域名解析了。
解决方案
思路一:在Docker容器中也配置一下hosts文件
这是很自然就想到的一个方案,因为既然本地可以通过配置hosts文件实现预发环境的访问,那么在Docker容器中配置hosts文件应该也能实现同样的效果。而要实现在Docker容器中配置hosts文件,通常也可以用不同的方式实现。
- 方式一:Docker容器启动之后,进入Docker容器中直接编辑
/etc/hosts
文件
这种方式很直接,就是进入Docker容器之后sudo vim /etc/hosts
,然后把要绑定的域名映射加到文件中即可。
但是这种方式有很大的局限性:一是一旦Docker容器重启,之前配置的hosts内容就会失效;二是如果机器比较多,配置器起来就相当麻烦
- 方式二:通过Docker启动参数配置hosts
示例如下:
# 添加单个hosts
docker run -it nginx --add-host=localhost:127.0.0.1
#添加多个hosts
docker run -it nginx --add-host=localhost:127.0.0.1 --add-host=example.com:127.0.0.1
- 方式三:在项目的Dockerfile中实现
/etc/hosts
的配置
如下,首先我在Dockerfile设置环境信息的地方加了一下内容:
# 在预发配置hosts
echo "setting hosts..."
if [ "$CURRENT_ENV" = "staging" ]; then
echo "now is staging env, need to setting hosts..."
sh "$APP_HOME/bin/hosts.sh"
fi
然后,在hosts.sh
脚本中实现/etc/hosts
配置(注意:普通用户一般没有/etc/hosts
文件的写权限,所以在写入文件之前,增加写权限至关重要,否则会报“权限不够”从而配置失败)
#!/bin/sh
echo "starting to config hosts..."
echo "current user is: "
whoami
sudo chmod 666 /etc/hosts
sudo echo "# custom hosts" >> /etc/hosts
sudo echo "123.123.123.123 pre1-xx.yy.com" >> /etc/hosts
sudo echo "123.123.123.123 pre2-xx.yy.com" >> /etc/hosts
echo "hosts config done"
思路二:通过HttpClient的DnsResolver解决
这种方式的核心是实现 org.apache.http.conn.DnsResolver
的resolve()
方法,然后在HttpClientConnectionManager
实例化的时候传入DnsResolver
的实例。
package com.amwalle.walle.util;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
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.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
/**
* @program: walle
* @description: send a request
* @author: pengyongjun
* @create: 2022.07.18 19:18
**/
public class HttpUtil {
public static CloseableHttpClient getHttpClient() throws KeyManagementException, NoSuchAlgorithmException {
TrustManager[] trustManagers = new TrustManager[1];
TrustManager trustManager = new TheTrustManager();
trustManagers[0] = trustManager;
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", socketFactory).build();
MyDnsResolver dnsResolver = new MyDnsResolver("pre1-xx.yy.com", "123.123.123.123");
dnsResolver.addResolve("pre2-xx.yy.com", "123.123.123.123");
HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry, null, null, dnsResolver);
return HttpClients.custom().setConnectionManager(connectionManager).build();
}
private static class TheTrustManager implements TrustManager, X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
private static class MyDnsResolver implements DnsResolver {
private final Map<String, InetAddress[]> MAPPINGS;
public MyDnsResolver(String host, String ip) {
MAPPINGS = new HashMap<>();
try {
MAPPINGS.put(host, new InetAddress[]{InetAddress.getByName(ip)});
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
public void addResolve(String host, String ip) {
try {
MAPPINGS.put(host, new InetAddress[]{InetAddress.getByName(ip)});
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
public InetAddress[] resolve(String host) throws UnknownHostException {
return MAPPINGS.containsKey(host) ? MAPPINGS.get(host) : new InetAddress[0];
}
}
public static void main(String[] args) {
try (final CloseableHttpClient httpClient = HttpUtil.getHttpClient()) {
final HttpGet httpGet = new HttpGet("https://pre1-xx.yy.com/scene/sceneOpt.do?_lang=zh&SSO_TICKET=533e04b85f4542dfbb41f128b493e300240dca00");
CloseableHttpResponse response = httpClient.execute(httpGet);
System.out.println(EntityUtils.toString(response.getEntity()));
} catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
}
}
}