前言
因工作需要频繁变更hosts, 故须自己实现一个动态管理器, 市面上其实已经有了类似的软件,比如switchhosts!
但因为不好集成其他功能(如远程连接KVM),所以还是决定自己开发一套。
原理
使用之前强烈建议先阅读本文了解原理:
Java实现实时生效hosts文件修改
准备
使用java
实现的话, alibaba
实际上已经开源了一个叫java-dns-cache-manipulator的项目, 使用起来非常简单, 但是未使用java17
进行测试过。
由于与网络相关, 我们同时引入okhttp
和java-dns-cache-manipulator
的最新版本:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dns-cache-manipulator</artifactId>
<version>RELEASE</version>
</dependency>
需求
我有一个NAS
,可以通过http://192.168.31.6:5000
进行访问, 我希望动态修改域名nas.y4d.com
映射到192.168.31.6
。
使用
dns-cache.properties
nas.y4d.com=192.168.31.66
DnsCache.kt
fun main() {
DnsCacheManipulator.loadDnsCacheConfig()
val okHttpClient = OkHttpClient()
val request =
Request.Builder().get().header("Accept-Language", "zh-CN,zh;q=0.9").url("http://nas.y4d.com:5000").build()
val response = okHttpClient.newCall(request).execute()
println(response.body.string())
}
此时如果直接使用java17
运行会报错:
VM options
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens
用法:
--add-opens <source-module>/<package>=<target-module>(,<target-module>)*
其中<source-module>
和<target-module>
是模块名称,<package>
是包的名称。
该--add-opens
选项可以多次使用,但对于源模块和包名称的任何特定组合最多使用一次。每个实例的作用是将指定包的限定打开从源模块> 添加到目标模块。opens
本质上是模块声明中子句的命令行形式,或者是Module::addOpens
方法的无限制形式的调用。因此,只要目标模块读取源模块,目标模块中的代码就能够使用核心反射 API 来访问源模块的命名包中的所有类型(公共类型和其他类型)。
参考: https://openjdk.org/jeps/261#Breaking-encapsulation
问题
如果对于新的域名映射仅限于java
程序进行访问时, 上面方案是没有问题的, 但有时我们需要借助外部程序进行访问, 比如浏览器, 那么上述方案就行不通了, 因此我们还是需要对全局的hosts
文件进行修改来达到全局dns
配置的目的.
修改hosts
package com.y4d.host;
import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Security;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
public class Hosts {
private static final Path HOSTS_FILE_PATH = Paths.get(getHostFile());
private static final String UPDATE_COMMENT = "# __UPDATE_BY_JAVA__";
private static final Path BACKUP = Paths.get(HOSTS_FILE_PATH + ".bak");
static {
try {
Runtime.getRuntime().addShutdownHook(new Thread(Hosts::rollback));
if (!Files.exists(HOSTS_FILE_PATH)) {
Files.createFile(HOSTS_FILE_PATH);
}
boolean isOriginHosts = Files.readAllLines(HOSTS_FILE_PATH).stream().noneMatch(UPDATE_COMMENT::equals);
if (isOriginHosts) {
Files.deleteIfExists(BACKUP);
printHosts("backup");
Files.copy(HOSTS_FILE_PATH, BACKUP);
}
} catch (IOException e) {
// 备份失败则退出程序
throw new RuntimeException(e);
}
}
/**
* 获取host文件路径
*
* @return 文件路径
*/
public static String getHostFile() {
String fileName;
// 判断系统
if ("linux".equalsIgnoreCase(System.getProperty("os.name"))) {
fileName = "/etc/hosts";
} else {
fileName = System.getenv("windir") + "\\system32\\drivers\\etc\\hosts";
}
return fileName;
}
private static void rollback() {
try {
Files.deleteIfExists(HOSTS_FILE_PATH);
Files.copy(BACKUP, HOSTS_FILE_PATH);
flushDns();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void printHosts(String name) throws IOException {
System.out.println("option: " + name);
Files.readAllLines(HOSTS_FILE_PATH).forEach(System.out::println);
}
private static boolean update(Properties hosts) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
hosts.forEach((k, v) -> map.put(String.valueOf(k), String.valueOf(v)));
return update(map);
}
/**
* 更新hosts
*
* @param hosts 这里key表示domain, value表示ip, 因为hosts可以对一个ip配置多个域名, 而java中map不允许存在重复, 所以入参这里有调换
* @return
*/
private static boolean update(Map<String, String> hosts) {
Security.setProperty("networkaddress.cache.ttl", "0");
Security.setProperty("networkaddress.cache.negative.ttl", "0");
StringBuilder sb = new StringBuilder(UPDATE_COMMENT);
hosts.forEach((k, v) -> {
if (v == null || v.isEmpty()) {
return;
}
sb.append(System.lineSeparator());
String line = String.join(" ", v, k);
sb.append(line);
});
try {
Path path = Files.writeString(HOSTS_FILE_PATH, sb.toString());
flushDns();
printHosts("update");
return path.equals(HOSTS_FILE_PATH);
} catch (IOException e) {
e.printStackTrace();
System.err.println("更新hosts失败");
// 更新失败则回退
rollback();
}
return false;
}
private static void flushDns() throws IOException {
Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "ipconfig /flushdns"});
}
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
HashMap<String, String> hosts = new HashMap<>();
String domain1 = "y.nas.xyz";
String domain2 = "yw.nas.xyz";
String ip = "192.168.31.66";
hosts.put(domain1, ip);
hosts.put(domain2, ip);
update(hosts);
Desktop.getDesktop().browse(new URI("http://" + domain1 + ":5000"));
Desktop.getDesktop().browse(new URI("http://" + domain2 + ":5000"));
TimeUnit.SECONDS.sleep(5);
}
}